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->{pg_server_prepare} = 0 if $version > 9.1; #and we're using a deb-7 version DBD::Pg?
132 $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
137 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
142 Build the DSN for the RT database. Doesn't take any parameters, draws all that
150 # Unless the database port is a positive integer, we really don't want to pass it.
151 my $db_port = RT->Config->Get('DatabasePort');
152 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
153 my $db_host = RT->Config->Get('DatabaseHost');
154 $db_host = undef unless $db_host;
155 my $db_name = RT->Config->Get('DatabaseName');
156 my $db_type = RT->Config->Get('DatabaseType');
157 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
158 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
162 Database => $db_name,
165 RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
167 if ( $db_type eq 'Oracle' && $db_host ) {
168 $args{'SID'} = delete $args{'Database'};
170 $self->SUPER::BuildDSN( %args );
175 Returns the DSN for this handle. In order to get correct value you must
176 build DSN first, see L</BuildDSN>.
178 This is method can be called as class method, in this case creates
179 temporary handle object, L</BuildDSN builds DSN> and returns it.
185 return $self->SUPER::DSN if ref $self;
187 my $handle = $self->new;
194 Returns a DSN suitable for database creates and drops
195 and user creates and drops.
197 Gets RT's DSN first (see L<DSN>) and then change it according
198 to requirements of a database system RT's using.
205 my $db_name = RT->Config->Get('DatabaseName');
206 my $db_type = RT->Config->Get('DatabaseType');
208 my $dsn = $self->DSN;
209 if ( $db_type eq 'mysql' ) {
210 # with mysql, you want to connect sans database to funge things
211 $dsn =~ s/dbname=\Q$db_name//;
213 elsif ( $db_type eq 'Pg' ) {
214 # with postgres, you want to connect to template1 database
215 $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
220 =head2 Database compatibility and integrity checks
228 $self = new $self unless ref $self;
230 unless ($RT::Handle and $RT::Handle->dbh) {
232 unless ( eval { RT::ConnectToDatabase(); 1 } ) {
233 return (0, 'no connection', "$@");
237 require RT::CurrentUser;
238 my $test_user = RT::CurrentUser->new;
239 $test_user->Load('RT_System');
240 unless ( $test_user->id ) {
241 return (0, 'no system user', "Couldn't find RT_System user in the DB '". $self->DSN ."'");
244 $test_user = RT::CurrentUser->new;
245 $test_user->Load('Nobody');
246 unless ( $test_user->id ) {
247 return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
253 sub CheckCompatibility {
256 my $state = shift || 'post';
258 my $db_type = RT->Config->Get('DatabaseType');
259 if ( $db_type eq "mysql" ) {
260 # Check which version we're running
261 my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
262 return (0, "couldn't get version of the mysql server")
265 ($version) = $version =~ /^(\d+\.\d+)/;
266 return (0, "RT is unsupported on MySQL versions before 4.1. Your version is $version.")
269 # MySQL must have InnoDB support
270 local $dbh->{FetchHashKeyName} = 'NAME_lc';
271 my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no");
272 if ( $innodb eq "no" ) {
273 return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
274 "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n".
275 "and check that there are no 'skip-innodb' lines in your my.cnf.");
276 } elsif ( $innodb eq "disabled" ) {
277 return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
278 "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n");
281 if ( $state eq 'post' ) {
282 my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Tickets")->[1];
283 unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
284 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
287 $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
288 unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
289 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
290 ."Follow instructions in the UPGRADING.mysql file.");
294 my $max_packet = ($dbh->selectrow_array("show variables like 'max_allowed_packet'"))[1];
295 if ($state =~ /^(create|post)$/ and $max_packet <= (1024 * 1024)) {
296 my $max_packet = sprintf("%.1fM", $max_packet/1024/1024);
297 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";
306 my $dbh = $RT::Handle->dbh;
307 local $dbh->{'RaiseError'} = 0;
308 local $dbh->{'PrintError'} = 0;
309 my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
310 $has ||= ($dbh->selectrow_array(
311 "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
314 return 0 unless lc($has||'') eq "yes";
318 =head2 Database maintanance
320 =head3 CreateDatabase $DBH
322 Creates a new database. This method can be used as class method.
324 Takes DBI handle. Many database systems require special handle to
325 allow you to create a new database, so you have to use L<SystemDSN>
326 method during connection.
328 Fetches type and name of the DB from the config.
334 my $dbh = shift or return (0, "No DBI handle provided");
335 my $db_type = RT->Config->Get('DatabaseType');
336 my $db_name = RT->Config->Get('DatabaseName');
339 if ( $db_type eq 'SQLite' ) {
340 return (1, 'Skipped as SQLite doesn\'t need any action');
342 elsif ( $db_type eq 'Oracle' ) {
343 my $db_user = RT->Config->Get('DatabaseUser');
344 my $db_pass = RT->Config->Get('DatabasePassword');
346 "CREATE USER $db_user IDENTIFIED BY $db_pass"
347 ." default tablespace USERS"
348 ." temporary tablespace TEMP"
349 ." quota unlimited on USERS"
352 return $status, "Couldn't create user $db_user identified by $db_pass."
353 ."\nError: ". $dbh->errstr;
355 $status = $dbh->do( "GRANT connect, resource TO $db_user" );
357 return $status, "Couldn't grant connect and resource to $db_user."
358 ."\nError: ". $dbh->errstr;
360 return (1, "Created user $db_user. All RT's objects should be in his schema.");
362 elsif ( $db_type eq 'Pg' ) {
363 $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
365 elsif ( $db_type eq 'mysql' ) {
366 $status = $dbh->do("CREATE DATABASE $db_name DEFAULT CHARACTER SET utf8");
369 $status = $dbh->do("CREATE DATABASE $db_name");
371 return ($status, $DBI::errstr);
374 =head3 DropDatabase $DBH
376 Drops RT's database. This method can be used as class method.
378 Takes DBI handle as first argument. Many database systems require
379 a special handle to allow you to drop a database, so you may have
380 to use L<SystemDSN> when acquiring the DBI handle.
382 Fetches the type and name of the database from the config.
388 my $dbh = shift or return (0, "No DBI handle provided");
390 my $db_type = RT->Config->Get('DatabaseType');
391 my $db_name = RT->Config->Get('DatabaseName');
393 if ( $db_type eq 'Oracle' ) {
394 my $db_user = RT->Config->Get('DatabaseUser');
395 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
397 return 0, "Couldn't drop user $db_user."
398 ."\nError: ". $dbh->errstr;
400 return (1, "Successfully dropped user '$db_user' with his schema.");
402 elsif ( $db_type eq 'SQLite' ) {
404 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
405 unlink $path or return (0, "Couldn't remove '$path': $!");
408 $dbh->do("DROP DATABASE ". $db_name)
409 or return (0, $DBI::errstr);
421 my $base_path = shift || $RT::EtcPath;
423 my $db_type = RT->Config->Get('DatabaseType');
424 return (1) if $db_type eq 'SQLite';
426 $dbh = $self->dbh if !$dbh && ref $self;
427 return (0, "No DBI handle provided") unless $dbh;
429 return (0, "'$base_path' doesn't exist") unless -e $base_path;
432 if ( -d $base_path ) {
433 $path = File::Spec->catfile( $base_path, "acl.$db_type");
434 $path = $self->GetVersionFile($dbh, $path);
436 $path = File::Spec->catfile( $base_path, "acl")
437 unless $path && -e $path;
438 return (0, "Couldn't find ACLs for $db_type")
445 do $path || return (0, "Couldn't load ACLs: " . $@);
447 foreach my $statement (@acl) {
448 my $sth = $dbh->prepare($statement)
449 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
450 unless ( $sth->execute ) {
451 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
464 my $base_path = (shift || $RT::EtcPath);
466 $dbh = $self->dbh if !$dbh && ref $self;
467 return (0, "No DBI handle provided") unless $dbh;
469 my $db_type = RT->Config->Get('DatabaseType');
472 if ( -d $base_path ) {
473 $file = $base_path . "/schema." . $db_type;
478 $file = $self->GetVersionFile( $dbh, $file );
480 return (0, "Couldn't find schema file(s) '$file*'");
482 unless ( -f $file && -r $file ) {
483 return (0, "File '$file' doesn't exist or couldn't be read");
488 open( my $fh_schema, '<', $file ) or die $!;
491 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
495 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
499 if ( $line =~ /;(\s*)$/ ) {
500 $statement =~ s/;(\s*)$//g;
501 push @schema, $statement;
505 close $fh_schema; close $fh_schema_local;
507 if ( $db_type eq 'Oracle' ) {
508 my $db_user = RT->Config->Get('DatabaseUser');
509 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
511 return $status, "Couldn't set current schema to $db_user."
512 ."\nError: ". $dbh->errstr;
516 local $SIG{__WARN__} = sub {};
518 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
519 foreach my $statement (@schema) {
520 if ( $statement =~ /^\s*;$/ ) {
524 my $sth = $dbh->prepare($statement)
525 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
526 unless ( $sth->execute or $is_local ) {
527 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
530 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
534 =head1 GetVersionFile
536 Takes base name of the file as argument, scans for <base name>-<version> named
537 files and returns file name with closest version to the version of the RT DB.
544 my $base_name = shift;
546 my $db_version = ref $self
547 ? $self->DatabaseVersion
549 my $tmp = RT::Handle->new;
551 $tmp->DatabaseVersion;
555 my @files = File::Glob::bsd_glob("$base_name*");
556 return '' unless @files;
558 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
560 foreach ( reverse sort cmp_version keys %version ) {
561 if ( cmp_version( $db_version, $_ ) >= 0 ) {
567 return defined $version? $version{ $version } : undef;
579 sub cmp_version($$) {
581 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
582 split /([^0-9]+)/, $a;
583 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
584 split /([^0-9]+)/, $b;
586 ? push @b, (0) x (@a-@b)
587 : push @a, (0) x (@b-@a);
588 for ( my $i = 0; $i < @a; $i++ ) {
589 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
601 =head2 InsertInitialData
603 Inserts system objects into RT's DB, like system user or 'nobody',
604 internal groups and other records required. However, this method
605 doesn't insert any real users like 'root' and you have to use
606 InsertData or another way to do that.
608 Takes no arguments. Returns status and message tuple.
610 It's safe to call this method even if those objects already exist.
614 sub InsertInitialData {
619 # create RT_System user and grant him rights
621 require RT::CurrentUser;
623 my $test_user = RT::User->new( RT::CurrentUser->new() );
624 $test_user->Load('RT_System');
625 if ( $test_user->id ) {
626 push @warns, "Found system user in the DB.";
629 my $user = RT::User->new( RT::CurrentUser->new() );
630 my ( $val, $msg ) = $user->_BootstrapCreate(
632 RealName => 'The RT System itself',
633 Comments => 'Do not delete or modify this user. '
634 . 'It is integral to RT\'s internal database structures',
636 LastUpdatedBy => '1',
638 return ($val, $msg) unless $val;
640 DBIx::SearchBuilder::Record::Cachable->FlushCache;
643 # init RT::SystemUser and RT::System objects
644 RT::InitSystemObjects();
645 unless ( RT->SystemUser->id ) {
646 return (0, "Couldn't load system user");
649 # grant SuperUser right to system user
651 my $test_ace = RT::ACE->new( RT->SystemUser );
652 $test_ace->LoadByCols(
653 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
654 PrincipalType => 'Group',
655 RightName => 'SuperUser',
656 ObjectType => 'RT::System',
659 if ( $test_ace->id ) {
660 push @warns, "System user has global SuperUser right.";
662 my $ace = RT::ACE->new( RT->SystemUser );
663 my ( $val, $msg ) = $ace->_BootstrapCreate(
664 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
665 PrincipalType => 'Group',
666 RightName => 'SuperUser',
667 ObjectType => 'RT::System',
670 return ($val, $msg) unless $val;
672 DBIx::SearchBuilder::Record::Cachable->FlushCache;
676 # $self->loc('Everyone'); # For the string extractor to get a string to localize
677 # $self->loc('Privileged'); # For the string extractor to get a string to localize
678 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
679 foreach my $name (qw(Everyone Privileged Unprivileged)) {
680 my $group = RT::Group->new( RT->SystemUser );
681 $group->LoadSystemInternalGroup( $name );
683 push @warns, "System group '$name' already exists.";
687 $group = RT::Group->new( RT->SystemUser );
688 my ( $val, $msg ) = $group->_Create(
690 Domain => 'SystemInternal',
691 Description => 'Pseudogroup for internal use', # loc
695 return ($val, $msg) unless $val;
700 my $user = RT::User->new( RT->SystemUser );
701 $user->Load('Nobody');
703 push @warns, "Found 'Nobody' user in the DB.";
706 my ( $val, $msg ) = $user->Create(
708 RealName => 'Nobody in particular',
709 Comments => 'Do not delete or modify this user. It is integral '
710 .'to RT\'s internal data structures',
713 return ($val, $msg) unless $val;
716 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
717 push @warns, "User 'Nobody' has global OwnTicket right.";
719 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
720 Right => 'OwnTicket',
721 Object => $RT::System,
723 return ($val, $msg) unless $val;
727 # rerun to get init Nobody as well
728 RT::InitSystemObjects();
731 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
732 my $group = RT::Group->new( RT->SystemUser );
733 $group->LoadSystemRoleGroup( $name );
735 push @warns, "System role '$name' already exists.";
739 $group = RT::Group->new( RT->SystemUser );
740 my ( $val, $msg ) = $group->_Create(
742 Domain => 'RT::System-Role',
743 Description => 'SystemRolegroup for internal use', # loc
747 return ($val, $msg) unless $val;
750 push @warns, "You appear to have a functional RT database."
753 return (1, join "\n", @warns);
758 Load some sort of data into the database, takes path to a file.
764 my $datafile = shift;
765 my $root_password = shift;
767 disconnect_after => 1,
771 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
772 our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
773 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
774 local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
775 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
778 $RT::Logger->debug("Going to load '$datafile' data file");
779 eval { require $datafile }
780 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
783 $RT::Logger->debug("Running initial actions...");
784 foreach ( @Initial ) {
786 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
788 $RT::Logger->debug("Done.");
791 $RT::Logger->debug("Creating groups...");
792 foreach my $item (@Groups) {
793 my $new_entry = RT::Group->new( RT->SystemUser );
794 $item->{Domain} ||= 'UserDefined';
795 my $member_of = delete $item->{'MemberOf'};
796 my $members = delete $item->{'Members'};
797 my ( $return, $msg ) = $new_entry->_Create(%$item);
799 $RT::Logger->error( $msg );
802 $RT::Logger->debug($return .".");
805 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
806 foreach( @$member_of ) {
807 my $parent = RT::Group->new(RT->SystemUser);
808 if ( ref $_ eq 'HASH' ) {
809 $parent->LoadByCols( %$_ );
812 $parent->LoadUserDefinedGroup( $_ );
816 "(Error: wrong format of MemberOf field."
817 ." Should be name of user defined group or"
818 ." hash reference with 'column => value' pairs."
819 ." Use array reference to add to multiple groups)"
823 unless ( $parent->Id ) {
824 $RT::Logger->error("(Error: couldn't load group to add member)");
827 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
829 $RT::Logger->error( $msg );
831 $RT::Logger->debug( $return ."." );
835 push @Members, map { +{Group => $new_entry->id,
836 Class => "RT::User", Name => $_} }
837 @{ $members->{Users} || [] };
838 push @Members, map { +{Group => $new_entry->id,
839 Class => "RT::Group", Name => $_} }
840 @{ $members->{Groups} || [] };
842 $RT::Logger->debug("done.");
845 $RT::Logger->debug("Creating users...");
846 foreach my $item (@Users) {
847 if ( $item->{'Name'} eq 'root' && $root_password ) {
848 $item->{'Password'} = $root_password;
850 my $new_entry = RT::User->new( RT->SystemUser );
851 my ( $return, $msg ) = $new_entry->Create(%$item);
853 $RT::Logger->error( $msg );
855 $RT::Logger->debug( $return ."." );
858 $RT::Logger->debug("done.");
861 $RT::Logger->debug("Adding users and groups to groups...");
862 for my $item (@Members) {
863 my $group = RT::Group->new(RT->SystemUser);
864 $group->LoadUserDefinedGroup( delete $item->{Group} );
865 unless ($group->Id) {
866 RT->Logger->error("Unable to find group '$group' to add members to");
870 my $class = delete $item->{Class} || 'RT::User';
871 my $member = $class->new( RT->SystemUser );
872 $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
873 $member->LoadByCols( %$item );
874 unless ($member->Id) {
875 RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
879 my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
881 $RT::Logger->error( $msg );
883 $RT::Logger->debug( $return ."." );
888 $RT::Logger->debug("Creating queues...");
889 for my $item (@Queues) {
890 my $new_entry = RT::Queue->new(RT->SystemUser);
891 my ( $return, $msg ) = $new_entry->Create(%$item);
893 $RT::Logger->error( $msg );
895 $RT::Logger->debug( $return ."." );
898 $RT::Logger->debug("done.");
900 if ( @CustomFields ) {
901 $RT::Logger->debug("Creating custom fields...");
902 for my $item ( @CustomFields ) {
903 my $new_entry = RT::CustomField->new( RT->SystemUser );
904 my $values = delete $item->{'Values'};
907 # if ref then it's list of queues, so we do things ourself
908 if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
909 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
910 @queues = @{ delete $item->{'Queue'} };
913 if ( $item->{'BasedOn'} ) {
914 if ( $item->{'BasedOn'} =~ /^\d+$/) {
915 # Already have an ID -- should be fine
916 } elsif ( $item->{'LookupType'} ) {
917 my $basedon = RT::CustomField->new($RT::SystemUser);
918 my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
919 LookupType => $item->{'LookupType'} );
921 $item->{'BasedOn'} = $basedon->Id;
923 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
924 delete $item->{'BasedOn'};
927 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
928 delete $item->{'BasedOn'};
933 my ( $return, $msg ) = $new_entry->Create(%$item);
935 $RT::Logger->error( $msg );
939 foreach my $value ( @{$values} ) {
940 my ( $return, $msg ) = $new_entry->AddValue(%$value);
941 $RT::Logger->error( $msg ) unless $return;
945 if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
946 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
947 $ocf->Create( CustomField => $new_entry->Id );
950 for my $q (@queues) {
951 my $q_obj = RT::Queue->new(RT->SystemUser);
953 unless ( $q_obj->Id ) {
954 $RT::Logger->error("Could not find queue ". $q );
957 my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
958 ( $return, $msg ) = $OCF->Create(
959 CustomField => $new_entry->Id,
960 ObjectId => $q_obj->Id,
962 $RT::Logger->error( $msg ) unless $return and $OCF->Id;
966 $RT::Logger->debug("done.");
969 $RT::Logger->debug("Creating ACL...");
970 for my $item (@ACL) {
972 my ($princ, $object);
974 # Global rights or Queue rights?
975 if ( $item->{'CF'} ) {
976 $object = RT::CustomField->new( RT->SystemUser );
977 my @columns = ( Name => $item->{'CF'} );
978 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
979 $object->LoadByName( @columns );
980 } elsif ( $item->{'Queue'} ) {
981 $object = RT::Queue->new(RT->SystemUser);
982 $object->Load( $item->{'Queue'} );
984 $object = $RT::System;
987 $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
989 # Group rights or user rights?
990 if ( $item->{'GroupDomain'} ) {
991 $princ = RT::Group->new(RT->SystemUser);
992 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
993 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
994 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
995 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
996 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
997 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
998 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
1001 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
1002 Queue => $object->id);
1004 $princ->Load( $item->{'GroupId'} );
1006 unless ( $princ->Id ) {
1007 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
1011 $princ = RT::User->new(RT->SystemUser);
1012 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
1014 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
1020 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
1021 Right => $item->{'Right'},
1024 unless ( $return ) {
1025 $RT::Logger->error( $msg );
1028 $RT::Logger->debug( $return ."." );
1031 $RT::Logger->debug("done.");
1034 if ( @ScripActions ) {
1035 $RT::Logger->debug("Creating ScripActions...");
1037 for my $item (@ScripActions) {
1038 my $new_entry = RT::ScripAction->new(RT->SystemUser);
1039 my ( $return, $msg ) = $new_entry->Create(%$item);
1040 unless ( $return ) {
1041 $RT::Logger->error( $msg );
1044 $RT::Logger->debug( $return ."." );
1048 $RT::Logger->debug("done.");
1051 if ( @ScripConditions ) {
1052 $RT::Logger->debug("Creating ScripConditions...");
1054 for my $item (@ScripConditions) {
1055 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1056 my ( $return, $msg ) = $new_entry->Create(%$item);
1057 unless ( $return ) {
1058 $RT::Logger->error( $msg );
1061 $RT::Logger->debug( $return ."." );
1065 $RT::Logger->debug("done.");
1069 $RT::Logger->debug("Creating templates...");
1071 for my $item (@Templates) {
1072 my $new_entry = RT::Template->new(RT->SystemUser);
1073 my ( $return, $msg ) = $new_entry->Create(%$item);
1074 unless ( $return ) {
1075 $RT::Logger->error( $msg );
1078 $RT::Logger->debug( $return ."." );
1081 $RT::Logger->debug("done.");
1084 $RT::Logger->debug("Creating scrips...");
1086 for my $item (@Scrips) {
1087 my $new_entry = RT::Scrip->new(RT->SystemUser);
1089 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1090 push @queues, 0 unless @queues; # add global queue at least
1092 foreach my $q ( @queues ) {
1093 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
1094 unless ( $return ) {
1095 $RT::Logger->error( $msg );
1098 $RT::Logger->debug( $return ."." );
1102 $RT::Logger->debug("done.");
1104 if ( @Attributes ) {
1105 $RT::Logger->debug("Creating attributes...");
1106 my $sys = RT::System->new(RT->SystemUser);
1108 for my $item (@Attributes) {
1109 my $obj = delete $item->{Object}; # XXX: make this something loadable
1111 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1112 unless ( $return ) {
1113 $RT::Logger->error( $msg );
1116 $RT::Logger->debug( $return ."." );
1119 $RT::Logger->debug("done.");
1122 $RT::Logger->debug("Running final actions...");
1126 $RT::Logger->error( "Failed to run one of final actions: $@" )
1129 $RT::Logger->debug("done.");
1132 # XXX: This disconnect doesn't really belong here; it's a relict from when
1133 # this method was extracted from rt-setup-database. However, too much
1134 # depends on it to change without significant testing. At the very least,
1135 # we can provide a way to skip the side-effect.
1136 if ( $args{disconnect_after} ) {
1137 my $db_type = RT->Config->Get('DatabaseType');
1138 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1141 $RT::Logger->debug("Done setting up database content.");
1143 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1144 # should return (0, $msg) if error happens instead of just warning.
1145 # anyway, we need to return something here to tell if everything is ok
1146 return( 1, 'Done inserting data' );
1149 =head2 ACLEquivGroupId
1151 Given a userid, return that user's acl equivalence group
1155 sub ACLEquivGroupId {
1158 my $cu = RT->SystemUser;
1160 require RT::CurrentUser;
1161 $cu = RT::CurrentUser->new;
1162 $cu->LoadByName('RT_System');
1163 warn "Couldn't load RT_System user" unless $cu->id;
1166 my $equiv_group = RT::Group->new( $cu );
1167 $equiv_group->LoadACLEquivalenceGroup( $id );
1168 return $equiv_group->Id;
1173 Returns the SQL query history associated with this handle. The top level array
1174 represents a lists of request. Each request is a hash with metadata about the
1175 request (such as the URL) and a list of queries. You'll probably not be using this.
1182 return $self->{QueryHistory};
1185 =head2 AddRequestToHistory
1187 Adds a web request to the query history. It must be a hash with keys Path (a
1188 string) and Queries (an array reference of arrays, where elements are time,
1189 sql, bind parameters, and duration).
1193 sub AddRequestToHistory {
1195 my $request = shift;
1197 push @{ $self->{QueryHistory} }, $request;
1202 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1203 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1212 return $self->dbh->quote($value);
1217 Takes a SQL query and an array reference of bind parameters and fills in the
1218 query's C<?> parameters.
1229 # is this regex sufficient?
1230 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1235 # log a mason stack trace instead of a Carp::longmess because it's less painful
1236 # and uses mason component paths properly
1237 sub _LogSQLStatement {
1239 my $statement = shift;
1240 my $duration = shift;
1243 require HTML::Mason::Exceptions;
1244 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1250 my $dbh = shift || $self->dbh;
1255 $dbh->{Driver}->{Name} eq 'Pg'
1256 && $dbh->{'pg_server_version'} >= 90200
1257 && !eval { DBD::Pg->VERSION('2.19.3'); 1 }
1259 die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@";
1265 my $sth = $dbh->table_info( '', undef, undef, "'TABLE'");
1266 while ( my $table = $sth->fetchrow_hashref ) {
1267 push @res, $table->{TABLE_NAME} || $table->{table_name};
1273 __PACKAGE__->FinalizeDatabaseType;
1275 RT::Base->_ImportOverlays();