1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::Handle - RT's database handle
56 BEGIN { RT::LoadConfig() };
61 C<RT::Handle> is RT specific wrapper over one of L<DBIx::SearchBuilder::Handle>
62 classes. As RT works with different types of DBs we subclass repsective handler
63 from L<DBIx::SerachBuilder>. Type of the DB is defined by C<DatabasseType> RT's
64 config option. You B<must> load this module only when the configs have been
78 =head2 FinalizeDatabaseType
80 Sets RT::Handle's superclass to the correct subclass of
81 L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration.
85 sub FinalizeDatabaseType {
87 use base "DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType');
91 die "Unable to load DBIx::SearchBuilder database handle for '". RT->Config->Get('DatabaseType') ."'.\n".
92 "Perhaps you've picked an invalid database type or spelled it incorrectly.\n".
99 Connects to RT's database using credentials and options from the RT config.
108 my $db_type = RT->Config->Get('DatabaseType');
109 if ( $db_type eq 'Oracle' ) {
110 $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
111 $ENV{'NLS_NCHAR'} = "AL32UTF8";
114 $self->SUPER::Connect(
115 User => RT->Config->Get('DatabaseUser'),
116 Password => RT->Config->Get('DatabasePassword'),
120 if ( $db_type eq 'mysql' ) {
121 my $version = $self->DatabaseVersion;
122 ($version) = $version =~ /^(\d+\.\d+)/;
123 $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
127 if ( $db_type eq 'Pg' ) {
128 my $version = $self->DatabaseVersion;
129 ($version) = $version =~ /^(\d+\.\d+)/;
130 $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
135 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
140 Build the DSN for the RT database. Doesn't take any parameters, draws all that
148 # Unless the database port is a positive integer, we really don't want to pass it.
149 my $db_port = RT->Config->Get('DatabasePort');
150 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
151 my $db_host = RT->Config->Get('DatabaseHost');
152 $db_host = undef unless $db_host;
153 my $db_name = RT->Config->Get('DatabaseName');
154 my $db_type = RT->Config->Get('DatabaseType');
155 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
156 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
160 Database => $db_name,
163 RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
164 DisconnectHandleOnDestroy => 1,
166 if ( $db_type eq 'Oracle' && $db_host ) {
167 $args{'SID'} = delete $args{'Database'};
169 $self->SUPER::BuildDSN( %args );
174 Returns the DSN for this handle. In order to get correct value you must
175 build DSN first, see L</BuildDSN>.
177 This is method can be called as class method, in this case creates
178 temporary handle object, L</BuildDSN builds DSN> and returns it.
184 return $self->SUPER::DSN if ref $self;
186 my $handle = $self->new;
193 Returns a DSN suitable for database creates and drops
194 and user creates and drops.
196 Gets RT's DSN first (see L<DSN>) and then change it according
197 to requirements of a database system RT's using.
204 my $db_name = RT->Config->Get('DatabaseName');
205 my $db_type = RT->Config->Get('DatabaseType');
207 my $dsn = $self->DSN;
208 if ( $db_type eq 'mysql' ) {
209 # with mysql, you want to connect sans database to funge things
210 $dsn =~ s/dbname=\Q$db_name//;
212 elsif ( $db_type eq 'Pg' ) {
213 # with postgres, you want to connect to template1 database
214 $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
219 =head2 Database compatibility and integrity checks
227 $self = new $self unless ref $self;
229 unless ($RT::Handle and $RT::Handle->dbh) {
231 unless ( eval { RT::ConnectToDatabase(); 1 } ) {
232 return (0, 'no connection', "$@");
236 require RT::CurrentUser;
237 my $test_user = RT::CurrentUser->new;
238 $test_user->Load('RT_System');
239 unless ( $test_user->id ) {
240 return (0, 'no system user', "Couldn't find RT_System user in the DB '". $self->DSN ."'");
243 $test_user = RT::CurrentUser->new;
244 $test_user->Load('Nobody');
245 unless ( $test_user->id ) {
246 return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
249 return $RT::Handle->dbh;
252 sub CheckCompatibility {
255 my $state = shift || 'post';
257 my $db_type = RT->Config->Get('DatabaseType');
258 if ( $db_type eq "mysql" ) {
259 # Check which version we're running
260 my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
261 return (0, "couldn't get version of the mysql server")
264 ($version) = $version =~ /^(\d+\.\d+)/;
265 return (0, "RT is unsupported on MySQL versions before 4.1. Your version is $version.")
268 # MySQL must have InnoDB support
269 local $dbh->{FetchHashKeyName} = 'NAME_lc';
270 my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no");
271 if ( $innodb eq "no" ) {
272 return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
273 "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n".
274 "and check that there are no 'skip-innodb' lines in your my.cnf.");
275 } elsif ( $innodb eq "disabled" ) {
276 return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
277 "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n");
280 if ( $state eq 'post' ) {
281 my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Tickets")->[1];
282 unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
283 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
286 $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
287 unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
288 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
289 ."Follow instructions in the UPGRADING.mysql file.");
293 my $max_packet = ($dbh->selectrow_array("show variables like 'max_allowed_packet'"))[1];
294 if ($state =~ /^(create|post)$/ and $max_packet <= (1024 * 1024)) {
295 my $max_packet = sprintf("%.1fM", $max_packet/1024/1024);
296 warn "max_allowed_packet is set to $max_packet, which limits the maximum attachment or email size that RT can process. Consider adjusting MySQL's max_allowed_packet setting.\n";
305 my $dbh = $RT::Handle->dbh;
306 local $dbh->{'RaiseError'} = 0;
307 local $dbh->{'PrintError'} = 0;
308 my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
309 $has ||= ($dbh->selectrow_array(
310 "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
313 return 0 unless lc($has||'') eq "yes";
317 =head2 Database maintanance
319 =head3 CreateDatabase $DBH
321 Creates a new database. This method can be used as class method.
323 Takes DBI handle. Many database systems require special handle to
324 allow you to create a new database, so you have to use L<SystemDSN>
325 method during connection.
327 Fetches type and name of the DB from the config.
333 my $dbh = shift or return (0, "No DBI handle provided");
334 my $db_type = RT->Config->Get('DatabaseType');
335 my $db_name = RT->Config->Get('DatabaseName');
338 if ( $db_type eq 'SQLite' ) {
339 return (1, 'Skipped as SQLite doesn\'t need any action');
341 elsif ( $db_type eq 'Oracle' ) {
342 my $db_user = RT->Config->Get('DatabaseUser');
343 my $db_pass = RT->Config->Get('DatabasePassword');
345 "CREATE USER $db_user IDENTIFIED BY $db_pass"
346 ." default tablespace USERS"
347 ." temporary tablespace TEMP"
348 ." quota unlimited on USERS"
351 return $status, "Couldn't create user $db_user identified by $db_pass."
352 ."\nError: ". $dbh->errstr;
354 $status = $dbh->do( "GRANT connect, resource TO $db_user" );
356 return $status, "Couldn't grant connect and resource to $db_user."
357 ."\nError: ". $dbh->errstr;
359 return (1, "Created user $db_user. All RT's objects should be in his schema.");
361 elsif ( $db_type eq 'Pg' ) {
362 $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
365 $status = $dbh->do("CREATE DATABASE $db_name");
367 return ($status, $DBI::errstr);
370 =head3 DropDatabase $DBH
372 Drops RT's database. This method can be used as class method.
374 Takes DBI handle as first argument. Many database systems require
375 a special handle to allow you to drop a database, so you may have
376 to use L<SystemDSN> when acquiring the DBI handle.
378 Fetches the type and name of the database from the config.
384 my $dbh = shift or return (0, "No DBI handle provided");
386 my $db_type = RT->Config->Get('DatabaseType');
387 my $db_name = RT->Config->Get('DatabaseName');
389 if ( $db_type eq 'Oracle' ) {
390 my $db_user = RT->Config->Get('DatabaseUser');
391 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
393 return 0, "Couldn't drop user $db_user."
394 ."\nError: ". $dbh->errstr;
396 return (1, "Successfully dropped user '$db_user' with his schema.");
398 elsif ( $db_type eq 'SQLite' ) {
400 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
401 unlink $path or return (0, "Couldn't remove '$path': $!");
404 $dbh->do("DROP DATABASE ". $db_name)
405 or return (0, $DBI::errstr);
417 my $base_path = shift || $RT::EtcPath;
419 my $db_type = RT->Config->Get('DatabaseType');
420 return (1) if $db_type eq 'SQLite';
422 $dbh = $self->dbh if !$dbh && ref $self;
423 return (0, "No DBI handle provided") unless $dbh;
425 return (0, "'$base_path' doesn't exist") unless -e $base_path;
428 if ( -d $base_path ) {
429 $path = File::Spec->catfile( $base_path, "acl.$db_type");
430 $path = $self->GetVersionFile($dbh, $path);
432 $path = File::Spec->catfile( $base_path, "acl")
433 unless $path && -e $path;
434 return (0, "Couldn't find ACLs for $db_type")
441 do $path || return (0, "Couldn't load ACLs: " . $@);
443 foreach my $statement (@acl) {
444 my $sth = $dbh->prepare($statement)
445 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
446 unless ( $sth->execute ) {
447 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
460 my $base_path = (shift || $RT::EtcPath);
462 $dbh = $self->dbh if !$dbh && ref $self;
463 return (0, "No DBI handle provided") unless $dbh;
465 my $db_type = RT->Config->Get('DatabaseType');
468 if ( -d $base_path ) {
469 $file = $base_path . "/schema." . $db_type;
474 $file = $self->GetVersionFile( $dbh, $file );
476 return (0, "Couldn't find schema file(s) '$file*'");
478 unless ( -f $file && -r $file ) {
479 return (0, "File '$file' doesn't exist or couldn't be read");
484 open( my $fh_schema, '<', $file ) or die $!;
487 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
491 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
495 if ( $line =~ /;(\s*)$/ ) {
496 $statement =~ s/;(\s*)$//g;
497 push @schema, $statement;
501 close $fh_schema; close $fh_schema_local;
503 if ( $db_type eq 'Oracle' ) {
504 my $db_user = RT->Config->Get('DatabaseUser');
505 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
507 return $status, "Couldn't set current schema to $db_user."
508 ."\nError: ". $dbh->errstr;
512 local $SIG{__WARN__} = sub {};
514 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
515 foreach my $statement (@schema) {
516 if ( $statement =~ /^\s*;$/ ) {
520 my $sth = $dbh->prepare($statement)
521 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
522 unless ( $sth->execute or $is_local ) {
523 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
526 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
530 =head1 GetVersionFile
532 Takes base name of the file as argument, scans for <base name>-<version> named
533 files and returns file name with closest version to the version of the RT DB.
540 my $base_name = shift;
542 my $db_version = ref $self
543 ? $self->DatabaseVersion
545 my $tmp = RT::Handle->new;
547 $tmp->DatabaseVersion;
551 my @files = File::Glob::bsd_glob("$base_name*");
552 return '' unless @files;
554 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
556 foreach ( reverse sort cmp_version keys %version ) {
557 if ( cmp_version( $db_version, $_ ) >= 0 ) {
563 return defined $version? $version{ $version } : undef;
575 sub cmp_version($$) {
577 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
578 split /([^0-9]+)/, $a;
579 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
580 split /([^0-9]+)/, $b;
582 ? push @b, (0) x (@a-@b)
583 : push @a, (0) x (@b-@a);
584 for ( my $i = 0; $i < @a; $i++ ) {
585 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
597 =head2 InsertInitialData
599 Inserts system objects into RT's DB, like system user or 'nobody',
600 internal groups and other records required. However, this method
601 doesn't insert any real users like 'root' and you have to use
602 InsertData or another way to do that.
604 Takes no arguments. Returns status and message tuple.
606 It's safe to call this method even if those objects already exist.
610 sub InsertInitialData {
615 # create RT_System user and grant him rights
617 require RT::CurrentUser;
619 my $test_user = RT::User->new( RT::CurrentUser->new() );
620 $test_user->Load('RT_System');
621 if ( $test_user->id ) {
622 push @warns, "Found system user in the DB.";
625 my $user = RT::User->new( RT::CurrentUser->new() );
626 my ( $val, $msg ) = $user->_BootstrapCreate(
628 RealName => 'The RT System itself',
629 Comments => 'Do not delete or modify this user. '
630 . 'It is integral to RT\'s internal database structures',
632 LastUpdatedBy => '1',
634 return ($val, $msg) unless $val;
636 DBIx::SearchBuilder::Record::Cachable->FlushCache;
639 # init RT::SystemUser and RT::System objects
640 RT::InitSystemObjects();
641 unless ( RT->SystemUser->id ) {
642 return (0, "Couldn't load system user");
645 # grant SuperUser right to system user
647 my $test_ace = RT::ACE->new( RT->SystemUser );
648 $test_ace->LoadByCols(
649 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
650 PrincipalType => 'Group',
651 RightName => 'SuperUser',
652 ObjectType => 'RT::System',
655 if ( $test_ace->id ) {
656 push @warns, "System user has global SuperUser right.";
658 my $ace = RT::ACE->new( RT->SystemUser );
659 my ( $val, $msg ) = $ace->_BootstrapCreate(
660 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
661 PrincipalType => 'Group',
662 RightName => 'SuperUser',
663 ObjectType => 'RT::System',
666 return ($val, $msg) unless $val;
668 DBIx::SearchBuilder::Record::Cachable->FlushCache;
672 # $self->loc('Everyone'); # For the string extractor to get a string to localize
673 # $self->loc('Privileged'); # For the string extractor to get a string to localize
674 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
675 foreach my $name (qw(Everyone Privileged Unprivileged)) {
676 my $group = RT::Group->new( RT->SystemUser );
677 $group->LoadSystemInternalGroup( $name );
679 push @warns, "System group '$name' already exists.";
683 $group = RT::Group->new( RT->SystemUser );
684 my ( $val, $msg ) = $group->_Create(
686 Domain => 'SystemInternal',
687 Description => 'Pseudogroup for internal use', # loc
691 return ($val, $msg) unless $val;
696 my $user = RT::User->new( RT->SystemUser );
697 $user->Load('Nobody');
699 push @warns, "Found 'Nobody' user in the DB.";
702 my ( $val, $msg ) = $user->Create(
704 RealName => 'Nobody in particular',
705 Comments => 'Do not delete or modify this user. It is integral '
706 .'to RT\'s internal data structures',
709 return ($val, $msg) unless $val;
712 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
713 push @warns, "User 'Nobody' has global OwnTicket right.";
715 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
716 Right => 'OwnTicket',
717 Object => $RT::System,
719 return ($val, $msg) unless $val;
723 # rerun to get init Nobody as well
724 RT::InitSystemObjects();
727 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
728 my $group = RT::Group->new( RT->SystemUser );
729 $group->LoadSystemRoleGroup( $name );
731 push @warns, "System role '$name' already exists.";
735 $group = RT::Group->new( RT->SystemUser );
736 my ( $val, $msg ) = $group->_Create(
738 Domain => 'RT::System-Role',
739 Description => 'SystemRolegroup for internal use', # loc
743 return ($val, $msg) unless $val;
746 push @warns, "You appear to have a functional RT database."
749 return (1, join "\n", @warns);
754 Load some sort of data into the database, takes path to a file.
760 my $datafile = shift;
761 my $root_password = shift;
763 disconnect_after => 1,
767 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
768 our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
769 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
770 local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
771 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
774 $RT::Logger->debug("Going to load '$datafile' data file");
775 eval { require $datafile }
776 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
779 $RT::Logger->debug("Running initial actions...");
780 foreach ( @Initial ) {
782 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
784 $RT::Logger->debug("Done.");
787 $RT::Logger->debug("Creating groups...");
788 foreach my $item (@Groups) {
789 my $new_entry = RT::Group->new( RT->SystemUser );
790 my $member_of = delete $item->{'MemberOf'};
791 my ( $return, $msg ) = $new_entry->_Create(%$item);
793 $RT::Logger->error( $msg );
796 $RT::Logger->debug($return .".");
799 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
800 foreach( @$member_of ) {
801 my $parent = RT::Group->new(RT->SystemUser);
802 if ( ref $_ eq 'HASH' ) {
803 $parent->LoadByCols( %$_ );
806 $parent->LoadUserDefinedGroup( $_ );
810 "(Error: wrong format of MemberOf field."
811 ." Should be name of user defined group or"
812 ." hash reference with 'column => value' pairs."
813 ." Use array reference to add to multiple groups)"
817 unless ( $parent->Id ) {
818 $RT::Logger->error("(Error: couldn't load group to add member)");
821 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
823 $RT::Logger->error( $msg );
825 $RT::Logger->debug( $return ."." );
830 $RT::Logger->debug("done.");
833 $RT::Logger->debug("Creating users...");
834 foreach my $item (@Users) {
835 if ( $item->{'Name'} eq 'root' && $root_password ) {
836 $item->{'Password'} = $root_password;
838 my $new_entry = RT::User->new( RT->SystemUser );
839 my ( $return, $msg ) = $new_entry->Create(%$item);
841 $RT::Logger->error( $msg );
843 $RT::Logger->debug( $return ."." );
846 $RT::Logger->debug("done.");
849 $RT::Logger->debug("Creating queues...");
850 for my $item (@Queues) {
851 my $new_entry = RT::Queue->new(RT->SystemUser);
852 my ( $return, $msg ) = $new_entry->Create(%$item);
854 $RT::Logger->error( $msg );
856 $RT::Logger->debug( $return ."." );
859 $RT::Logger->debug("done.");
861 if ( @CustomFields ) {
862 $RT::Logger->debug("Creating custom fields...");
863 for my $item ( @CustomFields ) {
864 my $new_entry = RT::CustomField->new( RT->SystemUser );
865 my $values = delete $item->{'Values'};
868 # if ref then it's list of queues, so we do things ourself
869 if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
870 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
871 @queues = @{ delete $item->{'Queue'} };
874 if ( $item->{'BasedOn'} ) {
875 if ( $item->{'LookupType'} ) {
876 my $basedon = RT::CustomField->new($RT::SystemUser);
877 my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
878 LookupType => $item->{'LookupType'} );
880 $item->{'BasedOn'} = $basedon->Id;
882 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
883 delete $item->{'BasedOn'};
886 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
887 delete $item->{'BasedOn'};
892 my ( $return, $msg ) = $new_entry->Create(%$item);
894 $RT::Logger->error( $msg );
898 foreach my $value ( @{$values} ) {
899 my ( $return, $msg ) = $new_entry->AddValue(%$value);
900 $RT::Logger->error( $msg ) unless $return;
904 if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
905 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
906 $ocf->Create( CustomField => $new_entry->Id );
909 for my $q (@queues) {
910 my $q_obj = RT::Queue->new(RT->SystemUser);
912 unless ( $q_obj->Id ) {
913 $RT::Logger->error("Could not find queue ". $q );
916 my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
917 ( $return, $msg ) = $OCF->Create(
918 CustomField => $new_entry->Id,
919 ObjectId => $q_obj->Id,
921 $RT::Logger->error( $msg ) unless $return and $OCF->Id;
925 $RT::Logger->debug("done.");
928 $RT::Logger->debug("Creating ACL...");
929 for my $item (@ACL) {
931 my ($princ, $object);
933 # Global rights or Queue rights?
934 if ( $item->{'CF'} ) {
935 $object = RT::CustomField->new( RT->SystemUser );
936 my @columns = ( Name => $item->{'CF'} );
937 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
938 $object->LoadByName( @columns );
939 } elsif ( $item->{'Queue'} ) {
940 $object = RT::Queue->new(RT->SystemUser);
941 $object->Load( $item->{'Queue'} );
943 $object = $RT::System;
946 $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
948 # Group rights or user rights?
949 if ( $item->{'GroupDomain'} ) {
950 $princ = RT::Group->new(RT->SystemUser);
951 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
952 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
953 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
954 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
955 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
956 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
957 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
960 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
961 Queue => $object->id);
963 $princ->Load( $item->{'GroupId'} );
965 unless ( $princ->Id ) {
966 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
970 $princ = RT::User->new(RT->SystemUser);
971 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
973 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
979 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
980 Right => $item->{'Right'},
984 $RT::Logger->error( $msg );
987 $RT::Logger->debug( $return ."." );
990 $RT::Logger->debug("done.");
993 if ( @ScripActions ) {
994 $RT::Logger->debug("Creating ScripActions...");
996 for my $item (@ScripActions) {
997 my $new_entry = RT::ScripAction->new(RT->SystemUser);
998 my ( $return, $msg ) = $new_entry->Create(%$item);
1000 $RT::Logger->error( $msg );
1003 $RT::Logger->debug( $return ."." );
1007 $RT::Logger->debug("done.");
1010 if ( @ScripConditions ) {
1011 $RT::Logger->debug("Creating ScripConditions...");
1013 for my $item (@ScripConditions) {
1014 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1015 my ( $return, $msg ) = $new_entry->Create(%$item);
1016 unless ( $return ) {
1017 $RT::Logger->error( $msg );
1020 $RT::Logger->debug( $return ."." );
1024 $RT::Logger->debug("done.");
1028 $RT::Logger->debug("Creating templates...");
1030 for my $item (@Templates) {
1031 my $new_entry = RT::Template->new(RT->SystemUser);
1032 my ( $return, $msg ) = $new_entry->Create(%$item);
1033 unless ( $return ) {
1034 $RT::Logger->error( $msg );
1037 $RT::Logger->debug( $return ."." );
1040 $RT::Logger->debug("done.");
1043 $RT::Logger->debug("Creating scrips...");
1045 for my $item (@Scrips) {
1046 my $new_entry = RT::Scrip->new(RT->SystemUser);
1048 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1049 push @queues, 0 unless @queues; # add global queue at least
1051 foreach my $q ( @queues ) {
1052 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
1053 unless ( $return ) {
1054 $RT::Logger->error( $msg );
1057 $RT::Logger->debug( $return ."." );
1061 $RT::Logger->debug("done.");
1063 if ( @Attributes ) {
1064 $RT::Logger->debug("Creating attributes...");
1065 my $sys = RT::System->new(RT->SystemUser);
1067 for my $item (@Attributes) {
1068 my $obj = delete $item->{Object}; # XXX: make this something loadable
1070 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1071 unless ( $return ) {
1072 $RT::Logger->error( $msg );
1075 $RT::Logger->debug( $return ."." );
1078 $RT::Logger->debug("done.");
1081 $RT::Logger->debug("Running final actions...");
1085 $RT::Logger->error( "Failed to run one of final actions: $@" )
1088 $RT::Logger->debug("done.");
1091 # XXX: This disconnect doesn't really belong here; it's a relict from when
1092 # this method was extracted from rt-setup-database. However, too much
1093 # depends on it to change without significant testing. At the very least,
1094 # we can provide a way to skip the side-effect.
1095 if ( $args{disconnect_after} ) {
1096 my $db_type = RT->Config->Get('DatabaseType');
1097 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1100 $RT::Logger->debug("Done setting up database content.");
1102 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1103 # should return (0, $msg) if error happens instead of just warning.
1104 # anyway, we need to return something here to tell if everything is ok
1105 return( 1, 'Done inserting data' );
1108 =head2 ACLEquivGroupId
1110 Given a userid, return that user's acl equivalence group
1114 sub ACLEquivGroupId {
1117 my $cu = RT->SystemUser;
1119 require RT::CurrentUser;
1120 $cu = RT::CurrentUser->new;
1121 $cu->LoadByName('RT_System');
1122 warn "Couldn't load RT_System user" unless $cu->id;
1125 my $equiv_group = RT::Group->new( $cu );
1126 $equiv_group->LoadACLEquivalenceGroup( $id );
1127 return $equiv_group->Id;
1132 Returns the SQL query history associated with this handle. The top level array
1133 represents a lists of request. Each request is a hash with metadata about the
1134 request (such as the URL) and a list of queries. You'll probably not be using this.
1141 return $self->{QueryHistory};
1144 =head2 AddRequestToHistory
1146 Adds a web request to the query history. It must be a hash with keys Path (a
1147 string) and Queries (an array reference of arrays, where elements are time,
1148 sql, bind parameters, and duration).
1152 sub AddRequestToHistory {
1154 my $request = shift;
1156 push @{ $self->{QueryHistory} }, $request;
1161 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1162 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1171 return $self->dbh->quote($value);
1176 Takes a SQL query and an array reference of bind parameters and fills in the
1177 query's C<?> parameters.
1188 # is this regex sufficient?
1189 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1194 # log a mason stack trace instead of a Carp::longmess because it's less painful
1195 # and uses mason component paths properly
1196 sub _LogSQLStatement {
1198 my $statement = shift;
1199 my $duration = shift;
1202 require HTML::Mason::Exceptions;
1203 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1206 __PACKAGE__->FinalizeDatabaseType;
1208 RT::Base->_ImportOverlays();