1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::Handle - RT's database handle
56 BEGIN { RT::LoadConfig() };
61 C<RT::Handle> is RT specific wrapper over one of L<DBIx::SearchBuilder::Handle>
62 classes. As RT works with different types of DBs we subclass repsective handler
63 from L<DBIx::SerachBuilder>. Type of the DB is defined by C<DatabasseType> RT's
64 config option. You B<must> load this module only when the configs have been
78 =head2 FinalizeDatabaseType
80 Sets RT::Handle's superclass to the correct subclass of
81 L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration.
85 sub FinalizeDatabaseType {
87 use base "DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType');
91 die "Unable to load DBIx::SearchBuilder database handle for '". RT->Config->Get('DatabaseType') ."'.\n".
92 "Perhaps you've picked an invalid database type or spelled it incorrectly.\n".
99 Connects to RT's database using credentials and options from the RT config.
108 my $db_type = RT->Config->Get('DatabaseType');
109 if ( $db_type eq 'Oracle' ) {
110 $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
111 $ENV{'NLS_NCHAR'} = "AL32UTF8";
114 $self->SUPER::Connect(
115 User => RT->Config->Get('DatabaseUser'),
116 Password => RT->Config->Get('DatabasePassword'),
120 if ( $db_type eq 'mysql' ) {
121 my $version = $self->DatabaseVersion;
122 ($version) = $version =~ /^(\d+\.\d+)/;
123 $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
127 if ( $db_type eq 'Pg' ) {
128 my $version = $self->DatabaseVersion;
129 ($version) = $version =~ /^(\d+\.\d+)/;
130 $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
135 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
140 Build the DSN for the RT database. Doesn't take any parameters, draws all that
148 # Unless the database port is a positive integer, we really don't want to pass it.
149 my $db_port = RT->Config->Get('DatabasePort');
150 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
151 my $db_host = RT->Config->Get('DatabaseHost');
152 $db_host = undef unless $db_host;
153 my $db_name = RT->Config->Get('DatabaseName');
154 my $db_type = RT->Config->Get('DatabaseType');
155 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
156 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
160 Database => $db_name,
163 RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
164 DisconnectHandleOnDestroy => 1,
166 if ( $db_type eq 'Oracle' && $db_host ) {
167 $args{'SID'} = delete $args{'Database'};
169 $self->SUPER::BuildDSN( %args );
174 Returns the DSN for this handle. In order to get correct value you must
175 build DSN first, see L</BuildDSN>.
177 This is method can be called as class method, in this case creates
178 temporary handle object, L</BuildDSN builds DSN> and returns it.
184 return $self->SUPER::DSN if ref $self;
186 my $handle = $self->new;
193 Returns a DSN suitable for database creates and drops
194 and user creates and drops.
196 Gets RT's DSN first (see L<DSN>) and then change it according
197 to requirements of a database system RT's using.
204 my $db_name = RT->Config->Get('DatabaseName');
205 my $db_type = RT->Config->Get('DatabaseType');
207 my $dsn = $self->DSN;
208 if ( $db_type eq 'mysql' ) {
209 # with mysql, you want to connect sans database to funge things
210 $dsn =~ s/dbname=\Q$db_name//;
212 elsif ( $db_type eq 'Pg' ) {
213 # with postgres, you want to connect to template1 database
214 $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
219 =head2 Database compatibility and integrity checks
227 $self = new $self unless ref $self;
229 unless ($RT::Handle and $RT::Handle->dbh) {
231 unless ( eval { RT::ConnectToDatabase(); 1 } ) {
232 return (0, 'no connection', "$@");
236 require RT::CurrentUser;
237 my $test_user = RT::CurrentUser->new;
238 $test_user->Load('RT_System');
239 unless ( $test_user->id ) {
240 return (0, 'no system user', "Couldn't find RT_System user in the DB '". $self->DSN ."'");
243 $test_user = RT::CurrentUser->new;
244 $test_user->Load('Nobody');
245 unless ( $test_user->id ) {
246 return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
249 return $RT::Handle->dbh;
252 sub CheckCompatibility {
255 my $state = shift || 'post';
257 my $db_type = RT->Config->Get('DatabaseType');
258 if ( $db_type eq "mysql" ) {
259 # Check which version we're running
260 my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
261 return (0, "couldn't get version of the mysql server")
264 ($version) = $version =~ /^(\d+\.\d+)/;
265 return (0, "RT is unsupported on MySQL versions before 4.0.x, it's $version")
268 # MySQL must have InnoDB support
269 my $innodb = ($dbh->selectrow_array("show variables like 'have_innodb'"))[1];
270 if ( lc $innodb eq "no" ) {
271 return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
272 "See http://dev.mysql.com/doc/mysql/en/InnoDB.html");
273 } elsif ( lc $innodb eq "disabled" ) {
274 return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
275 "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
278 if ( $state eq 'post' ) {
279 my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Tickets")->[1];
280 unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
281 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
284 if ( $version >= 4.1 && $state eq 'post' ) {
285 my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
286 unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
287 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
288 ."Follow instructions in the UPGRADING.mysql file.");
298 my $dbh = $RT::Handle->dbh;
299 local $dbh->{'RaiseError'} = 0;
300 local $dbh->{'PrintError'} = 0;
301 my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
302 $has ||= ($dbh->selectrow_array(
303 "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
306 return 0 unless lc($has||'') eq "yes";
310 =head2 Database maintanance
312 =head3 CreateDatabase $DBH
314 Creates a new database. This method can be used as class method.
316 Takes DBI handle. Many database systems require special handle to
317 allow you to create a new database, so you have to use L<SystemDSN>
318 method during connection.
320 Fetches type and name of the DB from the config.
326 my $dbh = shift or return (0, "No DBI handle provided");
327 my $db_type = RT->Config->Get('DatabaseType');
328 my $db_name = RT->Config->Get('DatabaseName');
331 if ( $db_type eq 'SQLite' ) {
332 return (1, 'Skipped as SQLite doesn\'t need any action');
334 elsif ( $db_type eq 'Oracle' ) {
335 my $db_user = RT->Config->Get('DatabaseUser');
336 my $db_pass = RT->Config->Get('DatabasePassword');
338 "CREATE USER $db_user IDENTIFIED BY $db_pass"
339 ." default tablespace USERS"
340 ." temporary tablespace TEMP"
341 ." quota unlimited on USERS"
344 return $status, "Couldn't create user $db_user identified by $db_pass."
345 ."\nError: ". $dbh->errstr;
347 $status = $dbh->do( "GRANT connect, resource TO $db_user" );
349 return $status, "Couldn't grant connect and resource to $db_user."
350 ."\nError: ". $dbh->errstr;
352 return (1, "Created user $db_user. All RT's objects should be in his schema.");
354 elsif ( $db_type eq 'Pg' ) {
355 $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
358 $status = $dbh->do("CREATE DATABASE $db_name");
360 return ($status, $DBI::errstr);
363 =head3 DropDatabase $DBH
365 Drops RT's database. This method can be used as class method.
367 Takes DBI handle as first argument. Many database systems require
368 a special handle to allow you to drop a database, so you may have
369 to use L<SystemDSN> when acquiring the DBI handle.
371 Fetches the type and name of the database from the config.
377 my $dbh = shift or return (0, "No DBI handle provided");
379 my $db_type = RT->Config->Get('DatabaseType');
380 my $db_name = RT->Config->Get('DatabaseName');
382 if ( $db_type eq 'Oracle' ) {
383 my $db_user = RT->Config->Get('DatabaseUser');
384 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
386 return 0, "Couldn't drop user $db_user."
387 ."\nError: ". $dbh->errstr;
389 return (1, "Successfully dropped user '$db_user' with his schema.");
391 elsif ( $db_type eq 'SQLite' ) {
393 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
394 unlink $path or return (0, "Couldn't remove '$path': $!");
397 $dbh->do("DROP DATABASE ". $db_name)
398 or return (0, $DBI::errstr);
410 my $base_path = shift || $RT::EtcPath;
412 my $db_type = RT->Config->Get('DatabaseType');
413 return (1) if $db_type eq 'SQLite';
415 $dbh = $self->dbh if !$dbh && ref $self;
416 return (0, "No DBI handle provided") unless $dbh;
418 return (0, "'$base_path' doesn't exist") unless -e $base_path;
421 if ( -d $base_path ) {
422 $path = File::Spec->catfile( $base_path, "acl.$db_type");
423 $path = $self->GetVersionFile($dbh, $path);
425 $path = File::Spec->catfile( $base_path, "acl")
426 unless $path && -e $path;
427 return (0, "Couldn't find ACLs for $db_type")
434 do $path || return (0, "Couldn't load ACLs: " . $@);
436 foreach my $statement (@acl) {
437 my $sth = $dbh->prepare($statement)
438 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
439 unless ( $sth->execute ) {
440 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
453 my $base_path = (shift || $RT::EtcPath);
455 $dbh = $self->dbh if !$dbh && ref $self;
456 return (0, "No DBI handle provided") unless $dbh;
458 my $db_type = RT->Config->Get('DatabaseType');
461 if ( -d $base_path ) {
462 $file = $base_path . "/schema." . $db_type;
467 $file = $self->GetVersionFile( $dbh, $file );
469 return (0, "Couldn't find schema file(s) '$file*'");
471 unless ( -f $file && -r $file ) {
472 return (0, "File '$file' doesn't exist or couldn't be read");
477 open( my $fh_schema, '<', $file ) or die $!;
480 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
484 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
488 if ( $line =~ /;(\s*)$/ ) {
489 $statement =~ s/;(\s*)$//g;
490 push @schema, $statement;
494 close $fh_schema; close $fh_schema_local;
496 if ( $db_type eq 'Oracle' ) {
497 my $db_user = RT->Config->Get('DatabaseUser');
498 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
500 return $status, "Couldn't set current schema to $db_user."
501 ."\nError: ". $dbh->errstr;
505 local $SIG{__WARN__} = sub {};
507 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
508 foreach my $statement (@schema) {
509 if ( $statement =~ /^\s*;$/ ) {
513 my $sth = $dbh->prepare($statement)
514 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
515 unless ( $sth->execute or $is_local ) {
516 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
519 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
523 =head1 GetVersionFile
525 Takes base name of the file as argument, scans for <base name>-<version> named
526 files and returns file name with closest version to the version of the RT DB.
533 my $base_name = shift;
535 my $db_version = ref $self
536 ? $self->DatabaseVersion
538 my $tmp = RT::Handle->new;
540 $tmp->DatabaseVersion;
544 my @files = File::Glob::bsd_glob("$base_name*");
545 return '' unless @files;
547 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
549 foreach ( reverse sort cmp_version keys %version ) {
550 if ( cmp_version( $db_version, $_ ) >= 0 ) {
556 return defined $version? $version{ $version } : undef;
568 sub cmp_version($$) {
570 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
571 split /([^0-9]+)/, $a;
572 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
573 split /([^0-9]+)/, $b;
575 ? push @b, (0) x (@a-@b)
576 : push @a, (0) x (@b-@a);
577 for ( my $i = 0; $i < @a; $i++ ) {
578 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
590 =head2 InsertInitialData
592 Inserts system objects into RT's DB, like system user or 'nobody',
593 internal groups and other records required. However, this method
594 doesn't insert any real users like 'root' and you have to use
595 InsertData or another way to do that.
597 Takes no arguments. Returns status and message tuple.
599 It's safe to call this method even if those objects already exist.
603 sub InsertInitialData {
608 # create RT_System user and grant him rights
610 require RT::CurrentUser;
612 my $test_user = RT::User->new( RT::CurrentUser->new() );
613 $test_user->Load('RT_System');
614 if ( $test_user->id ) {
615 push @warns, "Found system user in the DB.";
618 my $user = RT::User->new( RT::CurrentUser->new() );
619 my ( $val, $msg ) = $user->_BootstrapCreate(
621 RealName => 'The RT System itself',
622 Comments => 'Do not delete or modify this user. '
623 . 'It is integral to RT\'s internal database structures',
625 LastUpdatedBy => '1',
627 return ($val, $msg) unless $val;
629 DBIx::SearchBuilder::Record::Cachable->FlushCache;
632 # init RT::SystemUser and RT::System objects
633 RT::InitSystemObjects();
634 unless ( RT->SystemUser->id ) {
635 return (0, "Couldn't load system user");
638 # grant SuperUser right to system user
640 my $test_ace = RT::ACE->new( RT->SystemUser );
641 $test_ace->LoadByCols(
642 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
643 PrincipalType => 'Group',
644 RightName => 'SuperUser',
645 ObjectType => 'RT::System',
648 if ( $test_ace->id ) {
649 push @warns, "System user has global SuperUser right.";
651 my $ace = RT::ACE->new( RT->SystemUser );
652 my ( $val, $msg ) = $ace->_BootstrapCreate(
653 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
654 PrincipalType => 'Group',
655 RightName => 'SuperUser',
656 ObjectType => 'RT::System',
659 return ($val, $msg) unless $val;
661 DBIx::SearchBuilder::Record::Cachable->FlushCache;
665 # $self->loc('Everyone'); # For the string extractor to get a string to localize
666 # $self->loc('Privileged'); # For the string extractor to get a string to localize
667 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
668 foreach my $name (qw(Everyone Privileged Unprivileged)) {
669 my $group = RT::Group->new( RT->SystemUser );
670 $group->LoadSystemInternalGroup( $name );
672 push @warns, "System group '$name' already exists.";
676 $group = RT::Group->new( RT->SystemUser );
677 my ( $val, $msg ) = $group->_Create(
679 Domain => 'SystemInternal',
680 Description => 'Pseudogroup for internal use', # loc
684 return ($val, $msg) unless $val;
689 my $user = RT::User->new( RT->SystemUser );
690 $user->Load('Nobody');
692 push @warns, "Found 'Nobody' user in the DB.";
695 my ( $val, $msg ) = $user->Create(
697 RealName => 'Nobody in particular',
698 Comments => 'Do not delete or modify this user. It is integral '
699 .'to RT\'s internal data structures',
702 return ($val, $msg) unless $val;
705 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
706 push @warns, "User 'Nobody' has global OwnTicket right.";
708 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
709 Right => 'OwnTicket',
710 Object => $RT::System,
712 return ($val, $msg) unless $val;
716 # rerun to get init Nobody as well
717 RT::InitSystemObjects();
720 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
721 my $group = RT::Group->new( RT->SystemUser );
722 $group->LoadSystemRoleGroup( $name );
724 push @warns, "System role '$name' already exists.";
728 $group = RT::Group->new( RT->SystemUser );
729 my ( $val, $msg ) = $group->_Create(
731 Domain => 'RT::System-Role',
732 Description => 'SystemRolegroup for internal use', # loc
736 return ($val, $msg) unless $val;
739 push @warns, "You appear to have a functional RT database."
742 return (1, join "\n", @warns);
747 Load some sort of data into the database, takes path to a file.
753 my $datafile = shift;
754 my $root_password = shift;
756 disconnect_after => 1,
760 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
761 our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
762 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
763 local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
764 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
767 $RT::Logger->debug("Going to load '$datafile' data file");
768 eval { require $datafile }
769 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
772 $RT::Logger->debug("Running initial actions...");
773 foreach ( @Initial ) {
775 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
777 $RT::Logger->debug("Done.");
780 $RT::Logger->debug("Creating groups...");
781 foreach my $item (@Groups) {
782 my $new_entry = RT::Group->new( RT->SystemUser );
783 my $member_of = delete $item->{'MemberOf'};
784 my ( $return, $msg ) = $new_entry->_Create(%$item);
786 $RT::Logger->error( $msg );
789 $RT::Logger->debug($return .".");
792 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
793 foreach( @$member_of ) {
794 my $parent = RT::Group->new(RT->SystemUser);
795 if ( ref $_ eq 'HASH' ) {
796 $parent->LoadByCols( %$_ );
799 $parent->LoadUserDefinedGroup( $_ );
803 "(Error: wrong format of MemberOf field."
804 ." Should be name of user defined group or"
805 ." hash reference with 'column => value' pairs."
806 ." Use array reference to add to multiple groups)"
810 unless ( $parent->Id ) {
811 $RT::Logger->error("(Error: couldn't load group to add member)");
814 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
816 $RT::Logger->error( $msg );
818 $RT::Logger->debug( $return ."." );
823 $RT::Logger->debug("done.");
826 $RT::Logger->debug("Creating users...");
827 foreach my $item (@Users) {
828 if ( $item->{'Name'} eq 'root' && $root_password ) {
829 $item->{'Password'} = $root_password;
831 my $new_entry = RT::User->new( RT->SystemUser );
832 my ( $return, $msg ) = $new_entry->Create(%$item);
834 $RT::Logger->error( $msg );
836 $RT::Logger->debug( $return ."." );
839 $RT::Logger->debug("done.");
842 $RT::Logger->debug("Creating queues...");
843 for my $item (@Queues) {
844 my $new_entry = RT::Queue->new(RT->SystemUser);
845 my ( $return, $msg ) = $new_entry->Create(%$item);
847 $RT::Logger->error( $msg );
849 $RT::Logger->debug( $return ."." );
852 $RT::Logger->debug("done.");
854 if ( @CustomFields ) {
855 $RT::Logger->debug("Creating custom fields...");
856 for my $item ( @CustomFields ) {
857 my $new_entry = RT::CustomField->new( RT->SystemUser );
858 my $values = delete $item->{'Values'};
861 # if ref then it's list of queues, so we do things ourself
862 if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
863 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
864 @queues = @{ delete $item->{'Queue'} };
867 if ( $item->{'BasedOn'} ) {
868 if ( $item->{'LookupType'} ) {
869 my $basedon = RT::CustomField->new($RT::SystemUser);
870 my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
871 LookupType => $item->{'LookupType'} );
873 $item->{'BasedOn'} = $basedon->Id;
875 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
876 delete $item->{'BasedOn'};
879 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
880 delete $item->{'BasedOn'};
885 my ( $return, $msg ) = $new_entry->Create(%$item);
887 $RT::Logger->error( $msg );
891 foreach my $value ( @{$values} ) {
892 my ( $return, $msg ) = $new_entry->AddValue(%$value);
893 $RT::Logger->error( $msg ) unless $return;
897 if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
898 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
899 $ocf->Create( CustomField => $new_entry->Id );
902 for my $q (@queues) {
903 my $q_obj = RT::Queue->new(RT->SystemUser);
905 unless ( $q_obj->Id ) {
906 $RT::Logger->error("Could not find queue ". $q );
909 my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
910 ( $return, $msg ) = $OCF->Create(
911 CustomField => $new_entry->Id,
912 ObjectId => $q_obj->Id,
914 $RT::Logger->error( $msg ) unless $return and $OCF->Id;
918 $RT::Logger->debug("done.");
921 $RT::Logger->debug("Creating ACL...");
922 for my $item (@ACL) {
924 my ($princ, $object);
926 # Global rights or Queue rights?
927 if ( $item->{'CF'} ) {
928 $object = RT::CustomField->new( RT->SystemUser );
929 my @columns = ( Name => $item->{'CF'} );
930 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
931 $object->LoadByName( @columns );
932 } elsif ( $item->{'Queue'} ) {
933 $object = RT::Queue->new(RT->SystemUser);
934 $object->Load( $item->{'Queue'} );
936 $object = $RT::System;
939 $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
941 # Group rights or user rights?
942 if ( $item->{'GroupDomain'} ) {
943 $princ = RT::Group->new(RT->SystemUser);
944 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
945 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
946 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
947 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
948 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
949 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
950 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
953 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
954 Queue => $object->id);
956 $princ->Load( $item->{'GroupId'} );
958 unless ( $princ->Id ) {
959 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
963 $princ = RT::User->new(RT->SystemUser);
964 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
966 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
972 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
973 Right => $item->{'Right'},
977 $RT::Logger->error( $msg );
980 $RT::Logger->debug( $return ."." );
983 $RT::Logger->debug("done.");
986 if ( @ScripActions ) {
987 $RT::Logger->debug("Creating ScripActions...");
989 for my $item (@ScripActions) {
990 my $new_entry = RT::ScripAction->new(RT->SystemUser);
991 my ( $return, $msg ) = $new_entry->Create(%$item);
993 $RT::Logger->error( $msg );
996 $RT::Logger->debug( $return ."." );
1000 $RT::Logger->debug("done.");
1003 if ( @ScripConditions ) {
1004 $RT::Logger->debug("Creating ScripConditions...");
1006 for my $item (@ScripConditions) {
1007 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1008 my ( $return, $msg ) = $new_entry->Create(%$item);
1009 unless ( $return ) {
1010 $RT::Logger->error( $msg );
1013 $RT::Logger->debug( $return ."." );
1017 $RT::Logger->debug("done.");
1021 $RT::Logger->debug("Creating templates...");
1023 for my $item (@Templates) {
1024 my $new_entry = RT::Template->new(RT->SystemUser);
1025 my ( $return, $msg ) = $new_entry->Create(%$item);
1026 unless ( $return ) {
1027 $RT::Logger->error( $msg );
1030 $RT::Logger->debug( $return ."." );
1033 $RT::Logger->debug("done.");
1036 $RT::Logger->debug("Creating scrips...");
1038 for my $item (@Scrips) {
1039 my $new_entry = RT::Scrip->new(RT->SystemUser);
1041 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1042 push @queues, 0 unless @queues; # add global queue at least
1044 foreach my $q ( @queues ) {
1045 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
1046 unless ( $return ) {
1047 $RT::Logger->error( $msg );
1050 $RT::Logger->debug( $return ."." );
1054 $RT::Logger->debug("done.");
1056 if ( @Attributes ) {
1057 $RT::Logger->debug("Creating attributes...");
1058 my $sys = RT::System->new(RT->SystemUser);
1060 for my $item (@Attributes) {
1061 my $obj = delete $item->{Object}; # XXX: make this something loadable
1063 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1064 unless ( $return ) {
1065 $RT::Logger->error( $msg );
1068 $RT::Logger->debug( $return ."." );
1071 $RT::Logger->debug("done.");
1074 $RT::Logger->debug("Running final actions...");
1078 $RT::Logger->error( "Failed to run one of final actions: $@" )
1081 $RT::Logger->debug("done.");
1084 # XXX: This disconnect doesn't really belong here; it's a relict from when
1085 # this method was extracted from rt-setup-database. However, too much
1086 # depends on it to change without significant testing. At the very least,
1087 # we can provide a way to skip the side-effect.
1088 if ( $args{disconnect_after} ) {
1089 my $db_type = RT->Config->Get('DatabaseType');
1090 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1093 $RT::Logger->debug("Done setting up database content.");
1095 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1096 # should return (0, $msg) if error happens instead of just warning.
1097 # anyway, we need to return something here to tell if everything is ok
1098 return( 1, 'Done inserting data' );
1101 =head2 ACLEquivGroupId
1103 Given a userid, return that user's acl equivalence group
1107 sub ACLEquivGroupId {
1110 my $cu = RT->SystemUser;
1112 require RT::CurrentUser;
1113 $cu = RT::CurrentUser->new;
1114 $cu->LoadByName('RT_System');
1115 warn "Couldn't load RT_System user" unless $cu->id;
1118 my $equiv_group = RT::Group->new( $cu );
1119 $equiv_group->LoadACLEquivalenceGroup( $id );
1120 return $equiv_group->Id;
1125 Returns the SQL query history associated with this handle. The top level array
1126 represents a lists of request. Each request is a hash with metadata about the
1127 request (such as the URL) and a list of queries. You'll probably not be using this.
1134 return $self->{QueryHistory};
1137 =head2 AddRequestToHistory
1139 Adds a web request to the query history. It must be a hash with keys Path (a
1140 string) and Queries (an array reference of arrays, where elements are time,
1141 sql, bind parameters, and duration).
1145 sub AddRequestToHistory {
1147 my $request = shift;
1149 push @{ $self->{QueryHistory} }, $request;
1154 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1155 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1164 return $self->dbh->quote($value);
1169 Takes a SQL query and an array reference of bind parameters and fills in the
1170 query's C<?> parameters.
1181 # is this regex sufficient?
1182 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1187 # log a mason stack trace instead of a Carp::longmess because it's less painful
1188 # and uses mason component paths properly
1189 sub _LogSQLStatement {
1191 my $statement = shift;
1192 my $duration = shift;
1195 require HTML::Mason::Exceptions;
1196 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1199 __PACKAGE__->FinalizeDatabaseType;
1201 RT::Base->_ImportOverlays();