summaryrefslogtreecommitdiff
path: root/rt/sbin
diff options
context:
space:
mode:
Diffstat (limited to 'rt/sbin')
-rw-r--r--rt/sbin/extract-message-catalog274
-rw-r--r--rt/sbin/extract_pod_tests159
-rw-r--r--rt/sbin/factory515
-rw-r--r--rt/sbin/license_tag243
-rw-r--r--rt/sbin/regression_harness56
-rwxr-xr-xrt/sbin/rt-dump-database.in173
-rw-r--r--rt/sbin/rt-setup-database.in712
-rw-r--r--rt/sbin/rt-test-dependencies.in479
8 files changed, 2611 insertions, 0 deletions
diff --git a/rt/sbin/extract-message-catalog b/rt/sbin/extract-message-catalog
new file mode 100644
index 000000000..34d44ed66
--- /dev/null
+++ b/rt/sbin/extract-message-catalog
@@ -0,0 +1,274 @@
+#!/usr/bin/perl -w
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+# Portions Copyright 2002 Autrijus Tang <autrijus@autrijus.org>
+
+use strict;
+
+use File::Find;
+use File::Copy;
+use Regexp::Common;
+use Carp;
+
+use vars qw($DEBUG $FILECAT);
+
+$DEBUG = 1;
+
+@ARGV = <lib/RT/I18N/*.po> unless @ARGV;
+
+$FILECAT = {};
+
+# extract all strings and stuff them into $FILECAT
+File::Find::find( { wanted => \&extract_strings_from_code, follow => 1 }, '.' );
+
+# ensure proper escaping and [_1] => %1 transformation
+foreach my $str ( sort keys %{$FILECAT} ) {
+ my $entry = $FILECAT->{$str};
+ my $oldstr = $str;
+
+ $str =~ s/\\/\\\\/g;
+ $str =~ s/\"/\\"/g;
+ $str =~ s/((?<!~)(?:~~)*)\[_(\d+)\]/$1%$2/g;
+ $str =~ s/((?<!~)(?:~~)*)\[([A-Za-z#*]\w*),([^\]]+)\]/"$1%$2(".escape($3).")"/eg;
+ $str =~ s/~([\[\]])/$1/g;
+
+ delete $FILECAT->{$oldstr};
+ $FILECAT->{$str} = $entry;
+}
+
+# update all language dictionaries
+foreach my $dict (@ARGV) {
+ $dict = "lib/RT/I18N/$dict.po" unless -f $dict or $dict =~ m!/!;
+
+ my $lang = $dict;
+ $lang =~ s|.*/||;
+ $lang =~ s|\.po$||;
+
+ update($lang, $dict);
+}
+
+
+# {{{ pull strings out of the code.
+
+sub extract_strings_from_code {
+ my $file = $_;
+
+ local $/;
+ return if ( -d $_ );
+ return if ( $File::Find::dir =~ 'lib/blib|lib/t/autogen|var|m4|local|\.svn' );
+ return if ( /\.po$|\.bak$|~|,D|,B$|extract-message-catalog$/ );
+ return if ( /^[\.#]/ );
+ return if ( -f "$_.in" );
+
+ print "Looking at $File::Find::name\n";
+ my $filename = $File::Find::name;
+ $filename =~ s'^\./'';
+ $filename =~ s'\.in$'';
+
+ unless (open _, $file) {
+ print "Cannot open $file for reading ($!), skipping.\n";
+ return;
+ }
+
+ $_ = <_>;
+
+ # Mason filter: <&|/l>...</&>
+ my $line = 1;
+ while (m!\G.*?<&\|/l(.*?)&>(.*?)</&>!sg) {
+ my ( $vars, $str ) = ( $1, $2 );
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $str =~ s/\\'/\'/g;
+ #print "STR IS $str\n";
+ push @{ $FILECAT->{$str} }, [ $filename, $line, $vars ];
+ }
+
+ # Localization function: loc(...)
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?\bloc$RE{balanced}{-parens=>'()'}{-keep}/sg) {
+ my $match = $1;
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+
+ my ( $vars, $str );
+ if ( $match =~
+ /\(\s*($RE{delimited}{-delim=>q{'"}}{-keep})(.*?)\s*\)$/ ) {
+
+ $str = substr( $1, 1, -1 ); # $str comes before $vars now
+ $vars = $9;
+ }
+ else {
+ next;
+ }
+
+ $vars =~ s/[\n\r]//g;
+ $str =~ s/\\'/\'/g;
+
+ push @{ $FILECAT->{$str} }, [ $filename, $line, $vars ];
+ }
+
+ # Comment-based mark: "..." # loc
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?($RE{delimited}{-delim=>q{'"}}{-keep})[\}\)\],]*\s*\#\s*loc\s*$/smg) {
+ my $str = substr($1, 1, -1);
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $str =~ s/\\'/\'/g;
+ push @{ $FILECAT->{$str} }, [ $filename, $line, '' ];
+ }
+
+ # Comment-based pair mark: "..." => "..." # loc_pair
+ $line = 1;
+ pos($_) = 0;
+ while (m/\G.*?(\w+)\s*=>\s*($RE{delimited}{-delim=>q{'"}}{-keep})[\}\)\],]*\s*\#\s*loc_pair\s*$/smg) {
+ my $key = $1;
+ my $val = substr($2, 1, -1);
+ $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext!
+ $key =~ s/\\'/\'/g;
+ $val =~ s/\\'/\'/g;
+ push @{ $FILECAT->{$key} }, [ $filename, $line, '' ];
+ push @{ $FILECAT->{$val} }, [ $filename, $line, '' ];
+ }
+
+ close (_);
+}
+# }}} extract from strings
+
+sub update {
+ my $lang = shift;
+ my $file = shift;
+ my ( %Lexicon, %Header);
+ my $out = '';
+
+ unless (!-e $file or -w $file) {
+ warn "Can't write to $lang, skipping...\n";
+ return;
+ }
+
+ print "Updating $lang...\n";
+
+ my @lines;
+ @lines = (<LEXICON>) if open (LEXICON, $file);
+ @lines = grep { !/^(#(:|\.)\s*|$)/ } @lines;
+ while (@lines) {
+ my $msghdr = "";
+ $msghdr .= shift @lines while ( $lines[0] && $lines[0] !~ /^msgid/ );
+
+ my $msgid = shift @lines;
+ my $msgstr = "";
+ $msgstr .= shift @lines while ( $lines[0] && $lines[0] =~ /^(msgstr|")/ );
+
+ last unless $msgid;
+
+ chomp $msgid;
+ chomp $msgstr;
+ $msgid =~ s/^msgid "(.*)"\s*?$/$1/ms or warn "$msgid in $file";
+ $msgstr =~ s/^msgstr "(.*)"\s*?$/$1/ms or warn "$msgstr in $file";
+
+ $Lexicon{$msgid} = $msgstr;
+ $Header{$msgid} = $msghdr;
+ }
+
+ my $is_english = ( $lang =~ /^en(?:[^A-Za-z]|$)/ );
+
+ foreach my $str ( sort keys %{$FILECAT} ) {
+ $Lexicon{$str} ||= '';;
+ }
+ foreach ( sort keys %Lexicon ) {
+ my $f = join ( ' ', sort map $_->[0].":".$_->[1], @{ $FILECAT->{$_} } );
+ my $nospace = $_;
+ $nospace =~ s/ +$//;
+
+ if ( !$Lexicon{$_} and $Lexicon{$nospace} ) {
+ $Lexicon{$_} =
+ $Lexicon{$nospace} . ( ' ' x ( length($_) - length($nospace) ) );
+ }
+
+ next if !length( $Lexicon{$_} ) and $is_english;
+
+ my %seen;
+ $out .= $Header{$_} if exists $Header{$_};
+
+
+
+ next if (!$f && $_ && !$Lexicon{$_});
+ if ( $f && $f !~ /^\s+$/ ) {
+
+ $out .= "#: $f\n";
+ }
+ elsif ($_) {
+ $out .= "#: NOT FOUND IN SOURCE\n";
+ }
+ foreach my $entry ( grep { $_->[2] } @{ $FILECAT->{$_} } ) {
+ my ( $file, $line, $var ) = @{$entry};
+ $var =~ s/^\s*,\s*//;
+ $var =~ s/\s*$//;
+ $out .= "#. ($var)\n" unless $seen{$var}++;
+ }
+ $out .= "msgid \"$_\"\nmsgstr \"$Lexicon{$_}\"\n\n";
+ }
+
+ open PO, ">$file" or die $!;
+ print PO $out;
+ close PO;
+
+ return 1;
+}
+
+sub escape {
+ my $text = shift;
+ $text =~ s/\b_(\d+)/%$1/;
+ return $text;
+}
+
+__END__
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
diff --git a/rt/sbin/extract_pod_tests b/rt/sbin/extract_pod_tests
new file mode 100644
index 000000000..616560b02
--- /dev/null
+++ b/rt/sbin/extract_pod_tests
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+use vars qw($VERSION);
+$VERSION = '0.06';
+
+use Pod::Tests;
+use Symbol;
+
+=pod
+
+=head1 NAME
+
+extract_pod_tests - RT-specific variant of pod2tests
+
+=head1 SYNOPSIS
+
+ pod2test [-Mmodule] [input [output]]
+
+=head1 DESCRIPTION
+
+B<pod2test> is a front-end for Test::Inline. It generates the
+"Bodies" of MakeMaker style .t testing files from embedded tests and
+code examples.
+
+If output is not specified, the resulting .t file will go to STDOUT.
+Otherwise, it will go to the given output file. If input is not
+given, it will draw from STDIN.
+
+If the given file contains no tests or code examples, no output will
+be given and no output file will be created.
+
+=cut
+
+my($infile, $outfile) = @ARGV;
+my($infh,$outfh);
+
+
+if( defined $infile ) {
+ $infh = gensym;
+ open($infh, $infile) or
+ die "Can't open the POD file $infile: $!";
+}
+else {
+ $infh = \*STDIN;
+}
+
+unless ($outfile) {
+ ( my $test = $infile ) =~ s/\.(pm|pod)$//;
+ $test =~ s/^lib\W//;
+ $test =~ s/\W/-/;
+ $test =~ s/\//__/g;
+
+ $outfile = "lib/t/autogen/autogen-$test.t";
+}
+
+
+my $p = Pod::Tests->new;
+$p->parse_fh($infh);
+
+# XXX Hack to put the filename into the #line directive
+$p->{file} = $infile || '';
+
+my @tests = $p->build_tests($p->tests);
+my @examples = $p->build_examples($p->examples);
+
+exit unless @tests or @examples;
+
+
+if( defined $outfile) {
+ $outfh = gensym;
+ open($outfh, ">$outfile") or
+ die "Can't open the test file $outfile: $!";
+}
+else {
+ $outfh = \*STDOUT;
+}
+
+
+print $outfh <<EOF;
+
+use Test::More qw/no_plan/;
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+EOF
+foreach my $test (@tests, @examples) {
+ print $outfh "$test\n";
+}
+
+print $outfh "1;\n";
+
+=pod
+
+=head1 BUGS and CAVEATS
+
+This is a very simple rough cut. It only does very rudimentary tests
+on the examples.
+
+=head1 AUTHOR
+
+
+
+Based on pod2tests by Michael G Schwern <schwern@pobox.com>
+
+=head1 SEE ALSO
+
+L<Test::Inline>
+
+=cut
+
+1;
diff --git a/rt/sbin/factory b/rt/sbin/factory
new file mode 100644
index 000000000..f72a29675
--- /dev/null
+++ b/rt/sbin/factory
@@ -0,0 +1,515 @@
+#!/usr/bin/perl -w
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+use DBI;
+
+my $database = shift;
+my $namespace = shift;
+
+my $CollectionBaseclass = 'RT::SearchBuilder';
+my $RecordBaseclass = 'RT::Record';
+
+my $driver = 'mysql';
+my $hostname = 'localhost';
+my $user = 'root';
+my $password = '';
+
+
+my $LicenseBlock = << '.';
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+.
+
+my $Attribution = << '.';
+# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
+# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
+#
+# !! DO NOT EDIT THIS FILE !!
+#
+
+use strict;
+.
+
+my $dsn = "DBI:$driver:database=$database;host=$hostname";
+
+my $dbh = DBI->connect( $dsn, $user, $password );
+
+#get all tables out of database
+my @tables = $dbh->tables();
+
+my ( %tablemap, $typemap, %modulemap );
+
+foreach my $table (@tables) {
+ $table =~ s/\`//g;
+ next if ($table eq 'sessions');
+ $table = ucfirst($table);
+ $table =~ s/field/Field/;
+ $table =~ s/group/Group/;
+ $table =~ s/custom/Custom/;
+ $table =~ s/member/Member/;
+ $table =~ s/Scripaction/ScripAction/g;
+ $table =~ s/condition/Condition/g;
+ $table =~ s/value/Value/;
+ $table =~ s/Acl/ACL/g;
+ $tablemap{$table} = $table;
+ $modulemap{$table} = $table;
+ if ( $table =~ /^(.*)s$/ ) {
+ $tablemap{$1} = $table;
+ $modulemap{$1} = $1;
+ }
+}
+$tablemap{'CreatedBy'} = 'User';
+$tablemap{'UpdatedBy'} = 'User';
+
+my %typemap;
+$typemap{'id'} = 'ro';
+$typemap{'Creator'} = 'auto';
+$typemap{'Created'} = 'auto';
+$typemap{'Updated'} = 'auto';
+$typemap{'UpdatedBy'} = 'auto';
+$typemap{'LastUpdated'} = 'auto';
+$typemap{'LastUpdatedBy'} = 'auto';
+
+foreach my $table (@tables) {
+ next if ($table eq 'sessions');
+ my $tablesingle = $table;
+ $tablesingle =~ s/s$//;
+ my $tableplural = $tablesingle . "s";
+
+ if ( $tablesingle eq 'ACL' ) {
+ $tablesingle = "ACE";
+ $tableplural = "ACL";
+ }
+
+ my %requirements;
+
+ my $CollectionClassName = $namespace . "::" . $tableplural;
+ my $RecordClassName = $namespace . "::" . $tablesingle;
+
+ my $path = $namespace;
+ $path =~ s/::/\//g;
+
+ my $RecordClassPath = $path . "/" . $tablesingle . ".pm";
+ my $CollectionClassPath = $path . "/" . $tableplural . ".pm";
+
+ #create a collection class
+ my $CreateInParams;
+ my $CreateOutParams;
+ my $ClassAccessible = "";
+ my $FieldsPod = "";
+ my $CreatePod = "";
+ my $RecordInit = "";
+ my %fields;
+
+
+ my $introspection = $dbh->prepare("SELECT * from $table where id is null");
+ $introspection->execute();
+ my @names =@{ $introspection->{'NAME'}};
+ my @types = @{$introspection->{'TYPE'}};
+ my @is_blob = @{$introspection->{'mysql_is_blob'}};
+ my @is_num = @{$introspection->{'mysql_is_num'}};
+
+ my %blobness = ();
+ my %sqltypes = ();
+ my %numeric = ();
+ foreach my $name (@names) {
+ $sqltypes{$name} = shift @types;
+ $blobness{$name} = (shift @is_blob || "0");
+ $numeric{$name} = (shift @is_num || "0");
+ }
+
+
+ my $sth = $dbh->prepare("DESCRIBE $table");
+ $sth->execute;
+
+ while ( my $row = $sth->fetchrow_hashref() ) {
+ my $field = $row->{'Field'};
+ my $type = $row->{'Type'};
+ my $default = $row->{'Default'};
+ my $length = 0;
+ if ($type =~ /^(?:.*?)\((\d+)\)$/) {
+ $length = $1;
+ }
+ $fields{$field} = 1;
+
+ #generate the 'accessible' datastructure
+
+ no warnings 'uninitialized';
+
+ if ( $typemap{$field} eq 'auto' ) {
+ $ClassAccessible .= " $field =>
+ {read => 1, auto => 1,";
+ }
+ elsif ( $typemap{$field} eq 'ro' ) {
+ $ClassAccessible .= " $field =>
+ {read => 1,";
+ }
+ else {
+ $ClassAccessible .= " $field =>
+ {read => 1, write => 1,";
+
+ }
+ $ClassAccessible .= " sql_type => $sqltypes{$field}, length => $length, is_blob => $blobness{$field}, is_numeric => $numeric{$field}, ";
+ $ClassAccessible .= " type => '$type', default => '$default'},\n";
+
+ #generate pod for the accessible fields
+ $FieldsPod .= "
+=head2 $field
+
+Returns the current value of $field.
+(In the database, $field is stored as $type.)
+
+";
+
+ unless ( exists $typemap{$field} && ( $typemap{$field} eq 'auto' || $typemap{$field} eq 'ro' )) {
+ $FieldsPod .= "
+
+=head2 Set$field VALUE
+
+
+Set $field to VALUE.
+Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
+(In the database, $field will be stored as a $type.)
+
+";
+ }
+
+ $FieldsPod .= "
+=cut
+
+";
+
+ if ( $modulemap{$field} ) {
+ $FieldsPod .= "
+=head2 ${field}Obj
+
+Returns the $modulemap{$field} Object which has the id returned by $field
+
+
+=cut
+
+sub ${field}Obj {
+ my \$self = shift;
+ my \$$field = ${namespace}::$modulemap{$field}->new(\$self->CurrentUser);
+ \$$field->Load(\$self->__Value('$field'));
+ return(\$$field);
+}
+";
+ $requirements{ $tablemap{$field} } =
+ "use ${namespace}::$modulemap{$field};";
+
+ }
+
+ unless ( $typemap{$field} eq 'auto' || $field eq 'id' ) {
+
+ #generate create statement
+ $CreateInParams .= " $field => '$default',\n";
+ $CreateOutParams .=
+ " $field => \$args{'$field'},\n";
+
+ #gerenate pod for the create statement
+ $CreatePod .= " $type '$field'";
+ $CreatePod .= " defaults to '$default'" if ($default);
+ $CreatePod .= ".\n";
+
+ }
+
+ }
+
+ my $Create = "
+sub Create {
+ my \$self = shift;
+ my \%args = (
+$CreateInParams
+ \@_);
+ \$self->SUPER::Create(
+$CreateOutParams);
+
+}
+";
+ $CreatePod .= "\n=cut\n\n";
+
+ my $CollectionClass = $LicenseBlock . $Attribution .
+
+ "
+
+=head1 NAME
+
+ $CollectionClassName -- Class Description
+
+=head1 SYNOPSIS
+
+ use $CollectionClassName
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+=cut
+
+package $CollectionClassName;
+
+use $CollectionBaseclass;
+use $RecordClassName;
+
+use vars qw( \@ISA );
+\@ISA= qw($CollectionBaseclass);
+
+
+sub _Init {
+ my \$self = shift;
+ \$self->{'table'} = '$table';
+ \$self->{'primary_key'} = 'id';
+
+";
+
+ if ( $fields{'SortOrder'} ) {
+
+ $CollectionClass .= "
+
+ # By default, order by SortOrder
+ \$self->OrderByCols(
+ { ALIAS => 'main',
+ FIELD => 'SortOrder',
+ ORDER => 'ASC' },
+ { ALIAS => 'main',
+ FIELD => 'id',
+ ORDER => 'ASC' },
+ );
+";
+ }
+ $CollectionClass .= "
+ return ( \$self->SUPER::_Init(\@_) );
+}
+
+
+=head2 NewItem
+
+Returns an empty new $RecordClassName item
+
+=cut
+
+sub NewItem {
+ my \$self = shift;
+ return($RecordClassName->new(\$self->CurrentUser));
+}
+" . MagicImport($CollectionClassName);
+
+ my $RecordClassHeader = $Attribution . "
+
+=head1 NAME
+
+$RecordClassName
+
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=cut
+
+package $RecordClassName;
+use $RecordBaseclass;
+";
+
+ foreach my $key ( keys %requirements ) {
+ $RecordClassHeader .= $requirements{$key} . "\n";
+ }
+ $RecordClassHeader .= "
+
+use vars qw( \@ISA );
+\@ISA= qw( $RecordBaseclass );
+
+sub _Init {
+ my \$self = shift;
+
+ \$self->Table('$table');
+ \$self->SUPER::_Init(\@_);
+}
+
+";
+
+ my $RecordClass = $LicenseBlock . $RecordClassHeader . "
+
+$RecordInit
+
+=head2 Create PARAMHASH
+
+Create takes a hash of values and creates a row in the database:
+
+$CreatePod
+
+$Create
+
+$FieldsPod
+
+sub _CoreAccessible {
+ {
+
+$ClassAccessible
+ }
+};
+
+" . MagicImport($RecordClassName);
+
+ print "About to make $RecordClassPath, $CollectionClassPath\n";
+ `mkdir -p $path`;
+
+ open( RECORD, ">$RecordClassPath" );
+ print RECORD $RecordClass;
+ close(RECORD);
+
+ open( COL, ">$CollectionClassPath" );
+ print COL $CollectionClass;
+ close(COL);
+
+}
+
+sub MagicImport {
+ my $class = shift;
+
+ #if (exists \$warnings::{unimport}) {
+ # no warnings qw(redefine);
+
+ my $path = $class;
+ $path =~ s#::#/#gi;
+
+
+ my $content = "
+ eval \"require @{[$class]}_Overlay\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Overlay.pm}) {
+ die \$@;
+ };
+
+ eval \"require @{[$class]}_Vendor\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Vendor.pm}) {
+ die \$@;
+ };
+
+ eval \"require @{[$class]}_Local\";
+ if (\$@ && \$@ !~ qr{^Can't locate ".$path."_Local.pm}) {
+ die \$@;
+ };
+
+
+
+
+=head1 SEE ALSO
+
+This class allows \"overlay\" methods to be placed
+into the following files _Overlay is for a System overlay by the original author,
+_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
+
+These overlay files can contain new subs or subs to replace existing subs in this module.
+
+Each of these files should begin with the line
+
+ no warnings qw(redefine);
+
+so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
+
+@{[$class]}_Overlay, @{[$class]}_Vendor, @{[$class]}_Local
+
+=cut
+
+
+1;
+";
+
+ return $content;
+}
+
+# }}}
+
diff --git a/rt/sbin/license_tag b/rt/sbin/license_tag
new file mode 100644
index 000000000..ed1d4eb64
--- /dev/null
+++ b/rt/sbin/license_tag
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+
+
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+my $LICENSE = <<'EOL';
+
+COPYRIGHT:
+
+This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+ <jesse@bestpractical.com>
+
+(Except where explicitly superseded by other copyright notices)
+
+
+LICENSE:
+
+This work is made available to you under the terms of Version 2 of
+the GNU General Public License. A copy of that license should have
+been provided with this software, but in any event can be snarfed
+from www.gnu.org.
+
+This work is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 or visit their web page on the internet at
+http://www.gnu.org/copyleft/gpl.html.
+
+
+CONTRIBUTION SUBMISSION POLICY:
+
+(The following paragraph is not intended to limit the rights granted
+to you to modify and distribute this software under the terms of
+the GNU General Public License and is only of importance to you if
+you choose to contribute your changes and enhancements to the
+community by submitting them to Best Practical Solutions, LLC.)
+
+By intentionally submitting any modifications, corrections or
+derivatives to this work, or any other work intended for use with
+Request Tracker, to Best Practical Solutions, LLC, you confirm that
+you are the copyright holder for those contributions and you grant
+Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+royalty-free, perpetual, license to use, copy, create derivative
+works based on those contributions, and sublicense and distribute
+those contributions and any derivatives thereof.
+
+EOL
+
+use File::Find;
+
+my @MAKE = qw(Makefile);
+
+File::Find::find({ no_chdir => 1, wanted => \&tag_pm}, 'lib');
+File::Find::find({ no_chdir => 1, wanted => \&tag_mason}, 'html');
+File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'sbin');
+File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'bin');
+tag_makefile ('Makefile.in');
+tag_makefile ('README');
+
+
+sub tag_mason {
+ my $pm = $_;
+ next unless (-f $pm);
+ next if ($pm =~ /images/);
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/%# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^%# BEGIN BPS TAGGED BLOCK {{{/ms) {
+ print "has license section";
+ $file =~ s/^%# BEGIN BPS TAGGED BLOCK {{{(.*?)%# END BPS TAGGED BLOCK }}}/%# BEGIN BPS TAGGED BLOCK {{{\n$pmlic%# END BPS TAGGED BLOCK }}}/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="%# BEGIN BPS TAGGED BLOCK {{{\n$pmlic%# END BPS TAGGED BLOCK }}}\n". $file;
+ }
+ $file =~ s/%# END BPS TAGGED BLOCK }}}(\n+)/%# END BPS TAGGED BLOCK }}}\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_makefile {
+ my $pm = shift;
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN BPS TAGGED BLOCK {{{/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN BPS TAGGED BLOCK {{{(.*?)# END BPS TAGGED BLOCK }}}/# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}\n". $file;
+ }
+ $file =~ s/# END BPS TAGGED BLOCK }}}(\n+)/# END BPS TAGGED BLOCK }}}\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_pm {
+ my $pm = $_;
+ next unless $pm =~ /\.pm/s;
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /mg;
+
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN BPS TAGGED BLOCK {{{/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN BPS TAGGED BLOCK {{{(.*?)# END BPS TAGGED BLOCK }}}/# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}/ms;
+
+
+ } else {
+ print "no license section";
+ $file ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}\n". $file;
+ }
+ $file =~ s/# END BPS TAGGED BLOCK }}}(\n+)/# END BPS TAGGED BLOCK }}}\n/mg;
+ print "\n";
+
+
+
+
+ open (FILE, ">$pm") || die "couldn't write new file $pm";
+ print FILE $file;
+ close FILE;
+
+}
+
+
+sub tag_script {
+ my $pm = $_;
+ return unless (-f $pm);
+ open(FILE,"<$pm") || die "Failed to open $pm";
+ my $file = (join "", <FILE>);
+ close (FILE);
+ my $pmlic = $LICENSE;
+ $pmlic =~ s/^/# /msg;
+
+ print "$pm - ";
+ if ($file =~ /^# BEGIN BPS TAGGED BLOCK {{{/ms) {
+ print "has license section";
+ $file =~ s/^# BEGIN BPS TAGGED BLOCK {{{(.*?)# END BPS TAGGED BLOCK }}}/# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}/ms;
+
+
+ } else {
+ print "no license section";
+ if ($file =~ /^(#!.*?)\n/) {
+
+ my $lic ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic# END BPS TAGGED BLOCK }}}\n";
+ $file =~ s/^(#!.*?)\n/$1\n$lic/;
+
+ }
+ }
+ $file =~ s/# END BPS TAGGED BLOCK }}}(\n+)/# END BPS TAGGED BLOCK }}}\n/mg;
+ print "\n";
+
+
+ open (FILE, ">$pm") || die "couldn't write new file";
+ print FILE $file;
+ close FILE;
+
+}
+
diff --git a/rt/sbin/regression_harness b/rt/sbin/regression_harness
new file mode 100644
index 000000000..1e97a2978
--- /dev/null
+++ b/rt/sbin/regression_harness
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+open (FH,"make regression|");
+
+my $skip_frontmatter = 1;
+while (<FH>) {
+ next if /^ok/;
+ $skip_frontmatter = 0 if (/autogen/);
+ print $_ unless ($skip_frontmatter);
+}
diff --git a/rt/sbin/rt-dump-database.in b/rt/sbin/rt-dump-database.in
new file mode 100755
index 000000000..734e00b77
--- /dev/null
+++ b/rt/sbin/rt-dump-database.in
@@ -0,0 +1,173 @@
+#!@PERL@ -w
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+use RT;
+use XML::Simple;
+
+RT::LoadConfig();
+RT::Init();
+
+my $LocalOnly = @ARGV ? shift(@ARGV) : 1;
+
+my %RV;
+my %Ignore = (
+ All => [qw(
+ id Created Creator LastUpdated LastUpdatedBy
+ )],
+ Templates => [qw(
+ TranslationOf
+ )],
+);
+
+my $SystemUserId = $RT::SystemUser->Id;
+my @classes = qw(
+ Users Groups Queues ScripActions ScripConditions
+ Templates Scrips ACL CustomFields
+);
+foreach my $class (@classes) {
+ require "RT/$class.pm";
+ my $objects = "RT::$class"->new($RT::SystemUser);
+ $objects->{find_disabled_rows} = 1;
+ $objects->UnLimit;
+
+ if ($class eq 'CustomFields') {
+ $objects->OrderByCols(
+ { FIELD => 'LookupType' },
+ { FIELD => 'SortOrder' },
+ { FIELD => 'Id' },
+ );
+ }
+ else {
+ $objects->OrderBy( FIELD => 'Id' );
+ }
+
+ if ($LocalOnly) {
+ next if $class eq 'ACL'; # XXX - would go into infinite loop - XXX
+ $objects->Limit( FIELD => 'LastUpdatedBy', OPERATOR => '!=', VALUE => $SystemUserId )
+ unless $class eq 'Groups';
+ $objects->Limit( FIELD => 'Id', OPERATOR => '!=', VALUE => $SystemUserId )
+ if $class eq 'Users';
+ $objects->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'UserDefined' )
+ if $class eq 'Groups';
+ }
+
+ my %fields;
+ while (my $obj = $objects->Next) {
+ next if $obj->can('LastUpdatedBy') and $obj->LastUpdatedBy == $SystemUserId;
+
+ if (!%fields) {
+ %fields = map { $_ => 1 } keys %{$obj->_ClassAccessible};
+ delete @fields{
+ @{$Ignore{$class}||=[]},
+ @{$Ignore{All}||=[]},
+ };
+ }
+
+ my $rv;
+ # next if $obj-> # skip default names
+ foreach my $field (sort keys %fields) {
+ my $value = $obj->__Value($field);
+ $rv->{$field} = $value if length($value);
+ }
+ delete $rv->{Disabled} unless $rv->{Disabled};
+
+ foreach my $record (map { /ACL/ ? 'ACE' : substr($_, 0, -1) } @classes) {
+ foreach my $key (map "$record$_", ('', 'Id')) {
+ next unless exists $rv->{$key};
+ my $id = $rv->{$key} or next;
+ my $obj = "RT::$record"->new($RT::SystemUser);
+ $obj->LoadByCols( Id => $id ) or next;
+ $rv->{$key} = $obj->__Value('Name') || 0;
+ }
+ }
+
+ if ($class eq 'Users' and defined $obj->Privileged) {
+ $rv->{Privileged} = int($obj->Privileged);
+ }
+ elsif ($class eq 'CustomFields') {
+ my $values = $obj->Values;
+ while (my $value = $values->Next) {
+ push @{$rv->{Values}}, {
+ map { ($_ => $value->__Value($_)) } qw(
+ Name Description SortOrder
+ ),
+ };
+ }
+ }
+
+ if (eval { require RT::Attributes; 1 }) {
+ my $attributes = $obj->Attributes;
+ while (my $attribute = $attributes->Next) {
+ my $content = $attribute->Content;
+ $rv->{Attributes}{$attribute->Name} = $content if length($content);
+ }
+ }
+
+ push @{$RV{$class}}, $rv;
+ }
+}
+
+print(<< ".");
+no strict; use XML::Simple; *_ = XMLin(do { local \$/; readline(DATA) }, ForceArray => [qw(
+ @classes Values
+)], NoAttr => 1, SuppressEmpty => ''); *\$_ = (\$_{\$_} || []) for keys \%_; 1; # vim: ft=xml
+__DATA__
+.
+
+print XMLout(
+ { map { ($_ => ($RV{$_} || [])) } @classes },
+ RootName => 'InitialData',
+ NoAttr => 1,
+ SuppressEmpty => '',
+ XMLDecl => '<?xml version="1.0" encoding="UTF-8"?>',
+);
diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in
new file mode 100644
index 000000000..cf607e286
--- /dev/null
+++ b/rt/sbin/rt-setup-database.in
@@ -0,0 +1,712 @@
+#!@PERL@ -w
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
+use vars
+ qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips @Attributes);
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+#This drags in RT's config.pm
+# We do it in a begin block because RT::Handle needs to know the type to do its
+# inheritance
+use RT;
+use Carp;
+use RT::User;
+use RT::CurrentUser;
+use RT::Template;
+use RT::ScripAction;
+use RT::ACE;
+use RT::Group;
+use RT::User;
+use RT::Queue;
+use RT::ScripCondition;
+use RT::CustomField;
+use RT::Scrip;
+
+RT::LoadConfig();
+use Term::ReadKey;
+use Getopt::Long;
+
+my %args;
+
+GetOptions(
+ \%args,
+ 'prompt-for-dba-password', 'force', 'debug',
+ 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
+ 'datadir=s'
+);
+
+unless ( $args{'action'} ) {
+ help();
+ exit(-1);
+}
+
+$| = 1; #unbuffer that output.
+
+require RT::Handle;
+my $Handle = RT::Handle->new($RT::DatabaseType);
+$Handle->BuildDSN;
+my $dbh;
+
+if ( $args{'prompt-for-dba-password'} ) {
+ $args{'dba-password'} = get_dba_password();
+ chomp( $args{'dba-password'} );
+}
+
+if ( $args{'action'} eq 'init' ) {
+ $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ print "Now creating a database for RT.\n";
+ if ( $RT::DatabaseType ne 'Oracle' || $args{'dba'} ne $RT::DatabaseUser ) {
+ create_db();
+ } else {
+ print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
+ }
+
+ if ( $RT::DatabaseType eq "mysql" ) {
+ # Check which version we're running
+ my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/;
+ print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4;
+
+ # MySQL must have InnoDB support
+ my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value};
+ if ( $innodb eq "NO" ) {
+ die "RT requires that MySQL be compiled with InnoDB table support.\n".
+ "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n";
+ } elsif ( $innodb eq "DISABLED" ) {
+ die "RT requires that MySQL InnoDB table support be enabled.\n".
+ ($version < 4
+ ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n"
+ : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
+ }
+ }
+
+ # SQLite can't deal with the disconnect/reconnect
+ unless ( $RT::DatabaseType eq 'SQLite' ) {
+
+ $dbh->disconnect;
+
+ if ( $RT::DatabaseType eq "Oracle" ) {
+ $RT::DatabasePassword = $RT::DatabasePassword; #Warning avidance
+ $dbh = DBI->connect( $Handle->DSN, ${RT::DatabaseUser}, ${RT::DatabasePassword} ) || die $DBI::errstr;
+ } else {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
+ }
+ }
+ print "Now populating database schema.\n";
+ insert_schema();
+ print "Now inserting database ACLs\n";
+ insert_acl() unless $RT::DatabaseType eq 'Oracle';
+ print "Now inserting RT core system objects\n";
+ insert_initial_data();
+ print "Now inserting RT data\n";
+ insert_data( $RT::EtcPath . "/initialdata" );
+}
+elsif ( $args{'action'} eq 'drop' ) {
+ unless ( $dbh =
+ DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
+ {
+ warn $DBI::errstr;
+ warn "Database doesn't appear to exist. Aborting database drop.";
+ exit;
+ }
+ drop_db();
+}
+elsif ( $args{'action'} eq 'insert_initial' ) {
+ insert_initial_data();
+}
+elsif ( $args{'action'} eq 'insert' ) {
+ insert_data( $args{'datafile'} || ($args{'datadir'}."/content") );
+}
+elsif ( $args{'action'} eq 'acl' ) {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_acl($args{'datadir'});
+}
+elsif ( $args{'action'} eq 'schema' ) {
+ $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+ || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+ insert_schema($args{'datadir'});
+}
+else {
+ print STDERR "$0 called with an invalid --action parameter\n";
+ exit(-1);
+}
+
+# {{{ sub insert_schema
+sub insert_schema {
+ my $base_path = (shift || $RT::EtcPath);
+ my (@schema);
+ print "Creating database schema.\n";
+
+ my $schema_file = $base_path . "/schema." . $RT::DatabaseType;
+ if ( -f $schema_file ) {
+ open( SCHEMA, "<$schema_file" ) or die "Can't open $schema_file: $!";
+ my @lines = <SCHEMA>;
+
+ my $local_schema_file = $RT::LocalEtcPath . "/schema." . $RT::DatabaseType;
+ if (-f $local_schema_file) {
+ open( SCHEMA_LOCAL, "<$local_schema_file" )
+ or die "Can't open $local_schema_file: $!";
+ push @lines, ';;', <SCHEMA_LOCAL>;
+ }
+
+ my $statement = "";
+ foreach my $line (@lines) {
+ $line =~ s/\#.*//g;
+ $line =~ s/--.*//g;
+ $statement .= $line;
+ if ( $line =~ /;(\s*)$/ ) {
+ $statement =~ s/;(\s*)$//g;
+ push @schema, $statement;
+ $statement = "";
+ }
+ }
+
+ local $SIG{__WARN__} = sub {};
+ my $is_local = 0; # local/etc/schema needs to be nonfatal.
+ $dbh->begin_work or die $dbh->errstr;
+ foreach my $statement (@schema) {
+ if ( $statement =~ /^\s*;$/ ) { $is_local = 1; next; }
+
+ print STDERR "SQL: $statement\n" if defined $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute or $is_local ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+ $dbh->commit or die $dbh->errstr;
+ }
+ else {
+ die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
+ }
+ print "Done setting up database schema.\n";
+}
+
+# }}}
+
+# {{{ sub drop_db
+sub drop_db {
+ if ( $RT::DatabaseType eq 'Oracle' ) {
+ print <<END;
+
+To delete the tables and sequences of the RT Oracle database by running
+ \@etc/drop.Oracle
+through SQLPlus.
+
+END
+ return;
+ }
+ unless ( $args{'force'} ) {
+ print <<END;
+
+About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
+WARNING: This will erase all data in $RT::DatabaseName.
+
+END
+ exit unless _yesno();
+
+ }
+
+ print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
+
+ if ( $RT::DatabaseType eq 'SQLite' ) {
+ unlink $RT::DatabaseName or warn $!;
+ return;
+ }
+ $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
+}
+
+# }}}
+
+# {{{ sub create_db
+sub create_db {
+ print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
+ if ( $RT::DatabaseType eq 'SQLite' ) {
+ return;
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
+ if ( $DBI::errstr ) {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
+ }
+ }
+ elsif ( $RT::DatabaseType eq 'Oracle' ) {
+ insert_acl();
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ $ENV{DB_LOCALE} = 'en_us.utf8';
+ $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
+ }
+ else {
+ $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
+ }
+}
+
+# }}}
+
+sub get_dba_password {
+ print "In order to create or update your RT database,";
+ print "this script needs to connect to your "
+ . $RT::DatabaseType
+ . " instance on "
+ . $RT::DatabaseHost . " as "
+ . $args{'dba'} . ".\n";
+ print "Please specify that user's database password below. If the user has no database\n";
+ print "password, just press return.\n\n";
+ print "Password: ";
+ ReadMode('noecho');
+ my $password = ReadLine(0);
+ ReadMode('normal');
+ print "\n";
+ return ($password);
+}
+
+# {{{ sub _yesno
+sub _yesno {
+ print "Proceed [y/N]:";
+ my $x = scalar(<STDIN>);
+ $x =~ /^y/i;
+}
+
+# }}}
+
+# {{{ insert_acls
+sub insert_acl {
+ my $base_path = (shift || $RT::EtcPath);
+
+ if ( $RT::DatabaseType =~ /^oracle$/i ) {
+ do $base_path . "/acl.Oracle"
+ || die "Couldn't find ACLS for Oracle\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^pg$/i ) {
+ do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
+ do $base_path . "/acl.mysql"
+ || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
+ do $base_path . "/acl.Sybase"
+ || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^informix$/i ) {
+ do $base_path . "/acl.Informix"
+ || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
+ }
+ elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
+ return;
+ }
+ else {
+ die "Unknown RT database type";
+ }
+
+ my @acl = acl($dbh);
+ foreach my $statement (@acl) {
+ print STDERR $statement if $args{'debug'};
+ my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+ unless ( $sth->execute ) {
+ die "Problem with statement:\n $statement\n" . $sth->errstr;
+ }
+ }
+ print "Done setting up database ACLs.\n";
+}
+
+# }}}
+
+=head2 get_system_dsn
+
+Returns a dsn suitable for database creates and drops
+and user creates and drops
+
+=cut
+
+sub get_system_dsn {
+
+ my $dsn = $Handle->DSN;
+
+ #with mysql, you want to connect sans database to funge things
+ if ( $RT::DatabaseType eq 'mysql' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName//;
+
+ # with postgres, you want to connect to database1
+ }
+ elsif ( $RT::DatabaseType eq 'Pg' ) {
+ $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
+ }
+ elsif ( $RT::DatabaseType eq 'Informix' ) {
+ # with Informix, you want to connect sans database:
+ $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
+ }
+ return $dsn;
+}
+
+sub insert_initial_data {
+
+ RT::InitLogging();
+
+ #connect to the db, for actual RT work
+ require RT::Handle;
+ $RT::Handle = RT::Handle->new();
+ $RT::Handle->Connect();
+
+ #Put together a current user object so we can create a User object
+ my $CurrentUser = new RT::CurrentUser();
+
+ print "Checking for existing system user...";
+ my $test_user = RT::User->new($CurrentUser);
+ $test_user->Load('RT_System');
+ if ( $test_user->id ) {
+ print "found!\n\nYou appear to have a functional RT database.\n"
+ . "Exiting, so as not to clobber your existing data.\n";
+ exit(-1);
+
+ }
+ else {
+ print "not found. This appears to be a new installation.\n";
+ }
+
+ print "Creating system user...";
+ my $RT_System = new RT::User($CurrentUser);
+
+ my ( $val, $msg ) = $RT_System->_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',
+ );
+
+ unless ( $val ) {
+ print "$msg\n";
+ exit(-1);
+ }
+ print "done.\n";
+ $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
+
+}
+
+# load some sort of data into the database
+
+sub insert_data {
+ my $datafile = shift;
+
+ #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+ RT::Init;
+
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByName('RT_System');
+
+ if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
+
+ print "Creating Superuser ACL...";
+
+ my $superuser_ace = RT::ACE->new($CurrentUser);
+ $superuser_ace->_BootstrapCreate(
+ PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
+ PrincipalType => 'Group',
+ RightName => 'SuperUser',
+ ObjectType => 'RT::System',
+ ObjectId => '1' );
+
+ print "done.\n";
+ }
+
+ # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+ # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
+
+ require $datafile
+ || die "Couldn't find initial data for import\n" . $@;
+
+ if ( @Groups ) {
+ print "Creating groups...";
+ foreach $item (@Groups) {
+ my $new_entry = RT::Group->new($CurrentUser);
+ my ( $return, $msg ) = $new_entry->_Create(%$item);
+ print "(Error: $msg)" unless $return;
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if ( @Users ) {
+ print "Creating users...";
+ foreach $item (@Users) {
+ my $new_entry = new RT::User($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless $return;
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if ( @Queues ) {
+ print "Creating queues...";
+ for $item (@Queues) {
+ my $new_entry = new RT::Queue($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ print "(Error: $msg)" unless $return;
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if ( @ACL ) {
+ print "Creating ACL...";
+ for my $item (@ACL) {
+
+ my ($princ, $object);
+
+ # Global rights or Queue rights?
+ if ( $item->{'Queue'} ) {
+ $object = RT::Queue->new($CurrentUser);
+ $object->Load( $item->{'Queue'} );
+ } else {
+ $object = $RT::System;
+ }
+
+ # Group rights or user rights?
+ if ( $item->{'GroupDomain'} ) {
+ $princ = RT::Group->new($CurrentUser);
+ 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($CurrentUser);
+ $princ->Load( $item->{'UserId'} );
+ }
+
+ # Grant it
+ my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+ Right => $item->{'Right'},
+ Object => $object );
+
+ if ( $return ) {
+ print $return. ".";
+ }
+ else {
+ print $msg . ".";
+
+ }
+
+ }
+ print "done.\n";
+ }
+ if ( @CustomFields ) {
+ print "Creating custom fields...";
+ for $item (@CustomFields) {
+ my $new_entry = new RT::CustomField($CurrentUser);
+ my $values = $item->{'Values'};
+ delete $item->{'Values'};
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ unless( $return ) {
+ print "(Error: $msg)\n";
+ next;
+ }
+
+ foreach my $value ( @{$values} ) {
+ my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
+ print "(Error: $emsg)\n" unless $eval;
+ }
+
+ if ( $item->{LookupType} && !exists $item->{'Queue'} ) { # enable by default
+ my $ocf = RT::ObjectCustomField->new($CurrentUser);
+ $ocf->Create( CustomField => $new_entry->Id );
+ }
+
+ print "(Error: $msg)\n" unless $return;
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if ( @ScripActions ) {
+ print "Creating ScripActions...";
+
+ for $item (@ScripActions) {
+ my $new_entry = RT::ScripAction->new($CurrentUser);
+ my ($return,$msg) = $new_entry->Create(%$item);
+ unless ($return) {
+ print "(Error: $msg)\n";
+ next;
+ }
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if ( @ScripConditions ) {
+ print "Creating ScripConditions...";
+
+ for $item (@ScripConditions) {
+ my $new_entry = RT::ScripCondition->new($CurrentUser);
+ my ($return,$msg) = $new_entry->Create(%$item);
+ unless ($return) {
+ print "(Error: $msg)\n";
+ next;
+ }
+ print $return. ".";
+ }
+
+ print "done.\n";
+ }
+
+ if ( @Templates ) {
+ print "Creating templates...";
+
+ for $item (@Templates) {
+ my $new_entry = new RT::Template($CurrentUser);
+ my $return = $new_entry->Create(%$item);
+ print $return. ".";
+ }
+ print "done.\n";
+ }
+ if ( @Scrips ) {
+ print "Creating scrips...";
+
+ for $item (@Scrips) {
+ my $new_entry = new RT::Scrip($CurrentUser);
+ my ( $return, $msg ) = $new_entry->Create(%$item);
+ if ( $return ) {
+ print $return. ".";
+ }
+ else {
+ print "(Error: $msg)\n";
+ }
+ }
+ print "done.\n";
+ }
+ if ( @Attributes ) {
+ print "Creating predefined searches...";
+ my $sys = RT::System->new($CurrentUser);
+
+ for $item (@Attributes) {
+ my $obj = delete $item->{Object}; # XXX: make this something loadable
+ $obj ||= $sys;
+ my ( $return, $msg ) = $obj->AddAttribute (%$item);
+ if ( $return ) {
+ print $return. ".";
+ }
+ else {
+ print "(Error: $msg)\n";
+ }
+ }
+ print "done.\n";
+ }
+ $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
+ print "Done setting up database content.\n";
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+ my $username = shift;
+ my $user = RT::User->new($RT::SystemUser);
+ $user->Load($username);
+ my $equiv_group = RT::Group->new($RT::SystemUser);
+ $equiv_group->LoadACLEquivalenceGroup($user);
+ return ( $equiv_group->Id );
+}
+
+sub help {
+
+ print <<EOF;
+
+$0: Set up RT's database
+
+--action init Initialize the database
+ drop Drop the database.
+ This will ERASE ALL YOUR DATA
+ insert_initial
+ Insert RT's core system objects
+ insert Insert data into RT's database.
+ By default, will use RT's installation data.
+ To use a local or supplementary datafile, specify it
+ using the '--datafile' option below.
+
+ acl Initialize only the database ACLs
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+ schema Initialize only the database schema
+ To use a local or supplementary datafile, specify it
+ using the '--datadir' option below.
+
+--datafile /path/to/datafile
+--datadir /path/to/ Used to specify a path to find the local
+ database schema and acls to be installed.
+
+
+--dba dba's username
+--dba-password dba's password
+--prompt-for-dba-password Ask for the database administrator's password interactively
+
+
+EOF
+
+}
+
+1;
diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in
new file mode 100644
index 000000000..ea6b9d154
--- /dev/null
+++ b/rt/sbin/rt-test-dependencies.in
@@ -0,0 +1,479 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+#
+# This is just a basic script that checks to make sure that all
+# the modules needed by RT before you can install it.
+#
+
+use strict;
+no warnings qw(numeric redefine);
+use Getopt::Long;
+my %args;
+my %deps;
+GetOptions(
+ \%args, 'v|verbose',
+ 'install', 'with-MYSQL',
+ 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
+ 'with-ORACLE', 'with-FASTCGI',
+ 'with-SPEEDYCGI', 'with-MODPERL1',
+ 'with-MODPERL2', 'with-DEV',
+ 'with-STANDALONE',
+ 'download=s',
+ 'repository=s'
+);
+
+unless (keys %args) {
+ help();
+ exit(0);
+}
+# Set up defaults
+my %default = (
+ 'with-MASON' => 1,
+ 'with-CORE' => 1,
+ 'with-CLI' => 1,
+ 'with-MAILGATE' => 1,
+ 'with-DEV' => @RT_DEVEL_MODE@,
+ 'with-STANDALONE' => @RT_STANDALONE@,
+);
+$args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
+
+
+{
+ my $section;
+ my %always_show_sections = (
+ perl => 1,
+ users => 1,
+ );
+
+ sub section {
+ my $s = shift;
+ $section = $s;
+ print "$s:\n";
+ }
+
+ my $any_missing = 0;
+ sub found {
+ my $msg = shift;
+ my $test = shift;
+ my $extra = shift;
+
+ $any_missing = 1 unless $test;
+ if ($args{'v'} or not $test or $always_show_sections{$section}) {
+ print "\t$msg...";
+ print $test ? "found" : "MISSING";
+ print "\n";
+ }
+
+ print "\t\t$extra\n" if defined $extra;
+ }
+
+ sub conclude {
+ if ($any_missing) {
+ print "\nSOMETHING WAS MISSING!\n";
+ } else {
+ print "\nEverything was found.\n";
+ }
+ }
+}
+
+sub help {
+
+ print <<'.';
+
+By default, testdeps determine whether you have
+installed all the perl modules RT needs to run.
+
+ --install Install missing modules
+
+The following switches will tell the tool to check for specific dependencies
+
+ --with-mysql Database interface for MySQL
+ --with-postgresql Database interface for PostgreSQL
+ --with-sqlite Database interface and driver for SQLite (unsupported)
+ --with-oracle Database interface for oracle (unsupported)
+
+ --with-standalone Libraries needed to support the standalone simple pure perl server
+ --with-fastcgi Libraries needed to support the fastcgi handler
+ --with-speedycgi Libraries needed to support the speedycgi handler
+ --with-modperl1 Libraries needed to support the modperl 1 handler
+ --with-modperl2 Libraries needed to support the modperl 2 handler
+
+ --with-dev Tools needed for RT development
+
+You can also specify -v or --verbose to list the status of all dependencies,
+rather than just the missing ones.
+
+The "RT_FIX_DEPS_CMD" environment variable, if set, will be used
+instead of the standard CPAN shell by --install to install any
+required modules. It will be called with the module name, or, if
+"RT_FIX_DEPS_CMD" contains a "%s", will replace the "%s" with the
+module name before calling the program.
+.
+}
+
+
+sub text_to_hash {
+ my %hash;
+ for my $line ( split /\n/, $_[0] ) {
+ my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
+ $value ||= '';
+ $hash{$key} = $value;
+ }
+
+ return %hash;
+}
+
+$deps{'CORE'} = [ text_to_hash( << '.') ];
+Digest::base
+Digest::MD5 2.27
+DBI 1.37
+Class::ReturnValue 0.40
+Date::Format
+DBIx::SearchBuilder 1.50
+Text::Template
+File::Spec 0.8
+HTML::Entities
+HTML::Scrubber 0.08
+Log::Dispatch 2.0
+Locale::Maketext 1.06
+Locale::Maketext::Lexicon 0.32
+Locale::Maketext::Fuzzy
+MIME::Entity 5.108
+Mail::Mailer 1.57
+Net::SMTP
+Text::Wrapper
+Time::ParseDate
+Time::HiRes
+File::Temp
+Text::Autoformat
+Text::Quoted 2.02
+Tree::Simple 1.04
+Regexp::Common
+Scalar::Util
+Module::Versions::Report 1.03
+Cache::Simple::TimedExpiry
+UNIVERSAL::require
+Calendar::Simple
+CSS::Squish 0.06
+.
+
+$deps{'MASON'} = [ text_to_hash( << '.') ];
+HTML::Mason 1.23
+Errno
+Digest::MD5 2.27
+CGI::Cookie 1.20
+Storable 2.08
+Apache::Session 1.53
+XML::RSS 1.05
+GD
+GD::Graph
+GD::Text
+Text::WikiFormat 0.76
+CSS::Squish 0.06
+.
+
+$deps{'STANDALONE'} = [ text_to_hash( << '.') ];
+HTTP::Server::Simple 0.07
+HTTP::Server::Simple::Mason 0.09
+.
+
+$deps{'MAILGATE'} = [ text_to_hash( << '.') ];
+HTML::TreeBuilder
+HTML::FormatText
+Getopt::Long
+LWP::UserAgent
+Pod::Usage
+.
+
+$deps{'CLI'} = [ text_to_hash( << '.') ];
+Getopt::Long 2.24
+LWP
+HTTP::Request::Common
+Text::ParseWords
+Term::ReadLine
+Term::ReadKey
+.
+
+$deps{'DEV'} = [ text_to_hash( << '.') ];
+Test::Inline
+Apache::Test
+HTML::Form
+HTML::TokeParser
+WWW::Mechanize
+Test::WWW::Mechanize 1.04
+Module::Refresh 0.03
+Test::Expect 0.30
+XML::Simple
+File::Find
+.
+
+$deps{'FASTCGI'} = [ text_to_hash( << '.') ];
+CGI 2.92
+FCGI
+CGI::Fast
+.
+
+$deps{'SPEEDYCGI'} = [ text_to_hash( << '.') ];
+CGI 2.92
+CGI::SpeedyCGI
+.
+
+
+$deps{'MODPERL1'} = [ text_to_hash( << '.') ];
+CGI 2.92
+Apache::Request
+Apache::DBI 0.92
+.
+
+$deps{'MODPERL2'} = [ text_to_hash( << '.') ];
+CGI 2.92
+Apache::DBI
+HTML::Mason 1.31
+.
+
+$deps{'MYSQL'} = [ text_to_hash( << '.') ];
+DBD::mysql 2.1018
+.
+$deps{'ORACLE'} = [ text_to_hash( << '.') ];
+DBD::Oracle
+.
+$deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
+DBD::Pg 1.43
+.
+
+$deps{'SQLITE'} = [ text_to_hash( << '.') ];
+DBD::SQLite 1.00
+.
+
+if ($args{'download'}) {
+
+ download_mods();
+}
+
+
+check_perl_version();
+
+check_users();
+
+
+foreach my $type (sort grep $args{$_}, keys %args) {
+ next unless ($type =~ /^with-(.*?)$/);
+
+ $type = $1;
+ section("$type dependencies");
+
+ my @missing;
+ my @deps = @{ $deps{$type} };
+ while (@deps) {
+ my $module = shift @deps;
+ my $version = shift @deps;
+ my $ret = test_dep($module, $version);
+
+ push @missing, $module, $version unless $ret;
+ }
+ if ( $args{'install'} ) {
+ while( @missing ) {
+ resolve_dep(shift @missing, shift @missing);
+ }
+ }
+}
+
+conclude();
+
+sub test_dep {
+ my $module = shift;
+ my $version = shift;
+
+ eval "use $module $version ()";
+ if ($@) {
+ my $error = $@;
+ $error =~ s/\n(.*)$//s;
+ undef $error unless $error =~ /this is only/;
+ found("$module $version", 0, $error);
+
+ return undef;
+ } else {
+ my $msg = "$module";
+ $msg .= " >=$version" if $version;
+ found($msg, 1);
+ return 1;
+ }
+}
+
+sub resolve_dep {
+ my $module = shift;
+ my $version = shift;
+
+ print "\nInstall module $module\n";
+
+ my $ext = $ENV{'RT_FIX_DEPS_CMD'};
+ unless( $ext ) {
+ my $configured = 1;
+ {
+ local @INC = @INC;
+ if ( $ENV{'HOME'} ) {
+ unshift @INC, "$ENV{'HOME'}/.cpan";
+ }
+ $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
+ }
+ unless ( $configured ) {
+ print <<END;
+You didn't configure CPAN shell yet.
+Please run `@PERL@ -MCPAN -e shell` tool and configure it.
+END
+ exit(1);
+ }
+ my $rv = eval { require CPAN; CPAN::Shell->install($module) };
+ return $rv unless $@;
+
+ print <<END;
+Failed to load module CPAN.
+
+-------- Error ---------
+$@
+------------------------
+
+When we tried to start installing RT's perl dependencies,
+we were unable to load the CPAN client. This module is usually distributed
+with Perl. This usually indicates that your vendor has shipped an unconfigured
+or incorrectly configured CPAN client.
+The error above may (or may not) give you a hint about what went wrong
+
+You have several choices about how to install dependencies in
+this situatation:
+
+1) use a different tool to install dependencies by running setting the following
+ shell environment variable and rerunning this tool:
+ RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
+2) Attempt to configure CPAN by running:
+ `@PERL@ -MCPAN -e shell` program from shell.
+ If this fails, you may have to manually upgrade CPAN (see below)
+3) Try to update the CPAN client. Download it from:
+ http://search.cpan.org/dist/CPAN and try again
+4) Install each dependency manually by downloading them one by one from
+ http://search.cpan.org
+
+END
+ exit(1);
+ }
+
+ if( $ext =~ /\%s/) {
+ $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
+ } else {
+ $ext .= " $module";
+ }
+ print "\t\tcommand: '$ext'\n";
+ return scalar `$ext 1>&2`;
+}
+
+sub download_mods {
+ my %modules;
+ use CPAN;
+
+ foreach my $key (keys %deps) {
+ my @deps = (@{$deps{$key}});
+ while (@deps) {
+ my $mod = shift @deps;
+ my $ver = shift @deps;
+ next if ($mod =~ /^(DBD-|Apache-Request)/);
+ $modules{$mod} = $ver;
+ }
+ }
+ my @mods = keys %modules;
+ CPAN::get();
+ my $moddir = $args{'download'};
+ foreach my $mod (@mods) {
+ $CPAN::Config->{'build_dir'} = $moddir;
+ CPAN::get($mod);
+ }
+
+ opendir(DIR, $moddir);
+ while ( my $dir = readdir(DIR)) {
+ print "Dir is $dir\n";
+ next if ( $dir =~ /^\.\.?$/);
+
+ # Skip things we've previously tagged
+ my $out = `svn ls $args{'repository'}/tags/$dir`;
+ next if ($out);
+
+ if ($dir =~ /^(.*)-(.*?)$/) {
+ `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
+ `rm -rf $moddir/$dir`;
+
+ }
+
+ }
+ closedir(DIR);
+ exit;
+}
+
+sub check_perl_version {
+ section("perl");
+ eval {require 5.008003};
+ if ($@) {
+ found("5.8.3", 0,"RT is known to be non-functional on versions of perl older than 5.8.3. Please upgrade to 5.8.3 or newer.");
+ exit(1);
+ } else {
+ found( ">=5.8.3($])", 1);
+ }
+}
+
+sub check_users {
+ section("users");
+ found("rt group (@RTGROUP@)", defined getgrnam("@RTGROUP@"));
+ found("bin owner (@BIN_OWNER@)", defined getpwnam("@BIN_OWNER@"));
+ found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
+ found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
+ found("web owner (@WEB_USER@)", defined getpwnam("@WEB_USER@"));
+ found("web group (@WEB_GROUP@)", defined getgrnam("@WEB_GROUP@"));
+}
+
+
+
+1;