1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 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;
134 # Force UTF8, even when database encoding is not UTF8
135 # DBD::Pg used to do this for us prior to v3
136 $self->dbh->do('SET client_encoding TO UTF8;');
137 $self->dbh->{pg_enable_utf8} = -1;
142 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
147 Build the DSN for the RT database. Doesn't take any parameters, draws all that
155 # Unless the database port is a positive integer, we really don't want to pass it.
156 my $db_port = RT->Config->Get('DatabasePort');
157 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
158 my $db_host = RT->Config->Get('DatabaseHost');
159 $db_host = undef unless $db_host;
160 my $db_name = RT->Config->Get('DatabaseName');
161 my $db_type = RT->Config->Get('DatabaseType');
162 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
163 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
167 Database => $db_name,
170 RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
172 if ( $db_type eq 'Oracle' && $db_host ) {
173 $args{'SID'} = delete $args{'Database'};
175 $self->SUPER::BuildDSN( %args );
180 Returns the DSN for this handle. In order to get correct value you must
181 build DSN first, see L</BuildDSN>.
183 This is method can be called as class method, in this case creates
184 temporary handle object, L</BuildDSN builds DSN> and returns it.
190 return $self->SUPER::DSN if ref $self;
192 my $handle = $self->new;
199 Returns a DSN suitable for database creates and drops
200 and user creates and drops.
202 Gets RT's DSN first (see L<DSN>) and then change it according
203 to requirements of a database system RT's using.
210 my $db_name = RT->Config->Get('DatabaseName');
211 my $db_type = RT->Config->Get('DatabaseType');
213 my $dsn = $self->DSN;
214 if ( $db_type eq 'mysql' ) {
215 # with mysql, you want to connect sans database to funge things
216 $dsn =~ s/dbname=\Q$db_name//;
218 elsif ( $db_type eq 'Pg' ) {
219 # with postgres, you want to connect to template1 database
220 $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
225 =head2 Database compatibility and integrity checks
233 $self = new $self unless ref $self;
235 unless ($RT::Handle and $RT::Handle->dbh) {
237 unless ( eval { RT::ConnectToDatabase(); 1 } ) {
238 return (0, 'no connection', "$@");
242 require RT::CurrentUser;
243 my $test_user = RT::CurrentUser->new;
244 $test_user->Load('RT_System');
245 unless ( $test_user->id ) {
246 return (0, 'no system user', "Couldn't find RT_System user in the DB '". $self->DSN ."'");
249 $test_user = RT::CurrentUser->new;
250 $test_user->Load('Nobody');
251 unless ( $test_user->id ) {
252 return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
258 sub CheckCompatibility {
261 my $state = shift || 'post';
263 my $db_type = RT->Config->Get('DatabaseType');
264 if ( $db_type eq "mysql" ) {
265 # Check which version we're running
266 my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
267 return (0, "couldn't get version of the mysql server")
270 ($version) = $version =~ /^(\d+\.\d+)/;
271 return (0, "RT is unsupported on MySQL versions before 4.1. Your version is $version.")
274 # MySQL must have InnoDB support
275 local $dbh->{FetchHashKeyName} = 'NAME_lc';
276 my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no");
277 if ( $innodb eq "no" ) {
278 return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
279 "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n".
280 "and check that there are no 'skip-innodb' lines in your my.cnf.");
281 } elsif ( $innodb eq "disabled" ) {
282 return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
283 "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n");
286 if ( $state eq 'post' ) {
287 my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Tickets")->[1];
288 unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
289 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
292 $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
293 unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
294 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
295 ."Follow instructions in the UPGRADING.mysql file.");
299 my $max_packet = ($dbh->selectrow_array("show variables like 'max_allowed_packet'"))[1];
300 if ($state =~ /^(create|post)$/ and $max_packet <= (1024 * 1024)) {
301 my $max_packet = sprintf("%.1fM", $max_packet/1024/1024);
302 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";
311 my $dbh = $RT::Handle->dbh;
312 local $dbh->{'RaiseError'} = 0;
313 local $dbh->{'PrintError'} = 0;
314 my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
315 $has ||= ($dbh->selectrow_array(
316 "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
319 return 0 unless lc($has||'') eq "yes";
323 =head2 Database maintanance
325 =head3 CreateDatabase $DBH
327 Creates a new database. This method can be used as class method.
329 Takes DBI handle. Many database systems require special handle to
330 allow you to create a new database, so you have to use L<SystemDSN>
331 method during connection.
333 Fetches type and name of the DB from the config.
339 my $dbh = shift or return (0, "No DBI handle provided");
340 my $db_type = RT->Config->Get('DatabaseType');
341 my $db_name = RT->Config->Get('DatabaseName');
344 if ( $db_type eq 'SQLite' ) {
345 return (1, 'Skipped as SQLite doesn\'t need any action');
347 elsif ( $db_type eq 'Oracle' ) {
348 my $db_user = RT->Config->Get('DatabaseUser');
349 my $db_pass = RT->Config->Get('DatabasePassword');
351 "CREATE USER $db_user IDENTIFIED BY $db_pass"
352 ." default tablespace USERS"
353 ." temporary tablespace TEMP"
354 ." quota unlimited on USERS"
357 return $status, "Couldn't create user $db_user identified by $db_pass."
358 ."\nError: ". $dbh->errstr;
360 $status = $dbh->do( "GRANT connect, resource TO $db_user" );
362 return $status, "Couldn't grant connect and resource to $db_user."
363 ."\nError: ". $dbh->errstr;
365 return (1, "Created user $db_user. All RT's objects should be in his schema.");
367 elsif ( $db_type eq 'Pg' ) {
368 $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
370 elsif ( $db_type eq 'mysql' ) {
371 $status = $dbh->do("CREATE DATABASE $db_name DEFAULT CHARACTER SET utf8");
374 $status = $dbh->do("CREATE DATABASE $db_name");
376 return ($status, $DBI::errstr);
379 =head3 DropDatabase $DBH
381 Drops RT's database. This method can be used as class method.
383 Takes DBI handle as first argument. Many database systems require
384 a special handle to allow you to drop a database, so you may have
385 to use L<SystemDSN> when acquiring the DBI handle.
387 Fetches the type and name of the database from the config.
393 my $dbh = shift or return (0, "No DBI handle provided");
395 my $db_type = RT->Config->Get('DatabaseType');
396 my $db_name = RT->Config->Get('DatabaseName');
398 if ( $db_type eq 'Oracle' ) {
399 my $db_user = RT->Config->Get('DatabaseUser');
400 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
402 return 0, "Couldn't drop user $db_user."
403 ."\nError: ". $dbh->errstr;
405 return (1, "Successfully dropped user '$db_user' with his schema.");
407 elsif ( $db_type eq 'SQLite' ) {
409 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
410 unlink $path or return (0, "Couldn't remove '$path': $!");
413 $dbh->do("DROP DATABASE ". $db_name)
414 or return (0, $DBI::errstr);
426 my $base_path = shift || $RT::EtcPath;
428 my $db_type = RT->Config->Get('DatabaseType');
429 return (1) if $db_type eq 'SQLite';
431 $dbh = $self->dbh if !$dbh && ref $self;
432 return (0, "No DBI handle provided") unless $dbh;
434 return (0, "'$base_path' doesn't exist") unless -e $base_path;
437 if ( -d $base_path ) {
438 $path = File::Spec->catfile( $base_path, "acl.$db_type");
439 $path = $self->GetVersionFile($dbh, $path);
441 $path = File::Spec->catfile( $base_path, "acl")
442 unless $path && -e $path;
443 return (0, "Couldn't find ACLs for $db_type")
450 do $path || return (0, "Couldn't load ACLs: " . $@);
452 foreach my $statement (@acl) {
453 my $sth = $dbh->prepare($statement)
454 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
455 unless ( $sth->execute ) {
456 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
469 my $base_path = (shift || $RT::EtcPath);
471 $dbh = $self->dbh if !$dbh && ref $self;
472 return (0, "No DBI handle provided") unless $dbh;
474 my $db_type = RT->Config->Get('DatabaseType');
477 if ( -d $base_path ) {
478 $file = $base_path . "/schema." . $db_type;
483 $file = $self->GetVersionFile( $dbh, $file );
485 return (0, "Couldn't find schema file(s) '$file*'");
487 unless ( -f $file && -r $file ) {
488 return (0, "File '$file' doesn't exist or couldn't be read");
493 open( my $fh_schema, '<', $file ) or die $!;
496 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
500 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
504 if ( $line =~ /;(\s*)$/ ) {
505 $statement =~ s/;(\s*)$//g;
506 push @schema, $statement;
510 close $fh_schema; close $fh_schema_local;
512 if ( $db_type eq 'Oracle' ) {
513 my $db_user = RT->Config->Get('DatabaseUser');
514 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
516 return $status, "Couldn't set current schema to $db_user."
517 ."\nError: ". $dbh->errstr;
521 local $SIG{__WARN__} = sub {};
523 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
524 foreach my $statement (@schema) {
525 if ( $statement =~ /^\s*;$/ ) {
529 my $sth = $dbh->prepare($statement)
530 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
531 unless ( $sth->execute or $is_local ) {
532 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
535 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
539 =head1 GetVersionFile
541 Takes base name of the file as argument, scans for <base name>-<version> named
542 files and returns file name with closest version to the version of the RT DB.
549 my $base_name = shift;
551 my $db_version = ref $self
552 ? $self->DatabaseVersion
554 my $tmp = RT::Handle->new;
556 $tmp->DatabaseVersion;
560 my @files = File::Glob::bsd_glob("$base_name*");
561 return '' unless @files;
563 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
565 foreach ( reverse sort cmp_version keys %version ) {
566 if ( cmp_version( $db_version, $_ ) >= 0 ) {
572 return defined $version? $version{ $version } : undef;
584 sub cmp_version($$) {
586 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
587 split /([^0-9]+)/, $a;
588 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
589 split /([^0-9]+)/, $b;
591 ? push @b, (0) x (@a-@b)
592 : push @a, (0) x (@b-@a);
593 for ( my $i = 0; $i < @a; $i++ ) {
594 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
606 =head2 InsertInitialData
608 Inserts system objects into RT's DB, like system user or 'nobody',
609 internal groups and other records required. However, this method
610 doesn't insert any real users like 'root' and you have to use
611 InsertData or another way to do that.
613 Takes no arguments. Returns status and message tuple.
615 It's safe to call this method even if those objects already exist.
619 sub InsertInitialData {
624 # create RT_System user and grant him rights
626 require RT::CurrentUser;
628 my $test_user = RT::User->new( RT::CurrentUser->new() );
629 $test_user->Load('RT_System');
630 if ( $test_user->id ) {
631 push @warns, "Found system user in the DB.";
634 my $user = RT::User->new( RT::CurrentUser->new() );
635 my ( $val, $msg ) = $user->_BootstrapCreate(
637 RealName => 'The RT System itself',
638 Comments => 'Do not delete or modify this user. '
639 . 'It is integral to RT\'s internal database structures',
641 LastUpdatedBy => '1',
643 return ($val, $msg) unless $val;
645 DBIx::SearchBuilder::Record::Cachable->FlushCache;
648 # init RT::SystemUser and RT::System objects
649 RT::InitSystemObjects();
650 unless ( RT->SystemUser->id ) {
651 return (0, "Couldn't load system user");
654 # grant SuperUser right to system user
656 my $test_ace = RT::ACE->new( RT->SystemUser );
657 $test_ace->LoadByCols(
658 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
659 PrincipalType => 'Group',
660 RightName => 'SuperUser',
661 ObjectType => 'RT::System',
664 if ( $test_ace->id ) {
665 push @warns, "System user has global SuperUser right.";
667 my $ace = RT::ACE->new( RT->SystemUser );
668 my ( $val, $msg ) = $ace->_BootstrapCreate(
669 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
670 PrincipalType => 'Group',
671 RightName => 'SuperUser',
672 ObjectType => 'RT::System',
675 return ($val, $msg) unless $val;
677 DBIx::SearchBuilder::Record::Cachable->FlushCache;
681 # $self->loc('Everyone'); # For the string extractor to get a string to localize
682 # $self->loc('Privileged'); # For the string extractor to get a string to localize
683 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
684 foreach my $name (qw(Everyone Privileged Unprivileged)) {
685 my $group = RT::Group->new( RT->SystemUser );
686 $group->LoadSystemInternalGroup( $name );
688 push @warns, "System group '$name' already exists.";
692 $group = RT::Group->new( RT->SystemUser );
693 my ( $val, $msg ) = $group->_Create(
695 Domain => 'SystemInternal',
696 Description => 'Pseudogroup for internal use', # loc
700 return ($val, $msg) unless $val;
705 my $user = RT::User->new( RT->SystemUser );
706 $user->Load('Nobody');
708 push @warns, "Found 'Nobody' user in the DB.";
711 my ( $val, $msg ) = $user->Create(
713 RealName => 'Nobody in particular',
714 Comments => 'Do not delete or modify this user. It is integral '
715 .'to RT\'s internal data structures',
718 return ($val, $msg) unless $val;
721 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
722 push @warns, "User 'Nobody' has global OwnTicket right.";
724 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
725 Right => 'OwnTicket',
726 Object => $RT::System,
728 return ($val, $msg) unless $val;
732 # rerun to get init Nobody as well
733 RT::InitSystemObjects();
736 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
737 my $group = RT::Group->new( RT->SystemUser );
738 $group->LoadSystemRoleGroup( $name );
740 push @warns, "System role '$name' already exists.";
744 $group = RT::Group->new( RT->SystemUser );
745 my ( $val, $msg ) = $group->_Create(
747 Domain => 'RT::System-Role',
748 Description => 'SystemRolegroup for internal use', # loc
752 return ($val, $msg) unless $val;
755 push @warns, "You appear to have a functional RT database."
758 return (1, join "\n", @warns);
763 Load some sort of data into the database, takes path to a file.
769 my $datafile = shift;
770 my $root_password = shift;
772 disconnect_after => 1,
776 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
777 our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
778 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
779 local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
780 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
783 $RT::Logger->debug("Going to load '$datafile' data file");
784 eval { require $datafile }
785 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
788 $RT::Logger->debug("Running initial actions...");
789 foreach ( @Initial ) {
791 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
793 $RT::Logger->debug("Done.");
796 $RT::Logger->debug("Creating groups...");
797 foreach my $item (@Groups) {
798 my $new_entry = RT::Group->new( RT->SystemUser );
799 $item->{Domain} ||= 'UserDefined';
800 my $member_of = delete $item->{'MemberOf'};
801 my $members = delete $item->{'Members'};
802 my ( $return, $msg ) = $new_entry->_Create(%$item);
804 $RT::Logger->error( $msg );
807 $RT::Logger->debug($return .".");
810 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
811 foreach( @$member_of ) {
812 my $parent = RT::Group->new(RT->SystemUser);
813 if ( ref $_ eq 'HASH' ) {
814 $parent->LoadByCols( %$_ );
817 $parent->LoadUserDefinedGroup( $_ );
821 "(Error: wrong format of MemberOf field."
822 ." Should be name of user defined group or"
823 ." hash reference with 'column => value' pairs."
824 ." Use array reference to add to multiple groups)"
828 unless ( $parent->Id ) {
829 $RT::Logger->error("(Error: couldn't load group to add member)");
832 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
834 $RT::Logger->error( $msg );
836 $RT::Logger->debug( $return ."." );
840 push @Members, map { +{Group => $new_entry->id,
841 Class => "RT::User", Name => $_} }
842 @{ $members->{Users} || [] };
843 push @Members, map { +{Group => $new_entry->id,
844 Class => "RT::Group", Name => $_} }
845 @{ $members->{Groups} || [] };
847 $RT::Logger->debug("done.");
850 $RT::Logger->debug("Creating users...");
851 foreach my $item (@Users) {
852 if ( $item->{'Name'} eq 'root' && $root_password ) {
853 $item->{'Password'} = $root_password;
855 my $new_entry = RT::User->new( RT->SystemUser );
856 my ( $return, $msg ) = $new_entry->Create(%$item);
858 $RT::Logger->error( $msg );
860 $RT::Logger->debug( $return ."." );
863 $RT::Logger->debug("done.");
866 $RT::Logger->debug("Adding users and groups to groups...");
867 for my $item (@Members) {
868 my $group = RT::Group->new(RT->SystemUser);
869 $group->LoadUserDefinedGroup( delete $item->{Group} );
870 unless ($group->Id) {
871 RT->Logger->error("Unable to find group '$group' to add members to");
875 my $class = delete $item->{Class} || 'RT::User';
876 my $member = $class->new( RT->SystemUser );
877 $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
878 $member->LoadByCols( %$item );
879 unless ($member->Id) {
880 RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
884 my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
886 $RT::Logger->error( $msg );
888 $RT::Logger->debug( $return ."." );
893 $RT::Logger->debug("Creating queues...");
894 for my $item (@Queues) {
895 my $new_entry = RT::Queue->new(RT->SystemUser);
896 my ( $return, $msg ) = $new_entry->Create(%$item);
898 $RT::Logger->error( $msg );
900 $RT::Logger->debug( $return ."." );
903 $RT::Logger->debug("done.");
905 if ( @CustomFields ) {
906 $RT::Logger->debug("Creating custom fields...");
907 for my $item ( @CustomFields ) {
908 my $new_entry = RT::CustomField->new( RT->SystemUser );
909 my $values = delete $item->{'Values'};
912 # if ref then it's list of queues, so we do things ourself
913 if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
914 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
915 @queues = @{ delete $item->{'Queue'} };
918 if ( $item->{'BasedOn'} ) {
919 if ( $item->{'BasedOn'} =~ /^\d+$/) {
920 # Already have an ID -- should be fine
921 } elsif ( $item->{'LookupType'} ) {
922 my $basedon = RT::CustomField->new($RT::SystemUser);
923 my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
924 LookupType => $item->{'LookupType'} );
926 $item->{'BasedOn'} = $basedon->Id;
928 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
929 delete $item->{'BasedOn'};
932 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
933 delete $item->{'BasedOn'};
938 my ( $return, $msg ) = $new_entry->Create(%$item);
940 $RT::Logger->error( $msg );
944 foreach my $value ( @{$values} ) {
945 my ( $return, $msg ) = $new_entry->AddValue(%$value);
946 $RT::Logger->error( $msg ) unless $return;
950 if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
951 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
952 $ocf->Create( CustomField => $new_entry->Id );
955 for my $q (@queues) {
956 my $q_obj = RT::Queue->new(RT->SystemUser);
958 unless ( $q_obj->Id ) {
959 $RT::Logger->error("Could not find queue ". $q );
962 my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
963 ( $return, $msg ) = $OCF->Create(
964 CustomField => $new_entry->Id,
965 ObjectId => $q_obj->Id,
967 $RT::Logger->error( $msg ) unless $return and $OCF->Id;
971 $RT::Logger->debug("done.");
974 $RT::Logger->debug("Creating ACL...");
975 for my $item (@ACL) {
977 my ($princ, $object);
979 # Global rights or Queue rights?
980 if ( $item->{'CF'} ) {
981 $object = RT::CustomField->new( RT->SystemUser );
982 my @columns = ( Name => $item->{'CF'} );
983 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
984 $object->LoadByName( @columns );
985 } elsif ( $item->{'Queue'} ) {
986 $object = RT::Queue->new(RT->SystemUser);
987 $object->Load( $item->{'Queue'} );
989 $object = $RT::System;
992 $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
994 # Group rights or user rights?
995 if ( $item->{'GroupDomain'} ) {
996 $princ = RT::Group->new(RT->SystemUser);
997 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
998 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
999 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
1000 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
1001 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
1002 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
1003 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
1006 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
1007 Queue => $object->id);
1009 $princ->Load( $item->{'GroupId'} );
1011 unless ( $princ->Id ) {
1012 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
1016 $princ = RT::User->new(RT->SystemUser);
1017 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
1019 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
1025 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
1026 Right => $item->{'Right'},
1029 unless ( $return ) {
1030 $RT::Logger->error( $msg );
1033 $RT::Logger->debug( $return ."." );
1036 $RT::Logger->debug("done.");
1039 if ( @ScripActions ) {
1040 $RT::Logger->debug("Creating ScripActions...");
1042 for my $item (@ScripActions) {
1043 my $new_entry = RT::ScripAction->new(RT->SystemUser);
1044 my ( $return, $msg ) = $new_entry->Create(%$item);
1045 unless ( $return ) {
1046 $RT::Logger->error( $msg );
1049 $RT::Logger->debug( $return ."." );
1053 $RT::Logger->debug("done.");
1056 if ( @ScripConditions ) {
1057 $RT::Logger->debug("Creating ScripConditions...");
1059 for my $item (@ScripConditions) {
1060 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1061 my ( $return, $msg ) = $new_entry->Create(%$item);
1062 unless ( $return ) {
1063 $RT::Logger->error( $msg );
1066 $RT::Logger->debug( $return ."." );
1070 $RT::Logger->debug("done.");
1074 $RT::Logger->debug("Creating templates...");
1076 for my $item (@Templates) {
1077 my $new_entry = RT::Template->new(RT->SystemUser);
1078 my ( $return, $msg ) = $new_entry->Create(%$item);
1079 unless ( $return ) {
1080 $RT::Logger->error( $msg );
1083 $RT::Logger->debug( $return ."." );
1086 $RT::Logger->debug("done.");
1089 $RT::Logger->debug("Creating scrips...");
1091 for my $item (@Scrips) {
1092 my $new_entry = RT::Scrip->new(RT->SystemUser);
1094 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1095 push @queues, 0 unless @queues; # add global queue at least
1097 foreach my $q ( @queues ) {
1098 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
1099 unless ( $return ) {
1100 $RT::Logger->error( $msg );
1103 $RT::Logger->debug( $return ."." );
1107 $RT::Logger->debug("done.");
1109 if ( @Attributes ) {
1110 $RT::Logger->debug("Creating attributes...");
1111 my $sys = RT::System->new(RT->SystemUser);
1113 for my $item (@Attributes) {
1114 my $obj = delete $item->{Object}; # XXX: make this something loadable
1116 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1117 unless ( $return ) {
1118 $RT::Logger->error( $msg );
1121 $RT::Logger->debug( $return ."." );
1124 $RT::Logger->debug("done.");
1127 $RT::Logger->debug("Running final actions...");
1131 $RT::Logger->error( "Failed to run one of final actions: $@" )
1134 $RT::Logger->debug("done.");
1137 # XXX: This disconnect doesn't really belong here; it's a relict from when
1138 # this method was extracted from rt-setup-database. However, too much
1139 # depends on it to change without significant testing. At the very least,
1140 # we can provide a way to skip the side-effect.
1141 if ( $args{disconnect_after} ) {
1142 my $db_type = RT->Config->Get('DatabaseType');
1143 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1146 $RT::Logger->debug("Done setting up database content.");
1148 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1149 # should return (0, $msg) if error happens instead of just warning.
1150 # anyway, we need to return something here to tell if everything is ok
1151 return( 1, 'Done inserting data' );
1154 =head2 ACLEquivGroupId
1156 Given a userid, return that user's acl equivalence group
1160 sub ACLEquivGroupId {
1163 my $cu = RT->SystemUser;
1165 require RT::CurrentUser;
1166 $cu = RT::CurrentUser->new;
1167 $cu->LoadByName('RT_System');
1168 warn "Couldn't load RT_System user" unless $cu->id;
1171 my $equiv_group = RT::Group->new( $cu );
1172 $equiv_group->LoadACLEquivalenceGroup( $id );
1173 return $equiv_group->Id;
1178 Returns the SQL query history associated with this handle. The top level array
1179 represents a lists of request. Each request is a hash with metadata about the
1180 request (such as the URL) and a list of queries. You'll probably not be using this.
1187 return $self->{QueryHistory};
1190 =head2 AddRequestToHistory
1192 Adds a web request to the query history. It must be a hash with keys Path (a
1193 string) and Queries (an array reference of arrays, where elements are time,
1194 sql, bind parameters, and duration).
1198 sub AddRequestToHistory {
1200 my $request = shift;
1202 push @{ $self->{QueryHistory} }, $request;
1207 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1208 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1217 return $self->dbh->quote($value);
1222 Takes a SQL query and an array reference of bind parameters and fills in the
1223 query's C<?> parameters.
1234 # is this regex sufficient?
1235 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1240 # log a mason stack trace instead of a Carp::longmess because it's less painful
1241 # and uses mason component paths properly
1242 sub _LogSQLStatement {
1244 my $statement = shift;
1245 my $duration = shift;
1248 require HTML::Mason::Exceptions;
1249 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1255 my $dbh = shift || $self->dbh;
1260 $dbh->{Driver}->{Name} eq 'Pg'
1261 && $dbh->{'pg_server_version'} >= 90200
1262 && !eval { DBD::Pg->VERSION('2.19.3'); 1 }
1264 die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@";
1270 my $sth = $dbh->table_info( '', undef, undef, "'TABLE'");
1271 while ( my $table = $sth->fetchrow_hashref ) {
1272 push @res, $table->{TABLE_NAME} || $table->{table_name};
1278 __PACKAGE__->FinalizeDatabaseType;
1280 RT::Base->_ImportOverlays();