+ my $db_type = RT->Config->Get('DatabaseType');
+
+ my $file;
+ if ( -d $base_path ) {
+ $file = $base_path . "/schema." . $db_type;
+ } else {
+ $file = $base_path;
+ }
+
+ $file = $self->GetVersionFile( $dbh, $file );
+ unless ( $file ) {
+ return (0, "Couldn't find schema file(s) '$file*'");
+ }
+ unless ( -f $file && -r $file ) {
+ return (0, "File '$file' doesn't exist or couldn't be read");
+ }
+
+ my (@schema);
+
+ open my $fh_schema, "<$file";
+
+ my $has_local = 0;
+ open my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type )
+ and $has_local = 1;
+
+ my $statement = "";
+ foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
+ $line =~ s/\#.*//g;
+ $line =~ s/--.*//g;
+ $statement .= $line;
+ if ( $line =~ /;(\s*)$/ ) {
+ $statement =~ s/;(\s*)$//g;
+ push @schema, $statement;
+ $statement = "";
+ }
+ }
+ close $fh_schema; close $fh_schema_local;
+
+ if ( $db_type eq 'Oracle' ) {
+ my $db_user = RT->Config->Get('DatabaseUser');
+ my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
+ unless ( $status ) {
+ return $status, "Couldn't set current schema to $db_user."
+ ."\nError: ". $dbh->errstr;
+ }
+ }
+
+ local $SIG{__WARN__} = sub {};
+ my $is_local = 0;
+ $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
+ foreach my $statement (@schema) {
+ if ( $statement =~ /^\s*;$/ ) {
+ $is_local = 1; next;
+ }
+
+ my $sth = $dbh->prepare($statement)
+ or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
+ unless ( $sth->execute or $is_local ) {
+ return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
+ }
+ }
+ $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
+ return (1);
+}
+
+=head1 GetVersionFile
+
+Takes base name of the file as argument, scans for <base name>-<version> named
+files and returns file name with closest version to the version of the RT DB.
+
+=cut
+
+sub GetVersionFile {
+ my $self = shift;
+ my $dbh = shift;
+ my $base_name = shift;
+
+ my $db_version = ref $self
+ ? $self->DatabaseVersion
+ : do {
+ my $tmp = RT::Handle->new;
+ $tmp->dbh($dbh);
+ $tmp->DatabaseVersion;
+ };
+
+ require File::Glob;
+ my @files = File::Glob::bsd_glob("$base_name*");
+ return '' unless @files;
+
+ my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
+ my $version;
+ foreach ( reverse sort cmp_version keys %version ) {
+ if ( cmp_version( $db_version, $_ ) >= 0 ) {
+ $version = $_;
+ last;
+ }
+ }
+
+ return defined $version? $version{ $version } : undef;
+}
+
+sub cmp_version($$) {
+ my ($a, $b) = (@_);
+ $b =~ s/HEAD$/9999/;
+ my @a = split /[^0-9]+/, $a;
+ my @b = split /[^0-9]+/, $b;
+ for ( my $i = 0; $i < @a; $i++ ) {
+ return 1 unless defined $b[$i];
+ return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
+ }
+ return 0 if @a == @b;
+ return -1;
+}
+
+
+=head2 InsertInitialData
+
+Inserts system objects into RT's DB, like system user or 'nobody',
+internal groups and other records required. However, this method
+doesn't insert any real users like 'root' and you have to use
+InsertData or another way to do that.
+
+Takes no arguments. Returns status and message tuple.
+
+It's safe to call this method even if those objects already exist.
+
+=cut
+
+sub InsertInitialData {
+ my $self = shift;
+
+ my @warns;
+
+ # create RT_System user and grant him rights
+ {
+ require RT::CurrentUser;
+
+ my $test_user = RT::User->new( new RT::CurrentUser );
+ $test_user->Load('RT_System');
+ if ( $test_user->id ) {
+ push @warns, "Found system user in the DB.";
+ }
+ else {
+ my $user = RT::User->new( new RT::CurrentUser );
+ my ( $val, $msg ) = $user->_BootstrapCreate(
+ Name => 'RT_System',
+ RealName => 'The RT System itself',
+ Comments => 'Do not delete or modify this user. '
+ . 'It is integral to RT\'s internal database structures',
+ Creator => '1',
+ LastUpdatedBy => '1',
+ );
+ return ($val, $msg) unless $val;
+ }
+ DBIx::SearchBuilder::Record::Cachable->FlushCache;
+ }
+
+ # init RT::SystemUser and RT::System objects
+ RT::InitSystemObjects();
+ unless ( $RT::SystemUser->id ) {
+ return (0, "Couldn't load system user");
+ }
+
+ # grant SuperUser right to system user
+ {
+ my $test_ace = RT::ACE->new( $RT::SystemUser );
+ $test_ace->LoadByCols(
+ PrincipalId => ACLEquivGroupId( $RT::SystemUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => 1,
+ );
+ if ( $test_ace->id ) {
+ push @warns, "System user has global SuperUser right.";
+ } else {
+ my $ace = RT::ACE->new( $RT::SystemUser );
+ my ( $val, $msg ) = $ace->_BootstrapCreate(
+ PrincipalId => ACLEquivGroupId( $RT::SystemUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => 1,
+ );
+ return ($val, $msg) unless $val;
+ }
+ DBIx::SearchBuilder::Record::Cachable->FlushCache;
+ }
+
+ # system groups
+ # $self->loc('Everyone'); # For the string extractor to get a string to localize
+ # $self->loc('Privileged'); # For the string extractor to get a string to localize
+ # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
+ foreach my $name (qw(Everyone Privileged Unprivileged)) {
+ my $group = RT::Group->new( $RT::SystemUser );
+ $group->LoadSystemInternalGroup( $name );
+ if ( $group->id ) {
+ push @warns, "System group '$name' already exists.";
+ next;
+ }
+
+ $group = RT::Group->new( $RT::SystemUser );
+ my ( $val, $msg ) = $group->_Create(
+ Type => $name,
+ Domain => 'SystemInternal',
+ Description => 'Pseudogroup for internal use', # loc
+ Name => '',
+ Instance => '',
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ # nobody
+ {
+ my $user = RT::User->new( $RT::SystemUser );
+ $user->Load('Nobody');
+ if ( $user->id ) {
+ push @warns, "Found 'Nobody' user in the DB.";
+ }
+ else {
+ my ( $val, $msg ) = $user->Create(
+ Name => 'Nobody',
+ RealName => 'Nobody in particular',
+ Comments => 'Do not delete or modify this user. It is integral '
+ .'to RT\'s internal data structures',
+ Privileged => 0,
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
+ push @warns, "User 'Nobody' has global OwnTicket right.";
+ } else {
+ my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
+ Right => 'OwnTicket',
+ Object => $RT::System,
+ );
+ return ($val, $msg) unless $val;
+ }
+ }
+
+ # rerun to get init Nobody as well
+ RT::InitSystemObjects();
+
+ # system role groups
+ foreach my $name (qw(Owner Requestor Cc AdminCc)) {
+ my $group = RT::Group->new( $RT::SystemUser );
+ $group->LoadSystemRoleGroup( $name );
+ if ( $group->id ) {
+ push @warns, "System role '$name' already exists.";
+ next;
+ }
+
+ $group = RT::Group->new( $RT::SystemUser );
+ my ( $val, $msg ) = $group->_Create(
+ Type => $name,
+ Domain => 'RT::System-Role',
+ Description => 'SystemRolegroup for internal use', # loc
+ Name => '',
+ Instance => '',
+ );
+ return ($val, $msg) unless $val;
+ }
+
+ push @warns, "You appear to have a functional RT database."
+ if @warns;
+
+ return (1, join "\n", @warns);
+}
+
+=head2 InsertData
+
+Load some sort of data into the database, takes path to a file.
+
+=cut
+
+sub InsertData {
+ my $self = shift;
+ my $datafile = shift;
+
+ # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+ our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+ @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+ local (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
+ @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
+
+ local $@;
+ $RT::Logger->debug("Going to load '$datafile' data file");
+ eval { require $datafile }
+ or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
+
+ if ( @Initial ) {
+ $RT::Logger->debug("Running initial actions...");
+ foreach ( @Initial ) {
+ local $@;
+ eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
+ }
+ $RT::Logger->debug("Done.");
+ }
+ if ( @Groups ) {
+ $RT::Logger->debug("Creating groups...");
+ foreach my $item (@Groups) {
+ my $new_entry = RT::Group->new( $RT::SystemUser );
+ my $member_of = delete $item->{'MemberOf'};
+ my ( $return, $msg ) = $new_entry->_Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ next;
+ } else {
+ $RT::Logger->debug($return .".");
+ }
+ if ( $member_of ) {
+ $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
+ foreach( @$member_of ) {
+ my $parent = RT::Group->new($RT::SystemUser);
+ if ( ref $_ eq 'HASH' ) {
+ $parent->LoadByCols( %$_ );
+ }
+ elsif ( !ref $_ ) {
+ $parent->LoadUserDefinedGroup( $_ );
+ }
+ else {
+ $RT::Logger->error(
+ "(Error: wrong format of MemberOf field."
+ ." Should be name of user defined group or"
+ ." hash reference with 'column => value' pairs."
+ ." Use array reference to add to multiple groups)"
+ );
+ next;
+ }
+ unless ( $parent->Id ) {
+ $RT::Logger->error("(Error: couldn't load group to add member)");
+ next;
+ }
+ my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Users ) {
+ $RT::Logger->debug("Creating users...");
+ foreach my $item (@Users) {
+ my $new_entry = new RT::User( $RT::SystemUser );
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Queues ) {
+ $RT::Logger->debug("Creating queues...");
+ for my $item (@Queues) {
+ my $new_entry = new RT::Queue($RT::SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ } else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @CustomFields ) {
+ $RT::Logger->debug("Creating custom fields...");
+ for my $item ( @CustomFields ) {
+ my $new_entry = new RT::CustomField( $RT::SystemUser );
+ my $values = delete $item->{'Values'};
+
+ my @queues;
+ # if ref then it's list of queues, so we do things ourself
+ if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
+ $item->{'LookupType'} = 'RT::Queue-RT::Ticket';
+ @queues = @{ delete $item->{'Queue'} };
+ }
+
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless( $return ) {
+ $RT::Logger->error( $msg );
+ next;
+ }
+
+ foreach my $value ( @{$values} ) {
+ my ( $return, $msg ) = $new_entry->AddValue(%$value);
+ $RT::Logger->error( $msg ) unless $return;
+ }
+
+ # apply by default
+ if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
+ my $ocf = RT::ObjectCustomField->new($RT::SystemUser);
+ $ocf->Create( CustomField => $new_entry->Id );
+ }
+
+ for my $q (@queues) {
+ my $q_obj = RT::Queue->new($RT::SystemUser);
+ $q_obj->Load($q);
+ unless ( $q_obj->Id ) {
+ $RT::Logger->error("Could not find queue ". $q );
+ next;
+ }
+ my $OCF = RT::ObjectCustomField->new($RT::SystemUser);
+ ( $return, $msg ) = $OCF->Create(
+ CustomField => $new_entry->Id,
+ ObjectId => $q_obj->Id,
+ );
+ $RT::Logger->error( $msg ) unless $return and $OCF->Id;
+ }
+ }
+
+ $RT::Logger->debug("done.");
+ }
+ if ( @ACL ) {
+ $RT::Logger->debug("Creating ACL...");
+ for my $item (@ACL) {
+
+ my ($princ, $object);
+
+ # Global rights or Queue rights?
+ if ( $item->{'CF'} ) {
+ $object = RT::CustomField->new( $RT::SystemUser );
+ my @columns = ( Name => $item->{'CF'} );
+ push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
+ $object->LoadByName( @columns );
+ } elsif ( $item->{'Queue'} ) {
+ $object = RT::Queue->new($RT::SystemUser);
+ $object->Load( $item->{'Queue'} );
+ } else {
+ $object = $RT::System;
+ }
+
+ $RT::Logger->error("Couldn't load object") and next unless $object and $object->Id;
+
+ # Group rights or user rights?
+ if ( $item->{'GroupDomain'} ) {
+ $princ = RT::Group->new($RT::SystemUser);
+ if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
+ $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+ } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
+ $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+ } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
+ $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
+ } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+ $item->{'Queue'} )
+ {
+ $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
+ Queue => $object->id);
+ } else {
+ $princ->Load( $item->{'GroupId'} );
+ }
+ } else {
+ $princ = RT::User->new($RT::SystemUser);
+ $princ->Load( $item->{'UserId'} );
+ }
+
+ # Grant it
+ my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+ Right => $item->{'Right'},
+ Object => $object
+ );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @ScripActions ) {
+ $RT::Logger->debug("Creating ScripActions...");
+
+ for my $item (@ScripActions) {
+ my $new_entry = RT::ScripAction->new($RT::SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @ScripConditions ) {
+ $RT::Logger->debug("Creating ScripConditions...");
+
+ for my $item (@ScripConditions) {
+ my $new_entry = RT::ScripCondition->new($RT::SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+
+ $RT::Logger->debug("done.");
+ }
+
+ if ( @Templates ) {
+ $RT::Logger->debug("Creating templates...");
+
+ for my $item (@Templates) {
+ my $new_entry = new RT::Template($RT::SystemUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Scrips ) {
+ $RT::Logger->debug("Creating scrips...");
+
+ for my $item (@Scrips) {
+ my $new_entry = new RT::Scrip($RT::SystemUser);
+
+ my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
+ push @queues, 0 unless @queues; # add global queue at least
+
+ foreach my $q ( @queues ) {
+ my ( $return, $msg ) = $new_entry->Create( %$item, Queue => $q );
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Attributes ) {
+ $RT::Logger->debug("Creating predefined searches...");
+ my $sys = RT::System->new($RT::SystemUser);
+
+ for my $item (@Attributes) {
+ my $obj = delete $item->{Object}; # XXX: make this something loadable
+ $obj ||= $sys;
+ my ( $return, $msg ) = $obj->AddAttribute (%$item);
+ unless ( $return ) {
+ $RT::Logger->error( $msg );
+ }
+ else {
+ $RT::Logger->debug( $return ."." );
+ }
+ }
+ $RT::Logger->debug("done.");
+ }
+ if ( @Final ) {
+ $RT::Logger->debug("Running final actions...");
+ for ( @Final ) {
+ local $@;
+ eval { $_->(); };
+ $RT::Logger->error( "Failed to run one of final actions: $@" )
+ if $@;
+ }
+ $RT::Logger->debug("done.");
+ }
+
+ my $db_type = RT->Config->Get('DatabaseType');
+ $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+
+ $RT::Logger->debug("Done setting up database content.");
+
+# TODO is it ok to return 1 here? If so, the previous codes in this sub
+# should return (0, $msg) if error happens instead of just warning.
+# anyway, we need to return something here to tell if everything is ok
+ return( 1, 'Done inserting data' );