1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 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'),
117 DisconnectHandleOnDestroy => 1,
121 if ( $db_type eq 'mysql' ) {
122 my $version = $self->DatabaseVersion;
123 ($version) = $version =~ /^(\d+\.\d+)/;
124 $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
128 if ( $db_type eq 'Pg' ) {
129 my $version = $self->DatabaseVersion;
130 ($version) = $version =~ /^(\d+\.\d+)/;
131 $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
136 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
141 Build the DSN for the RT database. Doesn't take any parameters, draws all that
149 # Unless the database port is a positive integer, we really don't want to pass it.
150 my $db_port = RT->Config->Get('DatabasePort');
151 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
152 my $db_host = RT->Config->Get('DatabaseHost');
153 $db_host = undef unless $db_host;
154 my $db_name = RT->Config->Get('DatabaseName');
155 my $db_type = RT->Config->Get('DatabaseType');
156 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
157 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
161 Database => $db_name,
164 RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
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 ."'");
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");
364 elsif ( $db_type eq 'mysql' ) {
365 $status = $dbh->do("CREATE DATABASE $db_name DEFAULT CHARACTER SET utf8");
368 $status = $dbh->do("CREATE DATABASE $db_name");
370 return ($status, $DBI::errstr);
373 =head3 DropDatabase $DBH
375 Drops RT's database. This method can be used as class method.
377 Takes DBI handle as first argument. Many database systems require
378 a special handle to allow you to drop a database, so you may have
379 to use L<SystemDSN> when acquiring the DBI handle.
381 Fetches the type and name of the database from the config.
387 my $dbh = shift or return (0, "No DBI handle provided");
389 my $db_type = RT->Config->Get('DatabaseType');
390 my $db_name = RT->Config->Get('DatabaseName');
392 if ( $db_type eq 'Oracle' ) {
393 my $db_user = RT->Config->Get('DatabaseUser');
394 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
396 return 0, "Couldn't drop user $db_user."
397 ."\nError: ". $dbh->errstr;
399 return (1, "Successfully dropped user '$db_user' with his schema.");
401 elsif ( $db_type eq 'SQLite' ) {
403 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
404 unlink $path or return (0, "Couldn't remove '$path': $!");
407 $dbh->do("DROP DATABASE ". $db_name)
408 or return (0, $DBI::errstr);
420 my $base_path = shift || $RT::EtcPath;
422 my $db_type = RT->Config->Get('DatabaseType');
423 return (1) if $db_type eq 'SQLite';
425 $dbh = $self->dbh if !$dbh && ref $self;
426 return (0, "No DBI handle provided") unless $dbh;
428 return (0, "'$base_path' doesn't exist") unless -e $base_path;
431 if ( -d $base_path ) {
432 $path = File::Spec->catfile( $base_path, "acl.$db_type");
433 $path = $self->GetVersionFile($dbh, $path);
435 $path = File::Spec->catfile( $base_path, "acl")
436 unless $path && -e $path;
437 return (0, "Couldn't find ACLs for $db_type")
444 do $path || return (0, "Couldn't load ACLs: " . $@);
446 foreach my $statement (@acl) {
447 my $sth = $dbh->prepare($statement)
448 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
449 unless ( $sth->execute ) {
450 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
463 my $base_path = (shift || $RT::EtcPath);
465 $dbh = $self->dbh if !$dbh && ref $self;
466 return (0, "No DBI handle provided") unless $dbh;
468 my $db_type = RT->Config->Get('DatabaseType');
471 if ( -d $base_path ) {
472 $file = $base_path . "/schema." . $db_type;
477 $file = $self->GetVersionFile( $dbh, $file );
479 return (0, "Couldn't find schema file(s) '$file*'");
481 unless ( -f $file && -r $file ) {
482 return (0, "File '$file' doesn't exist or couldn't be read");
487 open( my $fh_schema, '<', $file ) or die $!;
490 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
494 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
498 if ( $line =~ /;(\s*)$/ ) {
499 $statement =~ s/;(\s*)$//g;
500 push @schema, $statement;
504 close $fh_schema; close $fh_schema_local;
506 if ( $db_type eq 'Oracle' ) {
507 my $db_user = RT->Config->Get('DatabaseUser');
508 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
510 return $status, "Couldn't set current schema to $db_user."
511 ."\nError: ". $dbh->errstr;
515 local $SIG{__WARN__} = sub {};
517 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
518 foreach my $statement (@schema) {
519 if ( $statement =~ /^\s*;$/ ) {
523 my $sth = $dbh->prepare($statement)
524 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
525 unless ( $sth->execute or $is_local ) {
526 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
529 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
533 =head1 GetVersionFile
535 Takes base name of the file as argument, scans for <base name>-<version> named
536 files and returns file name with closest version to the version of the RT DB.
543 my $base_name = shift;
545 my $db_version = ref $self
546 ? $self->DatabaseVersion
548 my $tmp = RT::Handle->new;
550 $tmp->DatabaseVersion;
554 my @files = File::Glob::bsd_glob("$base_name*");
555 return '' unless @files;
557 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
559 foreach ( reverse sort cmp_version keys %version ) {
560 if ( cmp_version( $db_version, $_ ) >= 0 ) {
566 return defined $version? $version{ $version } : undef;
578 sub cmp_version($$) {
580 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
581 split /([^0-9]+)/, $a;
582 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
583 split /([^0-9]+)/, $b;
585 ? push @b, (0) x (@a-@b)
586 : push @a, (0) x (@b-@a);
587 for ( my $i = 0; $i < @a; $i++ ) {
588 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
600 =head2 InsertInitialData
602 Inserts system objects into RT's DB, like system user or 'nobody',
603 internal groups and other records required. However, this method
604 doesn't insert any real users like 'root' and you have to use
605 InsertData or another way to do that.
607 Takes no arguments. Returns status and message tuple.
609 It's safe to call this method even if those objects already exist.
613 sub InsertInitialData {
618 # create RT_System user and grant him rights
620 require RT::CurrentUser;
622 my $test_user = RT::User->new( RT::CurrentUser->new() );
623 $test_user->Load('RT_System');
624 if ( $test_user->id ) {
625 push @warns, "Found system user in the DB.";
628 my $user = RT::User->new( RT::CurrentUser->new() );
629 my ( $val, $msg ) = $user->_BootstrapCreate(
631 RealName => 'The RT System itself',
632 Comments => 'Do not delete or modify this user. '
633 . 'It is integral to RT\'s internal database structures',
635 LastUpdatedBy => '1',
637 return ($val, $msg) unless $val;
639 DBIx::SearchBuilder::Record::Cachable->FlushCache;
642 # init RT::SystemUser and RT::System objects
643 RT::InitSystemObjects();
644 unless ( RT->SystemUser->id ) {
645 return (0, "Couldn't load system user");
648 # grant SuperUser right to system user
650 my $test_ace = RT::ACE->new( RT->SystemUser );
651 $test_ace->LoadByCols(
652 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
653 PrincipalType => 'Group',
654 RightName => 'SuperUser',
655 ObjectType => 'RT::System',
658 if ( $test_ace->id ) {
659 push @warns, "System user has global SuperUser right.";
661 my $ace = RT::ACE->new( RT->SystemUser );
662 my ( $val, $msg ) = $ace->_BootstrapCreate(
663 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
664 PrincipalType => 'Group',
665 RightName => 'SuperUser',
666 ObjectType => 'RT::System',
669 return ($val, $msg) unless $val;
671 DBIx::SearchBuilder::Record::Cachable->FlushCache;
675 # $self->loc('Everyone'); # For the string extractor to get a string to localize
676 # $self->loc('Privileged'); # For the string extractor to get a string to localize
677 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
678 foreach my $name (qw(Everyone Privileged Unprivileged)) {
679 my $group = RT::Group->new( RT->SystemUser );
680 $group->LoadSystemInternalGroup( $name );
682 push @warns, "System group '$name' already exists.";
686 $group = RT::Group->new( RT->SystemUser );
687 my ( $val, $msg ) = $group->_Create(
689 Domain => 'SystemInternal',
690 Description => 'Pseudogroup for internal use', # loc
694 return ($val, $msg) unless $val;
699 my $user = RT::User->new( RT->SystemUser );
700 $user->Load('Nobody');
702 push @warns, "Found 'Nobody' user in the DB.";
705 my ( $val, $msg ) = $user->Create(
707 RealName => 'Nobody in particular',
708 Comments => 'Do not delete or modify this user. It is integral '
709 .'to RT\'s internal data structures',
712 return ($val, $msg) unless $val;
715 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
716 push @warns, "User 'Nobody' has global OwnTicket right.";
718 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
719 Right => 'OwnTicket',
720 Object => $RT::System,
722 return ($val, $msg) unless $val;
726 # rerun to get init Nobody as well
727 RT::InitSystemObjects();
730 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
731 my $group = RT::Group->new( RT->SystemUser );
732 $group->LoadSystemRoleGroup( $name );
734 push @warns, "System role '$name' already exists.";
738 $group = RT::Group->new( RT->SystemUser );
739 my ( $val, $msg ) = $group->_Create(
741 Domain => 'RT::System-Role',
742 Description => 'SystemRolegroup for internal use', # loc
746 return ($val, $msg) unless $val;
749 push @warns, "You appear to have a functional RT database."
752 return (1, join "\n", @warns);
757 Load some sort of data into the database, takes path to a file.
763 my $datafile = shift;
764 my $root_password = shift;
766 disconnect_after => 1,
770 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
771 our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
772 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
773 local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
774 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
777 $RT::Logger->debug("Going to load '$datafile' data file");
778 eval { require $datafile }
779 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
782 $RT::Logger->debug("Running initial actions...");
783 foreach ( @Initial ) {
785 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
787 $RT::Logger->debug("Done.");
790 $RT::Logger->debug("Creating groups...");
791 foreach my $item (@Groups) {
792 my $new_entry = RT::Group->new( RT->SystemUser );
793 $item->{Domain} ||= 'UserDefined';
794 my $member_of = delete $item->{'MemberOf'};
795 my $members = delete $item->{'Members'};
796 my ( $return, $msg ) = $new_entry->_Create(%$item);
798 $RT::Logger->error( $msg );
801 $RT::Logger->debug($return .".");
804 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
805 foreach( @$member_of ) {
806 my $parent = RT::Group->new(RT->SystemUser);
807 if ( ref $_ eq 'HASH' ) {
808 $parent->LoadByCols( %$_ );
811 $parent->LoadUserDefinedGroup( $_ );
815 "(Error: wrong format of MemberOf field."
816 ." Should be name of user defined group or"
817 ." hash reference with 'column => value' pairs."
818 ." Use array reference to add to multiple groups)"
822 unless ( $parent->Id ) {
823 $RT::Logger->error("(Error: couldn't load group to add member)");
826 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
828 $RT::Logger->error( $msg );
830 $RT::Logger->debug( $return ."." );
834 push @Members, map { +{Group => $new_entry->id,
835 Class => "RT::User", Name => $_} }
836 @{ $members->{Users} || [] };
837 push @Members, map { +{Group => $new_entry->id,
838 Class => "RT::Group", Name => $_} }
839 @{ $members->{Groups} || [] };
841 $RT::Logger->debug("done.");
844 $RT::Logger->debug("Creating users...");
845 foreach my $item (@Users) {
846 if ( $item->{'Name'} eq 'root' && $root_password ) {
847 $item->{'Password'} = $root_password;
849 my $new_entry = RT::User->new( RT->SystemUser );
850 my ( $return, $msg ) = $new_entry->Create(%$item);
852 $RT::Logger->error( $msg );
854 $RT::Logger->debug( $return ."." );
857 $RT::Logger->debug("done.");
860 $RT::Logger->debug("Adding users and groups to groups...");
861 for my $item (@Members) {
862 my $group = RT::Group->new(RT->SystemUser);
863 $group->LoadUserDefinedGroup( delete $item->{Group} );
864 unless ($group->Id) {
865 RT->Logger->error("Unable to find group '$group' to add members to");
869 my $class = delete $item->{Class} || 'RT::User';
870 my $member = $class->new( RT->SystemUser );
871 $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
872 $member->LoadByCols( %$item );
873 unless ($member->Id) {
874 RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
878 my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
880 $RT::Logger->error( $msg );
882 $RT::Logger->debug( $return ."." );
887 $RT::Logger->debug("Creating queues...");
888 for my $item (@Queues) {
889 my $new_entry = RT::Queue->new(RT->SystemUser);
890 my ( $return, $msg ) = $new_entry->Create(%$item);
892 $RT::Logger->error( $msg );
894 $RT::Logger->debug( $return ."." );
897 $RT::Logger->debug("done.");
899 if ( @CustomFields ) {
900 $RT::Logger->debug("Creating custom fields...");
901 for my $item ( @CustomFields ) {
902 my $new_entry = RT::CustomField->new( RT->SystemUser );
903 my $values = delete $item->{'Values'};
906 # if ref then it's list of queues, so we do things ourself
907 if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
908 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
909 @queues = @{ delete $item->{'Queue'} };
912 if ( $item->{'BasedOn'} ) {
913 if ( $item->{'BasedOn'} =~ /^\d+$/) {
914 # Already have an ID -- should be fine
915 } elsif ( $item->{'LookupType'} ) {
916 my $basedon = RT::CustomField->new($RT::SystemUser);
917 my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
918 LookupType => $item->{'LookupType'} );
920 $item->{'BasedOn'} = $basedon->Id;
922 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
923 delete $item->{'BasedOn'};
926 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
927 delete $item->{'BasedOn'};
932 my ( $return, $msg ) = $new_entry->Create(%$item);
934 $RT::Logger->error( $msg );
938 foreach my $value ( @{$values} ) {
939 my ( $return, $msg ) = $new_entry->AddValue(%$value);
940 $RT::Logger->error( $msg ) unless $return;
944 if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
945 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
946 $ocf->Create( CustomField => $new_entry->Id );
949 for my $q (@queues) {
950 my $q_obj = RT::Queue->new(RT->SystemUser);
952 unless ( $q_obj->Id ) {
953 $RT::Logger->error("Could not find queue ". $q );
956 my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
957 ( $return, $msg ) = $OCF->Create(
958 CustomField => $new_entry->Id,
959 ObjectId => $q_obj->Id,
961 $RT::Logger->error( $msg ) unless $return and $OCF->Id;
965 $RT::Logger->debug("done.");
968 $RT::Logger->debug("Creating ACL...");
969 for my $item (@ACL) {
971 my ($princ, $object);
973 # Global rights or Queue rights?
974 if ( $item->{'CF'} ) {
975 $object = RT::CustomField->new( RT->SystemUser );
976 my @columns = ( Name => $item->{'CF'} );
977 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
978 $object->LoadByName( @columns );
979 } elsif ( $item->{'Queue'} ) {
980 $object = RT::Queue->new(RT->SystemUser);
981 $object->Load( $item->{'Queue'} );
983 $object = $RT::System;
986 $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
988 # Group rights or user rights?
989 if ( $item->{'GroupDomain'} ) {
990 $princ = RT::Group->new(RT->SystemUser);
991 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
992 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
993 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
994 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
995 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
996 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
997 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
1000 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
1001 Queue => $object->id);
1003 $princ->Load( $item->{'GroupId'} );
1005 unless ( $princ->Id ) {
1006 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
1010 $princ = RT::User->new(RT->SystemUser);
1011 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
1013 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
1019 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
1020 Right => $item->{'Right'},
1023 unless ( $return ) {
1024 $RT::Logger->error( $msg );
1027 $RT::Logger->debug( $return ."." );
1030 $RT::Logger->debug("done.");
1033 if ( @ScripActions ) {
1034 $RT::Logger->debug("Creating ScripActions...");
1036 for my $item (@ScripActions) {
1037 my $new_entry = RT::ScripAction->new(RT->SystemUser);
1038 my ( $return, $msg ) = $new_entry->Create(%$item);
1039 unless ( $return ) {
1040 $RT::Logger->error( $msg );
1043 $RT::Logger->debug( $return ."." );
1047 $RT::Logger->debug("done.");
1050 if ( @ScripConditions ) {
1051 $RT::Logger->debug("Creating ScripConditions...");
1053 for my $item (@ScripConditions) {
1054 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1055 my ( $return, $msg ) = $new_entry->Create(%$item);
1056 unless ( $return ) {
1057 $RT::Logger->error( $msg );
1060 $RT::Logger->debug( $return ."." );
1064 $RT::Logger->debug("done.");
1068 $RT::Logger->debug("Creating templates...");
1070 for my $item (@Templates) {
1071 my $new_entry = RT::Template->new(RT->SystemUser);
1072 my ( $return, $msg ) = $new_entry->Create(%$item);
1073 unless ( $return ) {
1074 $RT::Logger->error( $msg );
1077 $RT::Logger->debug( $return ."." );
1080 $RT::Logger->debug("done.");
1083 $RT::Logger->debug("Creating scrips...");
1085 for my $item (@Scrips) {
1086 my $new_entry = RT::Scrip->new(RT->SystemUser);
1088 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1089 push @queues, 0 unless @queues; # add global queue at least
1091 foreach my $q ( @queues ) {
1092 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
1093 unless ( $return ) {
1094 $RT::Logger->error( $msg );
1097 $RT::Logger->debug( $return ."." );
1101 $RT::Logger->debug("done.");
1103 if ( @Attributes ) {
1104 $RT::Logger->debug("Creating attributes...");
1105 my $sys = RT::System->new(RT->SystemUser);
1107 for my $item (@Attributes) {
1108 my $obj = delete $item->{Object}; # XXX: make this something loadable
1110 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1111 unless ( $return ) {
1112 $RT::Logger->error( $msg );
1115 $RT::Logger->debug( $return ."." );
1118 $RT::Logger->debug("done.");
1121 $RT::Logger->debug("Running final actions...");
1125 $RT::Logger->error( "Failed to run one of final actions: $@" )
1128 $RT::Logger->debug("done.");
1131 # XXX: This disconnect doesn't really belong here; it's a relict from when
1132 # this method was extracted from rt-setup-database. However, too much
1133 # depends on it to change without significant testing. At the very least,
1134 # we can provide a way to skip the side-effect.
1135 if ( $args{disconnect_after} ) {
1136 my $db_type = RT->Config->Get('DatabaseType');
1137 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1140 $RT::Logger->debug("Done setting up database content.");
1142 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1143 # should return (0, $msg) if error happens instead of just warning.
1144 # anyway, we need to return something here to tell if everything is ok
1145 return( 1, 'Done inserting data' );
1148 =head2 ACLEquivGroupId
1150 Given a userid, return that user's acl equivalence group
1154 sub ACLEquivGroupId {
1157 my $cu = RT->SystemUser;
1159 require RT::CurrentUser;
1160 $cu = RT::CurrentUser->new;
1161 $cu->LoadByName('RT_System');
1162 warn "Couldn't load RT_System user" unless $cu->id;
1165 my $equiv_group = RT::Group->new( $cu );
1166 $equiv_group->LoadACLEquivalenceGroup( $id );
1167 return $equiv_group->Id;
1172 Returns the SQL query history associated with this handle. The top level array
1173 represents a lists of request. Each request is a hash with metadata about the
1174 request (such as the URL) and a list of queries. You'll probably not be using this.
1181 return $self->{QueryHistory};
1184 =head2 AddRequestToHistory
1186 Adds a web request to the query history. It must be a hash with keys Path (a
1187 string) and Queries (an array reference of arrays, where elements are time,
1188 sql, bind parameters, and duration).
1192 sub AddRequestToHistory {
1194 my $request = shift;
1196 push @{ $self->{QueryHistory} }, $request;
1201 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1202 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1211 return $self->dbh->quote($value);
1216 Takes a SQL query and an array reference of bind parameters and fills in the
1217 query's C<?> parameters.
1228 # is this regex sufficient?
1229 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1234 # log a mason stack trace instead of a Carp::longmess because it's less painful
1235 # and uses mason component paths properly
1236 sub _LogSQLStatement {
1238 my $statement = shift;
1239 my $duration = shift;
1242 require HTML::Mason::Exceptions;
1243 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1249 my $dbh = shift || $self->dbh;
1254 $dbh->{Driver}->{Name} eq 'Pg'
1255 && $dbh->{'pg_server_version'} >= 90200
1256 && !eval { DBD::Pg->VERSION('2.19.3'); 1 }
1258 die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@";
1264 my $sth = $dbh->table_info( '', undef, undef, "'TABLE'");
1265 while ( my $table = $sth->fetchrow_hashref ) {
1266 push @res, $table->{TABLE_NAME} || $table->{table_name};
1272 __PACKAGE__->FinalizeDatabaseType;
1274 RT::Base->_ImportOverlays();