diff options
Diffstat (limited to 'rt/sbin')
27 files changed, 1258 insertions, 3002 deletions
diff --git a/rt/sbin/extract-message-catalog b/rt/sbin/extract-message-catalog deleted file mode 100644 index f6a7f8505..000000000 --- a/rt/sbin/extract-message-catalog +++ /dev/null @@ -1,382 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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; - -# po dir is for extensions -@ARGV = (<lib/RT/I18N/*.po>, <lib/RT/I18N/*.pot>, <po/*.po>, <po/*.pot>) unless @ARGV; - -$FILECAT = {}; - -# extract all strings and stuff them into $FILECAT -# scan html dir for extensions -File::Find::find( { wanted => \&extract_strings_from_code, follow => 1 }, qw(bin sbin lib share html etc) ); - -# remove msgid with $ in it. XXX: perhaps give some warnings here -$FILECAT = { map { $_ => $FILECAT->{$_} } grep { !m/\$/ } keys %$FILECAT }; - -# 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.pot" if ( $dict eq 'rt' ); - $dict = "lib/RT/I18N/$dict.po" unless -f $dict or $dict =~ m!/!; - - my $lang = $dict; - $lang =~ s|.*/||; - $lang =~ s|\.po$||; - $lang =~ s|\.pot$||; - - 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 =~ - qr!lib/blib|lib/t/autogen|var|m4|local|share/fonts! ); - return if ( /\.(?:pot|po|bak|gif|png|psd|jpe?g|svg|css|js)$/ ); - return if ( /~|,D|,B$|extract-message-catalog$|tweak-template-locstring$/ ); - 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; - } - - my $re_space_wo_nl = qr{(?!\n)\s}; - my $re_loc_suffix = qr{$re_space_wo_nl* \# $re_space_wo_nl* loc $re_space_wo_nl* $}mx; - my $re_loc_qw_suffix = qr{$re_space_wo_nl* \# $re_space_wo_nl* loc_qw $re_space_wo_nl* $}mx; - my $re_loc_pair_suffix = qr{$re_space_wo_nl* \# $re_space_wo_nl* loc_pair $re_space_wo_nl* $}mx; - my $re_loc_left_pair_suffix = qr{$re_space_wo_nl* \# $re_space_wo_nl* loc_left_pair $re_space_wo_nl* $}mx; - my $re_delim = $RE{delimited}{-delim=>q{'"}}{-keep}; - - $_ = <_>; - - # Mason filter: <&|/l>...</&> - my $line = 1; - while (m!\G.*?<&\|/l(.*?)&>(.*?)</&>!sg) { - my ( $vars, $str ) = ( $1, $2 ); - $vars =~ s/[\n\r]//g; - $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_delim)(.*?)\s*\)$/so ) { - - $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_delim)[\}\)\],;]*$re_loc_suffix/smgo) { - my $str = $1; - $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext! - unless ( defined $str ) { - warn "Couldn't process loc at $filename:$line"; - next; - } - $str = substr($str, 1, -1); - $str =~ s/\\'/\'/g; - push @{ $FILECAT->{$str} }, [ $filename, $line, '' ]; - } - - # Comment-based qw mark: "qw(...)" # loc_qw - $line = 1; - pos($_) = 0; - while (m/\G.*?(?:(qw\([^)]+\))[\}\)\],;]*)?$re_loc_qw_suffix/smgo) { - my $str = $1; - $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext! - unless ( defined $str ) { - warn "Couldn't process loc_qw at $filename:$line"; - next; - } - foreach my $value (eval($str)) { - push @{ $FILECAT->{$value} }, [ $filename, $line, '' ]; - } - } - - # Comment-based left pair mark: "..." => ... # loc_left_pair - $line = 1; - pos($_) = 0; - while (m/\G.*?(?:(\w+)\s*=>[^#\n]+?)?$re_loc_left_pair_suffix/smgo) { - my $key = $1; - $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext! - unless ( defined $key ) { - warn "Couldn't process loc_left_pair at $filename:$line"; - next; - } - $key =~ s/\\'/\'/g; - push @{ $FILECAT->{$key} }, [ $filename, $line, '' ]; - } - - # Comment-based pair mark: "..." => "..." # loc_pair - $line = 1; - pos($_) = 0; - while (m/\G.*?(?:(\w+)\s*=>\s*($re_delim)[\}\)\],;]*)?$re_loc_pair_suffix/smgo) { - my $key = $1; - my $val = $2; - $line += ( () = ( $& =~ /\n/g ) ); # cryptocontext! - unless ( defined $key && defined $val ) { - warn "Couldn't process loc_pair at $filename:$line"; - next; - } - $val = substr($val, 1, -1); - $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 = ""; - -# '#~ ' is the prefix of launchpad for msg that's not found the the source -# we'll remove the prefix later so we can still show them with our own mark - - $msgid .= shift @lines while ( $lines[0] && $lines[0] =~ /^(#~ )?(msgid|")/ ); - my $msgstr = ""; - $msgstr .= shift @lines while ( $lines[0] && $lines[0] =~ /^(#~ )?(msgstr|")/ ); - - last unless $msgid; - - chomp $msgid; - chomp $msgstr; - - $msgid =~ s/^#~ //mg; - $msgstr =~ s/^#~ //mg; - - $msgid =~ s/^msgid "(.*)"\s*?$/$1/m or warn "$msgid in $file"; - - if ( $msgid eq '' ) { - # null msgid, msgstr will have head info - $msgstr =~ s/^msgstr "(.*)"\s*?$/$1/ms or warn "$msgstr in $file"; - } - else { - $msgstr =~ s/^msgstr "(.*)"\s*?$/$1/m or warn "$msgstr in $file"; - } - - if ( $msgid ne '' ) { - for my $msg ( \$msgid, \$msgstr ) { - if ( $$msg =~ /\n/ ) { - my @lines = split /\n/, $$msg; - $$msg = - shift @lines; # first line don't need to handle any more - for (@lines) { - if (/^"(.*)"\s*$/) { - $$msg .= $1; - } - } - } - - # convert \\n back to \n - $$msg =~ s/(?!\\)\\n/\n/g; - } - } - - $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 ' . fmt($_) . "msgstr \"$Lexicon{$_}\"\n\n"; - } - - open( PO, '>', $file ) or die "Couldn't open '$file' for writing: $!"; - print PO $out; - close PO; - - return 1; -} - -sub escape { - my $text = shift; - $text =~ s/\b_(\d+)/%$1/; - return $text; -} - -sub fmt { - my $str = shift; - return "\"$str\"\n" unless $str =~ /\n/; - - my $multi_line = ($str =~ /\n(?!\z)/); - $str =~ s/\n/\\n"\n"/g; - - if ($str =~ /\n"$/) { - chop $str; - } - else { - $str .= "\"\n"; - } - return $multi_line ? qq(""\n"$str) : qq("$str); -} - - -__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/factory b/rt/sbin/factory deleted file mode 100644 index a1d1f3e34..000000000 --- a/rt/sbin/factory +++ /dev/null @@ -1,520 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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; - -die "Usage: $0 database namespace" if @ARGV != 2; - -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-2008 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/licenses/old-licenses/gpl-2.0.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 $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 = map { s/^\`\Q$database\E\`\.//; $_ } $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 ) or die $!; - print RECORD $RecordClass; - close(RECORD); - - open( COL, '>', $CollectionClassPath ) or die $!; - 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 deleted file mode 100644 index 9ddf82e2d..000000000 --- a/rt/sbin/license_tag +++ /dev/null @@ -1,261 +0,0 @@ -#!/usr/bin/perl - - -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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-2011 Best Practical Solutions, LLC - <sales@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/licenses/old-licenses/gpl-2.0.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}, 'share/html'); -File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'sbin'); -File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'bin'); -File::Find::find({ no_chdir => 1, wanted => \&tag_script}, 'etc/upgrade'); -tag_makefile ('Makefile.in'); -tag_makefile ('README'); - - -sub tag_mason { - my $pm = $_; - return unless (-f $pm); - return if $pm =~ /images/ || $pm =~ /\.(?:png|jpe?g|gif)$/; - open( FILE, '<', $pm ) or die "Failed to open $pm"; - my $file = (join "", <FILE>); - close (FILE); - print "$pm - "; - return if another_license($pm => $file) && print "has different license\n"; - - my $pmlic = $LICENSE; - $pmlic =~ s/^/%# /mg; - $pmlic =~ s/\s*$//mg; - 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\n%# END BPS TAGGED BLOCK }}}/ms; - - - } else { - print "no license section"; - $file ="%# BEGIN BPS TAGGED BLOCK {{{\n$pmlic\n%# END BPS TAGGED BLOCK }}}\n". $file; - } - $file =~ s/%# END BPS TAGGED BLOCK }}}(\n+)/%# END BPS TAGGED BLOCK }}}\n/mg; - print "\n"; - - - - - open( FILE, '>', $pm ) or die "couldn't write new file"; - print FILE $file; - close FILE; - -} - - -sub tag_makefile { - my $pm = shift; - open( FILE, '<', $pm ) or die "Failed to open $pm"; - my $file = (join "", <FILE>); - close (FILE); - print "$pm - "; - return if another_license($pm => $file) && print "has different license\n"; - - my $pmlic = $LICENSE; - $pmlic =~ s/^/# /mg; - $pmlic =~ s/\s*$//mg; - 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\n# END BPS TAGGED BLOCK }}}/ms; - - - } else { - print "no license section"; - $file ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic\n# END BPS TAGGED BLOCK }}}\n". $file; - } - $file =~ s/# END BPS TAGGED BLOCK }}}(\n+)/# END BPS TAGGED BLOCK }}}\n/mg; - print "\n"; - - - - - open( FILE, '>', $pm ) or die "couldn't write new file"; - print FILE $file; - close FILE; - -} - - -sub tag_pm { - my $pm = $_; - next unless $pm =~ /\.pm/s; - open( FILE, '<', $pm ) or die "Failed to open $pm"; - my $file = (join "", <FILE>); - close (FILE); - print "$pm - "; - return if another_license($pm => $file) && print "has different license\n"; - - my $pmlic = $LICENSE; - $pmlic =~ s/^/# /mg; - $pmlic =~ s/\s*$//mg; - 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\n# END BPS TAGGED BLOCK }}}/ms; - - - } else { - print "no license section"; - $file ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic\n# END BPS TAGGED BLOCK }}}\n". $file; - } - $file =~ s/# END BPS TAGGED BLOCK }}}(\n+)/# END BPS TAGGED BLOCK }}}\n\n/mg; - print "\n"; - - - - - open( FILE, '>', $pm ) or die "couldn't write new file $pm"; - print FILE $file; - close FILE; - -} - - -sub tag_script { - my $pm = $_; - return unless (-f $pm); - open( FILE, '<', $pm ) or die "Failed to open $pm"; - my $file = (join "", <FILE>); - close (FILE); - print "$pm - "; - return if another_license($pm => $file) && print "has different license\n"; - - my $pmlic = $LICENSE; - $pmlic =~ s/^/# /msg; - $pmlic =~ s/\s*$//mg; - 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\n# END BPS TAGGED BLOCK }}}/ms; - - - } else { - print "no license section"; - if ($file =~ /^(#!.*?)\n/) { - - my $lic ="# BEGIN BPS TAGGED BLOCK {{{\n$pmlic\n# 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 ) or die "couldn't write new file"; - print FILE $file; - close FILE; - -} - -sub another_license { - my $name = shift; - my $file = shift; - - return 1 if ($name =~ /(?:FCKEditor|scriptaculous)/i); - - return 0 if $file =~ /Copyright\s+\(c\)\s+\d\d\d\d-\d\d\d\d Best Practical Solutions/i; - return 1 if $file =~ /\b(copyright|GPL|Public Domain)\b/i; # common - return 1 if $file =~ /\(c\)\s+\d\d\d\d(?:-\d\d\d\d)?/i; # prototype - return 0; -} - diff --git a/rt/sbin/merge-rosetta.pl b/rt/sbin/merge-rosetta.pl deleted file mode 100644 index 06eedf098..000000000 --- a/rt/sbin/merge-rosetta.pl +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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 }}} -exec('sbin/rt-message-catalog', 'rosetta', @ARGV); diff --git a/rt/sbin/rt-attributes-viewer b/rt/sbin/rt-attributes-viewer index 1ae83217b..b95f0884b 100755 --- a/rt/sbin/rt-attributes-viewer +++ b/rt/sbin/rt-attributes-viewer @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -52,7 +52,7 @@ use warnings; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -73,21 +73,16 @@ BEGIN { } } -my $id = shift; -usage() unless $id; - -sub usage { - print STDERR <<END; -Usage: $0 <attribute id> - -Description: +use Getopt::Long; +my %opt; +GetOptions( \%opt, 'help|h', ); -This script deserializes and print content of an attribute defined -by <attribute id>. May be useful for developers and for troubleshooting -problems. +my $id = shift; -END - exit 1; +if ( $opt{help} || !$id ) { + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; } require RT; @@ -95,7 +90,7 @@ RT::LoadConfig(); RT::Init(); require RT::Attribute; -my $attr = RT::Attribute->new( do { no warnings 'once'; $RT::SystemUser } ); +my $attr = RT::Attribute->new( RT->SystemUser ); $attr->Load( $id ); unless ( $attr->id ) { print STDERR "Couldn't load attribute #$id\n"; @@ -108,3 +103,20 @@ $res{$_} = $attr->$_() foreach qw(ObjectType ObjectId Name Description Content C use Data::Dumper; print "Content of attribute #$id: ". Dumper( \%res ); +__END__ + +=head1 NAME + +rt-attributes-viewer - show the content of an attribute + +=head1 SYNOPSIS + + # show the content of attribute 2 + rt-attributes-viewer 2 + +=head1 DESCRIPTION + +This script deserializes and print content of an attribute defined +by <attribute id>. May be useful for developers and for troubleshooting +problems. + diff --git a/rt/sbin/rt-attributes-viewer.in b/rt/sbin/rt-attributes-viewer.in index ce9f5f7a2..c776f1a8c 100644 --- a/rt/sbin/rt-attributes-viewer.in +++ b/rt/sbin/rt-attributes-viewer.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -73,21 +73,16 @@ BEGIN { } } -my $id = shift; -usage() unless $id; - -sub usage { - print STDERR <<END; -Usage: $0 <attribute id> - -Description: +use Getopt::Long; +my %opt; +GetOptions( \%opt, 'help|h', ); -This script deserializes and print content of an attribute defined -by <attribute id>. May be useful for developers and for troubleshooting -problems. +my $id = shift; -END - exit 1; +if ( $opt{help} || !$id ) { + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; } require RT; @@ -95,7 +90,7 @@ RT::LoadConfig(); RT::Init(); require RT::Attribute; -my $attr = RT::Attribute->new( do { no warnings 'once'; $RT::SystemUser } ); +my $attr = RT::Attribute->new( RT->SystemUser ); $attr->Load( $id ); unless ( $attr->id ) { print STDERR "Couldn't load attribute #$id\n"; @@ -108,3 +103,20 @@ $res{$_} = $attr->$_() foreach qw(ObjectType ObjectId Name Description Content C use Data::Dumper; print "Content of attribute #$id: ". Dumper( \%res ); +__END__ + +=head1 NAME + +rt-attributes-viewer - show the content of an attribute + +=head1 SYNOPSIS + + # show the content of attribute 2 + rt-attributes-viewer 2 + +=head1 DESCRIPTION + +This script deserializes and print content of an attribute defined +by <attribute id>. May be useful for developers and for troubleshooting +problems. + diff --git a/rt/sbin/rt-clean-sessions b/rt/sbin/rt-clean-sessions index c3dc20143..6189c4683 100755 --- a/rt/sbin/rt-clean-sessions +++ b/rt/sbin/rt-clean-sessions @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -52,7 +52,7 @@ use warnings; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -75,14 +75,13 @@ BEGIN { use Getopt::Long; my %opt; -GetOptions( \%opt, "older=s", "debug", "help", "skip-user"); +GetOptions( \%opt, "older=s", "debug", "help|h", "skip-user" ); if ( $opt{help} ) { require Pod::Usage; - import Pod::Usage; - pod2usage({ -message => "RT Session cleanup tool\n", verbose => 1 }); - exit 1; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; } @@ -113,7 +112,8 @@ RT::InitLogging(); require RT::Interface::Web::Session; -if( $opt{'older'} or my $alogoff = int RT->Config->Get('AutoLogoff') ) { +my $alogoff = int RT->Config->Get('AutoLogoff'); +if ( $opt{'older'} or $alogoff ) { my $min; foreach ($alogoff*60, $opt{'older'}) { next unless $_; @@ -137,13 +137,13 @@ rt-clean-sessions - clean old and duplicate RT sessions =head1 SYNOPSIS - rt-clean-sessions [--debug] [--older <NUM>[H|D|M|Y]] + rt-clean-sessions [--debug] [--older <NUM>[H|D|M|Y]] - rt-clean-sessions - rt-clean-sessions --debug - rt-clean-sessions --older 10D - rt-clean-sessions --debug --older 1M - rt-clean-sessions --older 10D --skip-user + rt-clean-sessions + rt-clean-sessions --debug + rt-clean-sessions --older 10D + rt-clean-sessions --debug --older 1M + rt-clean-sessions --older 10D --skip-user =head1 DESCRIPTION diff --git a/rt/sbin/rt-clean-sessions.in b/rt/sbin/rt-clean-sessions.in index 4ec6c49ad..24ee86837 100644 --- a/rt/sbin/rt-clean-sessions.in +++ b/rt/sbin/rt-clean-sessions.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -75,14 +75,13 @@ BEGIN { use Getopt::Long; my %opt; -GetOptions( \%opt, "older=s", "debug", "help", "skip-user"); +GetOptions( \%opt, "older=s", "debug", "help|h", "skip-user" ); if ( $opt{help} ) { require Pod::Usage; - import Pod::Usage; - pod2usage({ -message => "RT Session cleanup tool\n", verbose => 1 }); - exit 1; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; } @@ -113,7 +112,8 @@ RT::InitLogging(); require RT::Interface::Web::Session; -if( $opt{'older'} or my $alogoff = int RT->Config->Get('AutoLogoff') ) { +my $alogoff = int RT->Config->Get('AutoLogoff'); +if ( $opt{'older'} or $alogoff ) { my $min; foreach ($alogoff*60, $opt{'older'}) { next unless $_; @@ -137,13 +137,13 @@ rt-clean-sessions - clean old and duplicate RT sessions =head1 SYNOPSIS - rt-clean-sessions [--debug] [--older <NUM>[H|D|M|Y]] + rt-clean-sessions [--debug] [--older <NUM>[H|D|M|Y]] - rt-clean-sessions - rt-clean-sessions --debug - rt-clean-sessions --older 10D - rt-clean-sessions --debug --older 1M - rt-clean-sessions --older 10D --skip-user + rt-clean-sessions + rt-clean-sessions --debug + rt-clean-sessions --older 10D + rt-clean-sessions --debug --older 1M + rt-clean-sessions --older 10D --skip-user =head1 DESCRIPTION diff --git a/rt/sbin/rt-dump-database b/rt/sbin/rt-dump-database deleted file mode 100755 index f460e8648..000000000 --- a/rt/sbin/rt-dump-database +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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; - -# As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure -# it is UTF-8 so further XMLin will not break -binmode(STDOUT, ":utf8"); - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("lib", "local/lib"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -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 ( defined ($value) && 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-dump-database.in b/rt/sbin/rt-dump-database.in deleted file mode 100755 index b913f2ed1..000000000 --- a/rt/sbin/rt-dump-database.in +++ /dev/null @@ -1,199 +0,0 @@ -#!@PERL@ -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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; - -# As we specify that XML is UTF-8 and we output it to STDOUT, we must be sure -# it is UTF-8 so further XMLin will not break -binmode(STDOUT, ":utf8"); - -# fix lib paths, some may be relative -BEGIN { - require File::Spec; - my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); - my $bin_path; - - for my $lib (@libs) { - unless ( File::Spec->file_name_is_absolute($lib) ) { - unless ($bin_path) { - if ( File::Spec->file_name_is_absolute(__FILE__) ) { - $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; - } - else { - require FindBin; - no warnings "once"; - $bin_path = $FindBin::Bin; - } - } - $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); - } - unshift @INC, $lib; - } - -} - -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 ( defined ($value) && 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-email-dashboards b/rt/sbin/rt-email-dashboards index b64ccd843..7acaa2354 100755 --- a/rt/sbin/rt-email-dashboards +++ b/rt/sbin/rt-email-dashboards @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -52,7 +52,7 @@ use warnings; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -74,431 +74,39 @@ BEGIN { } -use RT; -use RT::Interface::CLI qw{ CleanEnv loc }; - -use Getopt::Long; -use HTML::Mason; -use HTML::RewriteAttributes::Resources; -use HTML::RewriteAttributes::Links; -use MIME::Types; -use POSIX 'tzset'; -use File::Temp 'tempdir'; - -# Clean out all the nasties from the environment -CleanEnv(); - -# Load the config file -RT::LoadConfig(); - -# Connect to the database and get RT::SystemUser and RT::Nobody loaded -RT::Init(); - -require RT::Interface::Web; -require RT::Interface::Web::Handler; -require RT::Dashboard; -$HTML::Mason::Commands::r = RT::Dashboard::FakeRequest->new; - -no warnings 'once'; - # Read in the options my %opts; +use Getopt::Long; GetOptions( \%opts, - "help", "dryrun", "verbose", "debug", "epoch=i", "all", "skip-acl" + "help|h", "dryrun", "time=i", "epoch=i", "all" ); if ($opts{'help'}) { require Pod::Usage; - import Pod::Usage; - pod2usage(-message => "RT Email Dashboards\n", -verbose => 1); - exit 1; + print Pod::Usage::pod2usage(-verbose => 2); + exit; } -# helper functions -sub verbose { print loc(@_), "\n" if $opts{debug} || $opts{verbose}; 1 } -sub debug { print loc(@_), "\n" if $opts{debug}; 1 } -sub error { $RT::Logger->error(loc(@_)); verbose(@_); 1 } -sub warning { $RT::Logger->warning(loc(@_)); verbose(@_); 1 } - -my $now = $opts{epoch} || time; -verbose "Using time [_1]", scalar localtime($now); - -my $from = get_from(); -debug "Sending email from [_1]", $from; - -# look through each user for her subscriptions -my $Users = RT::Users->new($RT::SystemUser); -$Users->LimitToPrivileged; - -while (defined(my $user = $Users->Next)) { - if ($user->PrincipalObj->Disabled) { - debug "Skipping over " - . $user->Name - . " due to having a disabled account."; - next; - } - - my ($hour, $dow, $dom) = hour_dow_dom_in($user->Timezone || RT->Config->Get('Timezone')); - $hour .= ':00'; - debug "Checking [_1]'s subscriptions: hour [_2], dow [_3], dom [_4]", - $user->Name, $hour, $dow, $dom; - - my $currentuser = RT::CurrentUser->new; - $currentuser->LoadByName($user->Name); - - # look through this user's subscriptions, are any supposed to be generated - # right now? - for my $subscription ($user->Attributes->Named('Subscription')) { - my $counter = $subscription->SubValue('Counter') || 0; - - if (!$opts{all}) { - debug "Checking against subscription with frequency [_1], hour [_2], dow [_3], dom [_4]", - $subscription->SubValue('Frequency'), $subscription->SubValue('Hour'), - $subscription->SubValue('Dow'), $subscription->SubValue('Dom'); - - next if $subscription->SubValue('Frequency') eq 'never'; - - # correct hour? - next if $subscription->SubValue('Hour') ne $hour; - - # if weekly, correct day of week? - if ( $subscription->SubValue('Frequency') eq 'weekly' ) { - next if $subscription->SubValue('Dow') ne $dow; - my $fow = $subscription->SubValue('Fow') || 1; - if ( $counter % $fow ) { - $subscription->SetSubValues( Counter => $counter + 1 ) - unless $opts{'dryrun'}; - next; - } - } - - # if monthly, correct day of month? - elsif ($subscription->SubValue('Frequency') eq 'monthly') { - next if $subscription->SubValue('Dom') != $dom; - } - - elsif ($subscription->SubValue('Frequency') eq 'm-f') { - next if $dow eq 'Sunday' || $dow eq 'Saturday'; - } - } - - my $email = $subscription->SubValue('Recipient') - || $user->EmailAddress; - - eval { send_dashboard($currentuser, $email, $subscription) }; - if ( $@ ) { - error 'Caught exception: ' . $@; - } - else { - $subscription->SetSubValues( - Counter => $counter + 1 ) - unless $opts{'dryrun'}; - } - } -} - -sub send_dashboard { - my ($currentuser, $email, $subscription) = @_; - - my $rows = $subscription->SubValue('Rows'); - - my $dashboard = RT::Dashboard->new($currentuser); - - my ($ok, $msg) = $dashboard->LoadById($subscription->SubValue('DashboardId')); - - # failed to load dashboard. perhaps it was deleted or it changed privacy - if (!$ok) { - warning "Unable to load dashboard [_1] of subscription [_2] for user [_3]: [_4]", - $subscription->SubValue('DashboardId'), - $subscription->Id, - $currentuser->Name, - $msg; - - my $ok = RT::Interface::Email::SendEmailUsingTemplate( - From => $from, - To => $email, - Template => 'Error: Missing dashboard', - Arguments => { - SubscriptionObj => $subscription, - }, - ); - - # only delete the subscription if the email looks like it went through - if ($ok) { - my ($deleted, $msg) = $subscription->Delete(); - if ($deleted) { - verbose("Deleted an obsolete subscription: [_1]", $msg); - } - else { - warning("Unable to delete an obsolete subscription: [_1]", $msg); - } - } - else { - warning("Unable to notify [_1] of an obsolete subscription", $currentuser->Name); - } - - return; - } - - verbose 'Creating dashboard "[_1]" for user "[_2]":', - $dashboard->Name, - $currentuser->Name; - - if ($opts{'dryrun'}) { - print << "SUMMARY"; - Dashboard: @{[ $dashboard->Name ]} - User: @{[ $currentuser->Name ]} <$email> -SUMMARY - return; - } - - $HTML::Mason::Commands::session{CurrentUser} = $currentuser; - my $contents = run_component( - '/Dashboards/Render.html', - id => $dashboard->Id, - Preview => 0, - ); - - for (@{ RT->Config->Get('EmailDashboardRemove') || [] }) { - $contents =~ s/$_//g; - } - - debug "Got [_1] characters of output.", length $contents; - - $contents = HTML::RewriteAttributes::Links->rewrite( - $contents, - RT->Config->Get('WebURL') . '/Dashboards/Render.html', - ); - - email_dashboard($currentuser, $email, $dashboard, $subscription, $contents); -} - -sub email_dashboard { - my ($currentuser, $email, $dashboard, $subscription, $content) = @_; - - verbose 'Sending dashboard "[_1]" to user [_2] <[_3]>', - $dashboard->Name, - $currentuser->Name, - $email; - - my $subject = sprintf '[%s] ' . RT->Config->Get('DashboardSubject'), - RT->Config->Get('rtname'), - ucfirst($subscription->SubValue('Frequency')), - $dashboard->Name; - - my $entity = build_email($content, $from, $email, $subject); - - my $ok = RT::Interface::Email::SendEmail( - Entity => $entity, - ); - - debug "Done sending dashboard to [_1] <[_2]>", - $currentuser->Name, $email - and return if $ok; - - error 'Failed to email dashboard to user [_1] <[_2]>', - $currentuser->Name, $email; -} +require RT; +require RT::Interface::CLI; +RT::Interface::CLI->import(qw{ CleanEnv loc }); -sub build_email { - my ($content, $from, $to, $subject) = @_; - my @parts; - my %cid_of; - - $content = HTML::RewriteAttributes::Resources->rewrite($content, sub { - my $uri = shift; - - # already attached this object - return "cid:$cid_of{$uri}" if $cid_of{$uri}; - - $cid_of{$uri} = time() . $$ . int(rand(1e6)); - my ($data, $filename, $mimetype, $encoding) = get_resource($uri); - - # downgrade non-text strings, because all strings are utf8 by - # default, which is wrong for non-text strings. - if ( $mimetype !~ m{text/} ) { - utf8::downgrade( $data, 1 ) or warning "downgrade $data failed"; - } - - push @parts, MIME::Entity->build( - Top => 0, - Data => $data, - Type => $mimetype, - Encoding => $encoding, - Disposition => 'inline', - Name => $filename, - 'Content-Id' => $cid_of{$uri}, - ); - - return "cid:$cid_of{$uri}"; - }, - inline_css => sub { - my $uri = shift; - my ($content) = get_resource($uri); - return $content; - }, - inline_imports => 1, - ); - - my $entity = MIME::Entity->build( - From => $from, - To => $to, - Subject => $subject, - Type => "multipart/mixed", - ); - - $entity->attach( - Data => Encode::encode_utf8($content), - Type => 'text/html', - Charset => 'UTF-8', - Disposition => 'inline', - ); - - for my $part (@parts) { - $entity->add_part($part); - } - - return $entity; -} - -sub get_from { - RT->Config->Get('DashboardAddress') || RT->Config->Get('OwnerEmail') -} - -{ - my $mason; - my $outbuf = ''; - my $data_dir = ''; - - sub mason { - unless ($mason) { - debug "Creating Mason object."; - - # user may not have permissions on the data directory, so create a - # new one - $data_dir = tempdir(CLEANUP => 1); - - $mason = HTML::Mason::Interp->new( - RT::Interface::Web::Handler->DefaultHandlerArgs, - out_method => \$outbuf, - autohandler_name => '', # disable forced login and more - data_dir => $data_dir, - ); - } - return $mason; - } - - sub run_component { - mason->exec(@_); - my $ret = $outbuf; - $outbuf = ''; - return $ret; - } -} - -{ - my %cache; - - sub hour_dow_dom_in { - my $tz = shift; - return @{$cache{$tz}} if exists $cache{$tz}; - - my ($hour, $dow, $dom); - - { - local $ENV{'TZ'} = $tz; - ## Using POSIX::tzset fixes a bug where the TZ environment variable - ## is cached. - tzset(); - (undef, undef, $hour, $dom, undef, undef, $dow) = localtime($now); - } - tzset(); # return back previous value - - $hour = "0$hour" - if length($hour) == 1; - $dow = (qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/)[$dow]; - - return @{$cache{$tz}} = ($hour, $dow, $dom); - } -} - -sub get_resource { - my $uri = URI->new(shift); - my ($content, $filename, $mimetype, $encoding); - - verbose "Getting resource [_1]", $uri; - - # strip out the equivalent of WebURL, so we start at the correct / - my $path = $uri->path; - my $webpath = RT->Config->Get('WebPath'); - $path =~ s/^\Q$webpath//; - - # add a leading / if needed - $path = "/$path" - unless $path =~ m{^/}; - - # grab the query arguments - my %args; - for (split /&/, ($uri->query||'')) { - my ($k, $v) = /^(.*?)=(.*)$/ - or die "Unable to parse query parameter '$_'"; - - for ($k, $v) { s/%(..)/chr hex $1/ge } - - # no value yet, simple key=value - if (!exists $args{$k}) { - $args{$k} = $v; - } - # already have key=value, need to upgrade it to key=[value1, value2] - elsif (!ref($args{$k})) { - $args{$k} = [$args{$k}, $v]; - } - # already key=[value1, value2], just add the new value - else { - push @{ $args{$k} }, $v; - } - } - - debug "Running component '[_1]'", $path; - $content = run_component($path, %args); - - # guess at the filename from the component name - $filename = $1 if $path =~ m{^.*/(.*?)$}; - - # the rest of this was taken from Email::MIME::CreateHTML::Resolver::LWP - ($mimetype, $encoding) = MIME::Types::by_suffix($filename); - - my $content_type = $HTML::Mason::Commands::r->content_type; - if ($content_type) { - $mimetype = $content_type; - - # strip down to just a MIME type - $mimetype = $1 if $mimetype =~ /(\S+);\s*charset=(.*)$/; - } - - #If all else fails then some conservative and general-purpose defaults are: - $mimetype ||= 'application/octet-stream'; - $encoding ||= 'base64'; +# Clean out all the nasties from the environment +CleanEnv(); - debug "Resource [_1]: length=[_2] filename='[_3]' mimetype='[_4]', encoding='[_5]'", - $uri, - length($content), - $filename, - $mimetype, - $encoding; +# Load the config file +RT::LoadConfig(); - return ($content, $filename, $mimetype, $encoding); -} +# Connect to the database and get RT::SystemUser and RT::Nobody loaded +RT::Init(); -package RT::Dashboard::FakeRequest; -sub new { bless {}, shift } -sub header_out { shift } -sub headers_out { shift } -sub content_type { - my $self = shift; - $self->{content_type} = shift if @_; - return $self->{content_type}; -} +require RT::Dashboard::Mailer; +RT::Dashboard::Mailer->MailDashboards( + All => $opts{all}, + DryRun => $opts{dryrun}, + Time => ($opts{time} || $opts{epoch} || time), # epoch is the old-style + Opts => \%opts, +); =head1 NAME @@ -506,7 +114,7 @@ rt-email-dashboards - Send email dashboards =head1 SYNOPSIS - /opt/rt3/local/sbin/rt-email-dashboards [options] + rt-email-dashboards [options] =head1 DESCRIPTION @@ -522,7 +130,7 @@ are taken to be in the user's timezone if available, UTC otherwise. You'll need to have cron run this script every hour. Here's an example crontab entry to do this. - 0 * * * * /usr/bin/perl /opt/rt3/local/sbin/rt-email-dashboards + 0 * * * * /usr/bin/perl /opt/rt4/local/sbin/rt-email-dashboards This will run the script every hour on the hour. This may need some further tweaking to be run as the correct user. @@ -533,34 +141,31 @@ This tool supports a few options. Most are for debugging. =over 8 +=item -h + =item --help Display this documentation =item --dryrun -Figure out which dashboards would be sent, but don't actually generate them +Figure out which dashboards would be sent, but don't actually generate or email +any of them -=item --epoch SECONDS +=item --time SECONDS Instead of using the current time to figure out which dashboards should be sent, use SECONDS (usually since midnight Jan 1st, 1970, so C<1192216018> would be Oct 12 19:06:58 GMT 2007). -=item --verbose - -Print out some tracing information (such as which dashboards are being -generated and sent out) - -=item --debug +=item --epoch SECONDS -Print out more tracing information (such as each user and subscription that is -being considered) +Back-compat for --time SECONDS. =item --all Ignore subscription frequency when considering each dashboard (should only be -used with --dryrun) +used with --dryrun for testing and debugging) =back diff --git a/rt/sbin/rt-email-dashboards.in b/rt/sbin/rt-email-dashboards.in index 50dad2f9b..aa5ce649f 100644 --- a/rt/sbin/rt-email-dashboards.in +++ b/rt/sbin/rt-email-dashboards.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -74,431 +74,39 @@ BEGIN { } -use RT; -use RT::Interface::CLI qw{ CleanEnv loc }; - -use Getopt::Long; -use HTML::Mason; -use HTML::RewriteAttributes::Resources; -use HTML::RewriteAttributes::Links; -use MIME::Types; -use POSIX 'tzset'; -use File::Temp 'tempdir'; - -# Clean out all the nasties from the environment -CleanEnv(); - -# Load the config file -RT::LoadConfig(); - -# Connect to the database and get RT::SystemUser and RT::Nobody loaded -RT::Init(); - -require RT::Interface::Web; -require RT::Interface::Web::Handler; -require RT::Dashboard; -$HTML::Mason::Commands::r = RT::Dashboard::FakeRequest->new; - -no warnings 'once'; - # Read in the options my %opts; +use Getopt::Long; GetOptions( \%opts, - "help", "dryrun", "verbose", "debug", "epoch=i", "all", "skip-acl" + "help|h", "dryrun", "time=i", "epoch=i", "all" ); if ($opts{'help'}) { require Pod::Usage; - import Pod::Usage; - pod2usage(-message => "RT Email Dashboards\n", -verbose => 1); - exit 1; + print Pod::Usage::pod2usage(-verbose => 2); + exit; } -# helper functions -sub verbose { print loc(@_), "\n" if $opts{debug} || $opts{verbose}; 1 } -sub debug { print loc(@_), "\n" if $opts{debug}; 1 } -sub error { $RT::Logger->error(loc(@_)); verbose(@_); 1 } -sub warning { $RT::Logger->warning(loc(@_)); verbose(@_); 1 } - -my $now = $opts{epoch} || time; -verbose "Using time [_1]", scalar localtime($now); - -my $from = get_from(); -debug "Sending email from [_1]", $from; - -# look through each user for her subscriptions -my $Users = RT::Users->new($RT::SystemUser); -$Users->LimitToPrivileged; - -while (defined(my $user = $Users->Next)) { - if ($user->PrincipalObj->Disabled) { - debug "Skipping over " - . $user->Name - . " due to having a disabled account."; - next; - } - - my ($hour, $dow, $dom) = hour_dow_dom_in($user->Timezone || RT->Config->Get('Timezone')); - $hour .= ':00'; - debug "Checking [_1]'s subscriptions: hour [_2], dow [_3], dom [_4]", - $user->Name, $hour, $dow, $dom; - - my $currentuser = RT::CurrentUser->new; - $currentuser->LoadByName($user->Name); - - # look through this user's subscriptions, are any supposed to be generated - # right now? - for my $subscription ($user->Attributes->Named('Subscription')) { - my $counter = $subscription->SubValue('Counter') || 0; - - if (!$opts{all}) { - debug "Checking against subscription with frequency [_1], hour [_2], dow [_3], dom [_4]", - $subscription->SubValue('Frequency'), $subscription->SubValue('Hour'), - $subscription->SubValue('Dow'), $subscription->SubValue('Dom'); - - next if $subscription->SubValue('Frequency') eq 'never'; - - # correct hour? - next if $subscription->SubValue('Hour') ne $hour; - - # if weekly, correct day of week? - if ( $subscription->SubValue('Frequency') eq 'weekly' ) { - next if $subscription->SubValue('Dow') ne $dow; - my $fow = $subscription->SubValue('Fow') || 1; - if ( $counter % $fow ) { - $subscription->SetSubValues( Counter => $counter + 1 ) - unless $opts{'dryrun'}; - next; - } - } - - # if monthly, correct day of month? - elsif ($subscription->SubValue('Frequency') eq 'monthly') { - next if $subscription->SubValue('Dom') != $dom; - } - - elsif ($subscription->SubValue('Frequency') eq 'm-f') { - next if $dow eq 'Sunday' || $dow eq 'Saturday'; - } - } - - my $email = $subscription->SubValue('Recipient') - || $user->EmailAddress; - - eval { send_dashboard($currentuser, $email, $subscription) }; - if ( $@ ) { - error 'Caught exception: ' . $@; - } - else { - $subscription->SetSubValues( - Counter => $counter + 1 ) - unless $opts{'dryrun'}; - } - } -} - -sub send_dashboard { - my ($currentuser, $email, $subscription) = @_; - - my $rows = $subscription->SubValue('Rows'); - - my $dashboard = RT::Dashboard->new($currentuser); - - my ($ok, $msg) = $dashboard->LoadById($subscription->SubValue('DashboardId')); - - # failed to load dashboard. perhaps it was deleted or it changed privacy - if (!$ok) { - warning "Unable to load dashboard [_1] of subscription [_2] for user [_3]: [_4]", - $subscription->SubValue('DashboardId'), - $subscription->Id, - $currentuser->Name, - $msg; - - my $ok = RT::Interface::Email::SendEmailUsingTemplate( - From => $from, - To => $email, - Template => 'Error: Missing dashboard', - Arguments => { - SubscriptionObj => $subscription, - }, - ); - - # only delete the subscription if the email looks like it went through - if ($ok) { - my ($deleted, $msg) = $subscription->Delete(); - if ($deleted) { - verbose("Deleted an obsolete subscription: [_1]", $msg); - } - else { - warning("Unable to delete an obsolete subscription: [_1]", $msg); - } - } - else { - warning("Unable to notify [_1] of an obsolete subscription", $currentuser->Name); - } - - return; - } - - verbose 'Creating dashboard "[_1]" for user "[_2]":', - $dashboard->Name, - $currentuser->Name; - - if ($opts{'dryrun'}) { - print << "SUMMARY"; - Dashboard: @{[ $dashboard->Name ]} - User: @{[ $currentuser->Name ]} <$email> -SUMMARY - return; - } - - $HTML::Mason::Commands::session{CurrentUser} = $currentuser; - my $contents = run_component( - '/Dashboards/Render.html', - id => $dashboard->Id, - Preview => 0, - ); - - for (@{ RT->Config->Get('EmailDashboardRemove') || [] }) { - $contents =~ s/$_//g; - } - - debug "Got [_1] characters of output.", length $contents; - - $contents = HTML::RewriteAttributes::Links->rewrite( - $contents, - RT->Config->Get('WebURL') . '/Dashboards/Render.html', - ); - - email_dashboard($currentuser, $email, $dashboard, $subscription, $contents); -} - -sub email_dashboard { - my ($currentuser, $email, $dashboard, $subscription, $content) = @_; - - verbose 'Sending dashboard "[_1]" to user [_2] <[_3]>', - $dashboard->Name, - $currentuser->Name, - $email; - - my $subject = sprintf '[%s] ' . RT->Config->Get('DashboardSubject'), - RT->Config->Get('rtname'), - ucfirst($subscription->SubValue('Frequency')), - $dashboard->Name; - - my $entity = build_email($content, $from, $email, $subject); - - my $ok = RT::Interface::Email::SendEmail( - Entity => $entity, - ); - - debug "Done sending dashboard to [_1] <[_2]>", - $currentuser->Name, $email - and return if $ok; - - error 'Failed to email dashboard to user [_1] <[_2]>', - $currentuser->Name, $email; -} +require RT; +require RT::Interface::CLI; +RT::Interface::CLI->import(qw{ CleanEnv loc }); -sub build_email { - my ($content, $from, $to, $subject) = @_; - my @parts; - my %cid_of; - - $content = HTML::RewriteAttributes::Resources->rewrite($content, sub { - my $uri = shift; - - # already attached this object - return "cid:$cid_of{$uri}" if $cid_of{$uri}; - - $cid_of{$uri} = time() . $$ . int(rand(1e6)); - my ($data, $filename, $mimetype, $encoding) = get_resource($uri); - - # downgrade non-text strings, because all strings are utf8 by - # default, which is wrong for non-text strings. - if ( $mimetype !~ m{text/} ) { - utf8::downgrade( $data, 1 ) or warning "downgrade $data failed"; - } - - push @parts, MIME::Entity->build( - Top => 0, - Data => $data, - Type => $mimetype, - Encoding => $encoding, - Disposition => 'inline', - Name => $filename, - 'Content-Id' => $cid_of{$uri}, - ); - - return "cid:$cid_of{$uri}"; - }, - inline_css => sub { - my $uri = shift; - my ($content) = get_resource($uri); - return $content; - }, - inline_imports => 1, - ); - - my $entity = MIME::Entity->build( - From => $from, - To => $to, - Subject => $subject, - Type => "multipart/mixed", - ); - - $entity->attach( - Data => Encode::encode_utf8($content), - Type => 'text/html', - Charset => 'UTF-8', - Disposition => 'inline', - ); - - for my $part (@parts) { - $entity->add_part($part); - } - - return $entity; -} - -sub get_from { - RT->Config->Get('DashboardAddress') || RT->Config->Get('OwnerEmail') -} - -{ - my $mason; - my $outbuf = ''; - my $data_dir = ''; - - sub mason { - unless ($mason) { - debug "Creating Mason object."; - - # user may not have permissions on the data directory, so create a - # new one - $data_dir = tempdir(CLEANUP => 1); - - $mason = HTML::Mason::Interp->new( - RT::Interface::Web::Handler->DefaultHandlerArgs, - out_method => \$outbuf, - autohandler_name => '', # disable forced login and more - data_dir => $data_dir, - ); - } - return $mason; - } - - sub run_component { - mason->exec(@_); - my $ret = $outbuf; - $outbuf = ''; - return $ret; - } -} - -{ - my %cache; - - sub hour_dow_dom_in { - my $tz = shift; - return @{$cache{$tz}} if exists $cache{$tz}; - - my ($hour, $dow, $dom); - - { - local $ENV{'TZ'} = $tz; - ## Using POSIX::tzset fixes a bug where the TZ environment variable - ## is cached. - tzset(); - (undef, undef, $hour, $dom, undef, undef, $dow) = localtime($now); - } - tzset(); # return back previous value - - $hour = "0$hour" - if length($hour) == 1; - $dow = (qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/)[$dow]; - - return @{$cache{$tz}} = ($hour, $dow, $dom); - } -} - -sub get_resource { - my $uri = URI->new(shift); - my ($content, $filename, $mimetype, $encoding); - - verbose "Getting resource [_1]", $uri; - - # strip out the equivalent of WebURL, so we start at the correct / - my $path = $uri->path; - my $webpath = RT->Config->Get('WebPath'); - $path =~ s/^\Q$webpath//; - - # add a leading / if needed - $path = "/$path" - unless $path =~ m{^/}; - - # grab the query arguments - my %args; - for (split /&/, ($uri->query||'')) { - my ($k, $v) = /^(.*?)=(.*)$/ - or die "Unable to parse query parameter '$_'"; - - for ($k, $v) { s/%(..)/chr hex $1/ge } - - # no value yet, simple key=value - if (!exists $args{$k}) { - $args{$k} = $v; - } - # already have key=value, need to upgrade it to key=[value1, value2] - elsif (!ref($args{$k})) { - $args{$k} = [$args{$k}, $v]; - } - # already key=[value1, value2], just add the new value - else { - push @{ $args{$k} }, $v; - } - } - - debug "Running component '[_1]'", $path; - $content = run_component($path, %args); - - # guess at the filename from the component name - $filename = $1 if $path =~ m{^.*/(.*?)$}; - - # the rest of this was taken from Email::MIME::CreateHTML::Resolver::LWP - ($mimetype, $encoding) = MIME::Types::by_suffix($filename); - - my $content_type = $HTML::Mason::Commands::r->content_type; - if ($content_type) { - $mimetype = $content_type; - - # strip down to just a MIME type - $mimetype = $1 if $mimetype =~ /(\S+);\s*charset=(.*)$/; - } - - #If all else fails then some conservative and general-purpose defaults are: - $mimetype ||= 'application/octet-stream'; - $encoding ||= 'base64'; +# Clean out all the nasties from the environment +CleanEnv(); - debug "Resource [_1]: length=[_2] filename='[_3]' mimetype='[_4]', encoding='[_5]'", - $uri, - length($content), - $filename, - $mimetype, - $encoding; +# Load the config file +RT::LoadConfig(); - return ($content, $filename, $mimetype, $encoding); -} +# Connect to the database and get RT::SystemUser and RT::Nobody loaded +RT::Init(); -package RT::Dashboard::FakeRequest; -sub new { bless {}, shift } -sub header_out { shift } -sub headers_out { shift } -sub content_type { - my $self = shift; - $self->{content_type} = shift if @_; - return $self->{content_type}; -} +require RT::Dashboard::Mailer; +RT::Dashboard::Mailer->MailDashboards( + All => $opts{all}, + DryRun => $opts{dryrun}, + Time => ($opts{time} || $opts{epoch} || time), # epoch is the old-style + Opts => \%opts, +); =head1 NAME @@ -506,7 +114,7 @@ rt-email-dashboards - Send email dashboards =head1 SYNOPSIS - /opt/rt3/local/sbin/rt-email-dashboards [options] + rt-email-dashboards [options] =head1 DESCRIPTION @@ -522,7 +130,7 @@ are taken to be in the user's timezone if available, UTC otherwise. You'll need to have cron run this script every hour. Here's an example crontab entry to do this. - 0 * * * * @PERL@ /opt/rt3/local/sbin/rt-email-dashboards + 0 * * * * @PERL@ /opt/rt4/local/sbin/rt-email-dashboards This will run the script every hour on the hour. This may need some further tweaking to be run as the correct user. @@ -533,34 +141,31 @@ This tool supports a few options. Most are for debugging. =over 8 +=item -h + =item --help Display this documentation =item --dryrun -Figure out which dashboards would be sent, but don't actually generate them +Figure out which dashboards would be sent, but don't actually generate or email +any of them -=item --epoch SECONDS +=item --time SECONDS Instead of using the current time to figure out which dashboards should be sent, use SECONDS (usually since midnight Jan 1st, 1970, so C<1192216018> would be Oct 12 19:06:58 GMT 2007). -=item --verbose - -Print out some tracing information (such as which dashboards are being -generated and sent out) - -=item --debug +=item --epoch SECONDS -Print out more tracing information (such as each user and subscription that is -being considered) +Back-compat for --time SECONDS. =item --all Ignore subscription frequency when considering each dashboard (should only be -used with --dryrun) +used with --dryrun for testing and debugging) =back diff --git a/rt/sbin/rt-email-digest b/rt/sbin/rt-email-digest index e26dde890..201cfd691 100755 --- a/rt/sbin/rt-email-digest +++ b/rt/sbin/rt-email-digest @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -51,7 +51,7 @@ use strict; BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -85,7 +85,7 @@ RT::Init(); sub usage { my ($error) = @_; - print loc("Usage: ") . "$0 -m (daily|weekly) [--print] [--help]\n"; + print loc("Usage:") . " $0 -m (daily|weekly) [--print] [--help]\n"; print loc( "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.", $0 @@ -188,13 +188,13 @@ sub send_digest { } } -=item mark_transactions_sent( $frequency, $user, @txn_list ); - -Takes a frequency string (either 'daily' or 'weekly'), a user and one or more -transaction objects as its arguments. Marks the given deferred -notifications as sent. - -=cut +# =item mark_transactions_sent( $frequency, $user, @txn_list ); +# +# Takes a frequency string (either 'daily' or 'weekly'), a user and one or more +# transaction objects as its arguments. Marks the given deferred +# notifications as sent. +# +# =cut sub mark_transactions_sent { my ( $freq, $user, @txns ) = @_; @@ -335,3 +335,42 @@ sub build_digest_for_user { return ( $contents_list, $contents_body ); } + +__END__ + +=head1 NAME + +rt-email-digest - dispatch deferred notifications as a per-user digest + +=head1 SYNOPSIS + + rt-email-digest -m (daily|weekly) [--print] [--help] + +=head1 DESCRIPTION + +This script is a tool to dispatch all deferred RT notifications as a per-user +object. + +=head1 OPTIONS + +=over + +=item mode + +Specify whether this is a daily or weekly run. + +--mode is equal to -m + +=item print + +Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent + +--print is equal to -p + +=item help + +Print this message + +--help is equal to -h + +=back diff --git a/rt/sbin/rt-email-digest.in b/rt/sbin/rt-email-digest.in index 5730717f2..fd257fa77 100644 --- a/rt/sbin/rt-email-digest.in +++ b/rt/sbin/rt-email-digest.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -85,7 +85,7 @@ RT::Init(); sub usage { my ($error) = @_; - print loc("Usage: ") . "$0 -m (daily|weekly) [--print] [--help]\n"; + print loc("Usage:") . " $0 -m (daily|weekly) [--print] [--help]\n"; print loc( "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.", $0 @@ -188,13 +188,13 @@ sub send_digest { } } -=item mark_transactions_sent( $frequency, $user, @txn_list ); - -Takes a frequency string (either 'daily' or 'weekly'), a user and one or more -transaction objects as its arguments. Marks the given deferred -notifications as sent. - -=cut +# =item mark_transactions_sent( $frequency, $user, @txn_list ); +# +# Takes a frequency string (either 'daily' or 'weekly'), a user and one or more +# transaction objects as its arguments. Marks the given deferred +# notifications as sent. +# +# =cut sub mark_transactions_sent { my ( $freq, $user, @txns ) = @_; @@ -335,3 +335,42 @@ sub build_digest_for_user { return ( $contents_list, $contents_body ); } + +__END__ + +=head1 NAME + +rt-email-digest - dispatch deferred notifications as a per-user digest + +=head1 SYNOPSIS + + rt-email-digest -m (daily|weekly) [--print] [--help] + +=head1 DESCRIPTION + +This script is a tool to dispatch all deferred RT notifications as a per-user +object. + +=head1 OPTIONS + +=over + +=item mode + +Specify whether this is a daily or weekly run. + +--mode is equal to -m + +=item print + +Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent + +--print is equal to -p + +=item help + +Print this message + +--help is equal to -h + +=back diff --git a/rt/sbin/rt-email-group-admin b/rt/sbin/rt-email-group-admin index 169ef9ab6..372a90f27 100755 --- a/rt/sbin/rt-email-group-admin +++ b/rt/sbin/rt-email-group-admin @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -89,7 +89,7 @@ use strict; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -111,23 +111,14 @@ BEGIN { } -use RT; -RT::LoadConfig; -RT::Init; - -require RT::Principal; -require RT::User; -require RT::Group; -require RT::ScripActions; - use Getopt::Long qw(GetOptions); +Getopt::Long::Configure( "pass_through" ); our $cmd = 'usage'; our $opts = {}; sub parse_args { my $tmp; - Getopt::Long::Configure( "pass_through" ); if ( GetOptions( 'list' => \$tmp ) && $tmp ) { $cmd = 'list'; } @@ -180,18 +171,28 @@ sub parse_args { } sub usage { - local $@; - eval "require Pod::PlainText;"; - if ( $@ ) { - print "see `perldoc $0`\n"; - } else { - my $parser = Pod::PlainText->new( sentence => 0, width => 78 ); - $parser->parse_from_file( $0 ); - } + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); +} + +my $help; +if ( GetOptions( 'help|h' => \$help ) && $help ) { + usage(); + exit; } parse_args(); +require RT; +RT->LoadConfig; +RT->Init; + +require RT::Principal; +require RT::User; +require RT::Group; +require RT::ScripActions; + + { eval "main::$cmd()"; if ( $@ ) { @@ -231,7 +232,7 @@ sub _list { print "Members: \n"; foreach( @princ ) { - my $obj = RT::Principal->new( $RT::SystemUser ); + my $obj = RT::Principal->new( RT->SystemUser ); $obj->Load( $_ ); next unless $obj->id; @@ -252,7 +253,7 @@ recipient list. Would be notify as comment if --comment specified. =cut sub create { - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'Name', VALUE => $opts->{'name'}, @@ -281,7 +282,7 @@ sub __create_empty { my $name = shift; my $as_comment = shift || 0; require RT::ScripAction; - my $action = RT::ScripAction->new( $RT::SystemUser ); + my $action = RT::ScripAction->new( RT->SystemUser ); $action->Create( Name => $name, Description => "Created with rt-email-group-admin script", @@ -302,7 +303,7 @@ sub __check_group { my $instance = shift; require RT::Group; - my $obj = RT::Group->new( $RT::SystemUser ); + my $obj = RT::Group->new( RT->SystemUser ); $obj->LoadUserDefinedGroup( $instance ); return $obj->id ? $obj : undef; } @@ -317,7 +318,7 @@ sub __check_user { my $instance = shift; require RT::User; - my $obj = RT::User->new( $RT::SystemUser ); + my $obj = RT::User->new( RT->SystemUser ); $obj->Load( $instance ); return $obj->id ? $obj : undef; } @@ -378,7 +379,7 @@ sub delete { } require RT::Scrips; - my $scrips = RT::Scrips->new( $RT::SystemUser ); + my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id ); if ( $scrips->Count ) { my @sid; @@ -451,7 +452,7 @@ sub rename { exit(-1); } - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} ); if ( $actions->Count ) { print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n"; @@ -482,7 +483,7 @@ sub argument_to_list { } sub _get_our_actions { - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'ExecModule', VALUE => 'NotifyGroup', diff --git a/rt/sbin/rt-email-group-admin.in b/rt/sbin/rt-email-group-admin.in index 600486005..0e32525d9 100755 --- a/rt/sbin/rt-email-group-admin.in +++ b/rt/sbin/rt-email-group-admin.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -111,23 +111,14 @@ BEGIN { } -use RT; -RT::LoadConfig; -RT::Init; - -require RT::Principal; -require RT::User; -require RT::Group; -require RT::ScripActions; - use Getopt::Long qw(GetOptions); +Getopt::Long::Configure( "pass_through" ); our $cmd = 'usage'; our $opts = {}; sub parse_args { my $tmp; - Getopt::Long::Configure( "pass_through" ); if ( GetOptions( 'list' => \$tmp ) && $tmp ) { $cmd = 'list'; } @@ -180,18 +171,28 @@ sub parse_args { } sub usage { - local $@; - eval "require Pod::PlainText;"; - if ( $@ ) { - print "see `perldoc $0`\n"; - } else { - my $parser = Pod::PlainText->new( sentence => 0, width => 78 ); - $parser->parse_from_file( $0 ); - } + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); +} + +my $help; +if ( GetOptions( 'help|h' => \$help ) && $help ) { + usage(); + exit; } parse_args(); +require RT; +RT->LoadConfig; +RT->Init; + +require RT::Principal; +require RT::User; +require RT::Group; +require RT::ScripActions; + + { eval "main::$cmd()"; if ( $@ ) { @@ -231,7 +232,7 @@ sub _list { print "Members: \n"; foreach( @princ ) { - my $obj = RT::Principal->new( $RT::SystemUser ); + my $obj = RT::Principal->new( RT->SystemUser ); $obj->Load( $_ ); next unless $obj->id; @@ -252,7 +253,7 @@ recipient list. Would be notify as comment if --comment specified. =cut sub create { - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'Name', VALUE => $opts->{'name'}, @@ -281,7 +282,7 @@ sub __create_empty { my $name = shift; my $as_comment = shift || 0; require RT::ScripAction; - my $action = RT::ScripAction->new( $RT::SystemUser ); + my $action = RT::ScripAction->new( RT->SystemUser ); $action->Create( Name => $name, Description => "Created with rt-email-group-admin script", @@ -302,7 +303,7 @@ sub __check_group { my $instance = shift; require RT::Group; - my $obj = RT::Group->new( $RT::SystemUser ); + my $obj = RT::Group->new( RT->SystemUser ); $obj->LoadUserDefinedGroup( $instance ); return $obj->id ? $obj : undef; } @@ -317,7 +318,7 @@ sub __check_user { my $instance = shift; require RT::User; - my $obj = RT::User->new( $RT::SystemUser ); + my $obj = RT::User->new( RT->SystemUser ); $obj->Load( $instance ); return $obj->id ? $obj : undef; } @@ -378,7 +379,7 @@ sub delete { } require RT::Scrips; - my $scrips = RT::Scrips->new( $RT::SystemUser ); + my $scrips = RT::Scrips->new( RT->SystemUser ); $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id ); if ( $scrips->Count ) { my @sid; @@ -451,7 +452,7 @@ sub rename { exit(-1); } - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} ); if ( $actions->Count ) { print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n"; @@ -482,7 +483,7 @@ sub argument_to_list { } sub _get_our_actions { - my $actions = RT::ScripActions->new( $RT::SystemUser ); + my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'ExecModule', VALUE => 'NotifyGroup', diff --git a/rt/sbin/rt-message-catalog b/rt/sbin/rt-message-catalog index dfb8ce3a4..070f6b2f3 100755 --- a/rt/sbin/rt-message-catalog +++ b/rt/sbin/rt-message-catalog @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -51,6 +51,9 @@ use warnings; use Locale::PO; use Getopt::Long; +use File::Temp 'tempdir'; + +use constant PO_DIR => 'share/po'; my %commands = ( stats => { }, @@ -81,7 +84,7 @@ exit; sub stats { my %opt = %{ shift() }; - my $dir = shift || 'lib/RT/I18N'; + my $dir = shift || PO_DIR; my $max = 0; my %res = (); @@ -125,7 +128,7 @@ sub stats { sub shrink { my %opt = %{ shift() }; - my $dir = shift || 'lib/RT/I18N'; + my $dir = shift || PO_DIR; my %keep = map { $_ => 1 } @{ $opt{'keep'} }; @@ -183,12 +186,11 @@ sub clean { sub rosetta { my %opt = %{ shift() }; - my $url = shift or die 'must provide rosseta download url or directory with new po files'; + my $url = shift or die 'must provide Rosetta download url or directory with new po files'; my $dir; - if ( $url =~ m/^[a-z]+:\/\// ) { - require File::Temp; - $dir = File::Temp::tempdir(); + if ( $url =~ m{^[a-z]+://} ) { + $dir = tempdir(); my ($fname) = $url =~ m{([^/]+)$}; print "Downloading $url\n"; @@ -207,7 +209,7 @@ sub rosetta { die "Is not URL or directory: '$url'"; } - my @files = <$dir/rt/*.po>, <$dir/*.po>; + my @files = ( <$dir/rt/*.po>, <$dir/*.po> ); unless ( @files ) { print STDERR "No files in $dir/rt/*.po and $dir/*.po\n"; exit; @@ -221,18 +223,17 @@ sub rosetta { for ( @files ) { my ($lang) = m/([\w_]+)\.po/; - my $fn_orig = "lib/RT/I18N/$lang.po"; + my $fn_orig = PO_DIR . "/$lang.po"; print "$_ -> $fn_orig\n"; # retain the "NOT FOUND IN SOURCE" entries - require File::Temp; my $tmp = File::Temp->new; system("sed -e 's/^#~ //' $_ > $tmp"); my $ext = Locale::Maketext::Extract->new; $ext->read_po($tmp); - my $po_orig = Locale::PO->load_file_ashash( -e $fn_orig? $fn_orig : 'lib/RT/I18N/rt.pot' ); + my $po_orig = Locale::PO->load_file_ashash( -e $fn_orig? $fn_orig : PO_DIR . '/rt.pot' ); # don't want empty vales to override ours. # don't want fuzzy flag as when uploading to rosetta again it's not accepted by rosetta. foreach my $msgid ($ext->msgids) { @@ -264,6 +265,6 @@ sub rosetta { sub extract { shift; - system($^X, 'sbin/extract-message-catalog', @_); + system($^X, 'devel/tools/extract-message-catalog', @_); } diff --git a/rt/sbin/rt-server b/rt/sbin/rt-server index f932ce8b4..78533c6e5 100755 --- a/rt/sbin/rt-server +++ b/rt/sbin/rt-server @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -51,8 +51,14 @@ use strict; # fix lib paths, some may be relative BEGIN { + die <<EOT if ${^TAINT}; +RT does not run under Perl's "taint mode". Remove -T from the command +line, or remove the PerlTaintCheck parameter from your mod_perl +configuration. +EOT + require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -74,16 +80,18 @@ BEGIN { } -use RT; -RT::LoadConfig(); -RT->InitLogging(); -if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - -RT::CheckPerlRequirements(); -RT->InitPluginPaths(); +use Getopt::Long; +no warnings 'once'; -my $port = shift @ARGV || RT->Config->Get('WebPort') || '8080'; +if (grep { m/help/ } @ARGV) { + require Pod::Usage; + print Pod::Usage::pod2usage( { verbose => 2 } ); + exit; +} +require RT; +RT->LoadConfig(); +require Module::Refresh if RT->Config->Get('DevelMode'); require RT::Handle; my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; @@ -107,7 +115,9 @@ EOF require RT::Installer; # don't enter install mode if the file exists but is unwritable if (-e RT::Installer->ConfigFile && !-w _) { - die "Since your configuration exists but is not writable, I'm refusing to do anything.\n"; + die 'Since your configuration exists (' + . RT::Installer->ConfigFile + . ") but is not writable, I'm refusing to do anything.\n"; } RT->Config->Set( 'LexiconLanguages' => '*' ); @@ -115,15 +125,158 @@ EOF RT->InstallMode(1); } else { - RT->ConnectToDatabase(); - RT->InitSystemObjects(); - RT->InitClasses(); - RT->InitPlugins(); + RT->Init(); + + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'post'); + unless ( $status ) { + print STDERR $msg, "\n\n"; + exit -1; + } +} + +# we must disconnect DB before fork +if ($RT::Handle) { + $RT::Handle->dbh(undef); + undef $RT::Handle; +} + +require RT::Interface::Web::Handler; +my $app = RT::Interface::Web::Handler->PSGIApp; + +if ($ENV{RT_TESTING}) { + my $screen_logger = $RT::Logger->remove('screen'); + require Log::Dispatch::Perl; + $RT::Logger->add( + Log::Dispatch::Perl->new( + name => 'rttest', + min_level => $screen_logger->min_level, + action => { + error => 'warn', + critical => 'warn' + } + ) + ); + require Plack::Middleware::Test::StashWarnings; + $app = Plack::Middleware::Test::StashWarnings->wrap($app); +} + +# when used as a psgi file +if (caller) { + return $app; +} + + +# load appropriate server + +require Plack::Runner; + +my $is_fastcgi = $0 =~ m/fcgi$/; +my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : + $is_fastcgi ? ( server => 'FCGI' ) + : (), + env => 'deployment' ); + +# figure out the port +my $port; + +# handle "rt-server 8888" for back-compat, but complain about it +if ($ARGV[0] && $ARGV[0] =~ m/^\d+$/) { + warn "Deprecated: please run $0 --port $ARGV[0] instead\n"; + unshift @ARGV, '--port'; +} + +my @args = @ARGV; + +use List::MoreUtils 'last_index'; +my $last_index = last_index { $_ eq '--port' } @args; + +my $explicit_port; + +if ( $last_index != -1 && $args[$last_index+1] =~ /^\d+$/ ) { + $explicit_port = $args[$last_index+1]; + $port = $explicit_port; + + # inform the rest of the system what port we manually chose + my $old_app = $app; + $app = sub { + my $env = shift; + + $env->{'rt.explicit_port'} = $port; + + $old_app->($env, @_); + }; +} +else { + # default to the configured WebPort and inform Plack::Runner + $port = RT->Config->Get('WebPort') || '8080'; + push @args, '--port', $port; +} + +push @args, '--server', 'Standalone' if RT->InstallMode; +push @args, '--server', 'Starlet' unless $r->{server} || grep { m/--server/ } @args; + +$r->parse_options(@args); + +delete $r->{options} if $is_fastcgi; ### mangle_host_port_socket ruins everything + +unless ($r->{env} eq 'development') { + push @{$r->{options}}, server_ready => sub { + my($args) = @_; + my $name = $args->{server_software} || ref($args); # $args is $server + my $host = $args->{host} || 0; + my $proto = $args->{proto} || 'http'; + print STDERR "$name: Accepting connections at $proto://$host:$args->{port}/\n"; + }; +} +eval { $r->run($app) }; +if (my $err = $@) { + handle_startup_error($err); +} + +exit 0; + +sub handle_startup_error { + my $err = shift; + if ( $err =~ /listen/ ) { + handle_bind_error(); + } else { + die + "Something went wrong while trying to run RT's standalone web server:\n\t" + . $err; + } } -require RT::Interface::Web::Standalone; -my $server = RT::Interface::Web::Standalone->new; -$server->net_server('RT::Interface::Web::Standalone::PreFork'); -$server->port($port); -$server->run(); +sub handle_bind_error { + + print STDERR <<EOF; +WARNING: RT couldn't start up a web server on port @{[$port]}. +This is often the case if the port is already in use or you're running @{[$0]} +as someone other than your system's "root" user. You may also specify a +temporary port with: $0 --port <port> +EOF + + if ($explicit_port) { + print STDERR + "Please check your system configuration or choose another port\n\n"; + } +} + +__END__ + +=head1 NAME + +rt-server - RT standalone server + +=head1 SYNOPSIS + + # runs prefork server listening on port 8080, requires Starlet + rt-server --port 8080 + + # runs server listening on port 8080 + rt-server --server Standalone --port 8080 + # or + standalone_httpd --port 8080 + + # runs other PSGI server on port 8080 + rt-server --server Starman --port 8080 diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in index bf6da6e58..b438202dd 100644 --- a/rt/sbin/rt-server.in +++ b/rt/sbin/rt-server.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -51,6 +51,12 @@ use strict; # fix lib paths, some may be relative BEGIN { + die <<EOT if ${^TAINT}; +RT does not run under Perl's "taint mode". Remove -T from the command +line, or remove the PerlTaintCheck parameter from your mod_perl +configuration. +EOT + require File::Spec; my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); my $bin_path; @@ -74,16 +80,18 @@ BEGIN { } -use RT; -RT::LoadConfig(); -RT->InitLogging(); -if (RT->Config->Get('DevelMode')) { require Module::Refresh; } - -RT::CheckPerlRequirements(); -RT->InitPluginPaths(); +use Getopt::Long; +no warnings 'once'; -my $port = shift @ARGV || RT->Config->Get('WebPort') || '8080'; +if (grep { m/help/ } @ARGV) { + require Pod::Usage; + print Pod::Usage::pod2usage( { verbose => 2 } ); + exit; +} +require RT; +RT->LoadConfig(); +require Module::Refresh if RT->Config->Get('DevelMode'); require RT::Handle; my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; @@ -107,7 +115,9 @@ EOF require RT::Installer; # don't enter install mode if the file exists but is unwritable if (-e RT::Installer->ConfigFile && !-w _) { - die "Since your configuration exists but is not writable, I'm refusing to do anything.\n"; + die 'Since your configuration exists (' + . RT::Installer->ConfigFile + . ") but is not writable, I'm refusing to do anything.\n"; } RT->Config->Set( 'LexiconLanguages' => '*' ); @@ -115,15 +125,158 @@ EOF RT->InstallMode(1); } else { - RT->ConnectToDatabase(); - RT->InitSystemObjects(); - RT->InitClasses(); - RT->InitPlugins(); + RT->Init(); + + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'post'); + unless ( $status ) { + print STDERR $msg, "\n\n"; + exit -1; + } +} + +# we must disconnect DB before fork +if ($RT::Handle) { + $RT::Handle->dbh(undef); + undef $RT::Handle; +} + +require RT::Interface::Web::Handler; +my $app = RT::Interface::Web::Handler->PSGIApp; + +if ($ENV{RT_TESTING}) { + my $screen_logger = $RT::Logger->remove('screen'); + require Log::Dispatch::Perl; + $RT::Logger->add( + Log::Dispatch::Perl->new( + name => 'rttest', + min_level => $screen_logger->min_level, + action => { + error => 'warn', + critical => 'warn' + } + ) + ); + require Plack::Middleware::Test::StashWarnings; + $app = Plack::Middleware::Test::StashWarnings->wrap($app); +} + +# when used as a psgi file +if (caller) { + return $app; +} + + +# load appropriate server + +require Plack::Runner; + +my $is_fastcgi = $0 =~ m/fcgi$/; +my $r = Plack::Runner->new( $0 =~ 'standalone' ? ( server => 'Standalone' ) : + $is_fastcgi ? ( server => 'FCGI' ) + : (), + env => 'deployment' ); + +# figure out the port +my $port; + +# handle "rt-server 8888" for back-compat, but complain about it +if ($ARGV[0] && $ARGV[0] =~ m/^\d+$/) { + warn "Deprecated: please run $0 --port $ARGV[0] instead\n"; + unshift @ARGV, '--port'; +} + +my @args = @ARGV; + +use List::MoreUtils 'last_index'; +my $last_index = last_index { $_ eq '--port' } @args; + +my $explicit_port; + +if ( $last_index != -1 && $args[$last_index+1] =~ /^\d+$/ ) { + $explicit_port = $args[$last_index+1]; + $port = $explicit_port; + + # inform the rest of the system what port we manually chose + my $old_app = $app; + $app = sub { + my $env = shift; + + $env->{'rt.explicit_port'} = $port; + + $old_app->($env, @_); + }; +} +else { + # default to the configured WebPort and inform Plack::Runner + $port = RT->Config->Get('WebPort') || '8080'; + push @args, '--port', $port; +} + +push @args, '--server', 'Standalone' if RT->InstallMode; +push @args, '--server', 'Starlet' unless $r->{server} || grep { m/--server/ } @args; + +$r->parse_options(@args); + +delete $r->{options} if $is_fastcgi; ### mangle_host_port_socket ruins everything + +unless ($r->{env} eq 'development') { + push @{$r->{options}}, server_ready => sub { + my($args) = @_; + my $name = $args->{server_software} || ref($args); # $args is $server + my $host = $args->{host} || 0; + my $proto = $args->{proto} || 'http'; + print STDERR "$name: Accepting connections at $proto://$host:$args->{port}/\n"; + }; +} +eval { $r->run($app) }; +if (my $err = $@) { + handle_startup_error($err); +} + +exit 0; + +sub handle_startup_error { + my $err = shift; + if ( $err =~ /listen/ ) { + handle_bind_error(); + } else { + die + "Something went wrong while trying to run RT's standalone web server:\n\t" + . $err; + } } -require RT::Interface::Web::Standalone; -my $server = RT::Interface::Web::Standalone->new; -$server->net_server('RT::Interface::Web::Standalone::PreFork'); -$server->port($port); -$server->run(); +sub handle_bind_error { + + print STDERR <<EOF; +WARNING: RT couldn't start up a web server on port @{[$port]}. +This is often the case if the port is already in use or you're running @{[$0]} +as someone other than your system's "root" user. You may also specify a +temporary port with: $0 --port <port> +EOF + + if ($explicit_port) { + print STDERR + "Please check your system configuration or choose another port\n\n"; + } +} + +__END__ + +=head1 NAME + +rt-server - RT standalone server + +=head1 SYNOPSIS + + # runs prefork server listening on port 8080, requires Starlet + rt-server --port 8080 + + # runs server listening on port 8080 + rt-server --server Standalone --port 8080 + # or + standalone_httpd --port 8080 + + # runs other PSGI server on port 8080 + rt-server --server Starman --port 8080 diff --git a/rt/sbin/rt-session-viewer b/rt/sbin/rt-session-viewer index 4a9cf09aa..4a9cf09aa 100644..100755 --- a/rt/sbin/rt-session-viewer +++ b/rt/sbin/rt-session-viewer diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index 125708847..2efb9f329 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -76,34 +76,57 @@ BEGIN { } -#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 -BEGIN { - use RT; - RT::LoadConfig(); - RT::InitClasses(); -} - use Term::ReadKey; use Getopt::Long; $| = 1; # unbuffer all output. -my %args; +my %args = ( + dba => '@DB_DBA@', +); GetOptions( \%args, 'action=s', 'force', 'debug', 'dba=s', 'dba-password=s', 'prompt-for-dba-password', - 'datafile=s', 'datadir=s' + 'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s', + 'help|h', ); -unless ( $args{'action'} ) { - help(); - exit(-1); +no warnings 'once'; +if ( $args{help} || ! $args{'action'} ) { + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; +} + +require RT; +RT->LoadConfig(); +RT->InitClasses(); + +# Force warnings to be output to STDERR if we're not already logging +# them at a higher level +RT->Config->Set( LogToScreen => 'warning') + unless ( RT->Config->Get( 'LogToScreen' ) + && RT->Config->Get( 'LogToScreen' ) =~ /^(debug|info|notice)$/ ); + +# get customized root password +my $root_password; +if ( $args{'root-password-file'} ) { + open( my $fh, '<', $args{'root-password-file'} ) + or die "Couldn't open 'args{'root-password-file'}' for reading: $!"; + $root_password = <$fh>; + chomp $root_password; + my $min_length = RT->Config->Get('MinimumPasswordLength'); + if ($min_length) { + die +"password needs to be at least $min_length long, please check file '$args{'root-password-file'}'" + if length $root_password < $min_length; + } + close $fh; } + # check and setup @actions my @actions = grep $_, split /,/, $args{'action'}; if ( @actions > 1 && $args{'datafile'} ) { @@ -124,7 +147,11 @@ foreach ( @actions ) { # convert init to multiple actions my $init = 0; if ( $actions[0] eq 'init' ) { - @actions = qw(create schema acl coredata insert); + if ($args{'skip-create'}) { + @actions = qw(schema coredata insert); + } else { + @actions = qw(create schema acl coredata insert); + } $init = 1; } @@ -154,20 +181,25 @@ my $dba_pass = exists($args{'dba-password'}) ? $args{'dba-password'} : $ENV{'RT_DBA_PASSWORD'}; -if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) { - $dba_pass = get_dba_password(); - chomp $dba_pass if defined($dba_pass); +if ($args{'skip-create'}) { + $dba_user = $db_user; + $dba_pass = $db_pass; +} else { + if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) { + $dba_pass = get_dba_password(); + chomp $dba_pass if defined($dba_pass); + } } print "Working with:\n" ."Type:\t$db_type\nHost:\t$db_host\nName:\t$db_name\n" - ."User:\t$db_user\nDBA:\t$dba_user\n"; + ."User:\t$db_user\nDBA:\t$dba_user" . ($args{'skip-create'} ? ' (No DBA)' : '') . "\n"; foreach my $action ( @actions ) { no strict 'refs'; my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args ); error($action, $msg) unless $status; - print $msg ."\n" if $msg; + print $msg .".\n" if $msg; print "Done.\n"; } @@ -215,35 +247,59 @@ sub action_acl { my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' ); return ($status, $msg) unless $status; - print "Now inserting database ACLs\n"; + print "Now inserting database ACLs.\n"; return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} ); } sub action_coredata { my %args = @_; - $RT::Handle = new RT::Handle; + $RT::Handle = RT::Handle->new; $RT::Handle->dbh( undef ); RT::ConnectToDatabase(); RT::InitLogging(); my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' ); return ($status, $msg) unless $status; - print "Now inserting RT core system objects\n"; + print "Now inserting RT core system objects.\n"; return $RT::Handle->InsertInitialData; } sub action_insert { my %args = @_; - $RT::Handle = new RT::Handle; + $RT::Handle = RT::Handle->new; RT::Init(); my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' ); return ($status, $msg) unless $status; - print "Now inserting data\n"; + print "Now inserting data.\n"; my $file = $args{'datafile'}; $file = $RT::EtcPath . "/initialdata" if $init && !$file; $file ||= $args{'datadir'}."/content"; - return $RT::Handle->InsertData( $file ); + + # Slurp in backcompat + my %removed; + my @back = @{$args{backcompat} || []}; + if (@back) { + my @lines = do {local @ARGV = @back; <>}; + for (@lines) { + s/\#.*//; + next unless /\S/; + my ($class, @fields) = split; + $class->_BuildTableAttributes; + $RT::Logger->debug("Temporarily removing @fields from $class"); + $removed{$class}{$_} = delete $RT::Record::_TABLE_ATTR->{$class}{$_} + for @fields; + } + } + + my @ret = $RT::Handle->InsertData( $file, $root_password ); + + # Put back the fields we chopped off + for my $class (keys %removed) { + $RT::Record::_TABLE_ATTR->{$class}{$_} = $removed{$class}{$_} + for keys %{$removed{$class}}; + } + return @ret; } sub action_upgrade { @@ -262,7 +318,7 @@ sub action_upgrade { $upgrading_from = scalar <STDIN>; chomp $upgrading_from; $upgrading_from =~ s/\s+//g; - } while $upgrading_from !~ /^\d+\.\d+\.\d+$/; + } while $upgrading_from !~ /^\d+\.\d+\.\w+$/; my $upgrading_to = $RT::VERSION; return (0, "The current version $upgrading_to is lower than $upgrading_from") @@ -271,11 +327,16 @@ sub action_upgrade { return (1, "The version $upgrading_to you're upgrading to is up to date") if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0; - my @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to); - - return (1, "No DB changes between $upgrading_from and $upgrading_to") + my @versions = get_versions_from_to($base_dir, $upgrading_from, undef); + return (1, "No DB changes since $upgrading_from") unless @versions; + if (RT::Handle::cmp_version($versions[-1], $upgrading_to) > 0) { + print "\n***** There are upgrades for $versions[-1], which is later than $upgrading_to,\n"; + print "***** which you are nominally upgrading to. Upgrading to $versions[-1] instead.\n"; + $upgrading_to = $versions[-1]; + } + print "\nGoing to apply following upgrades:\n"; print map "* $_\n", @versions; @@ -292,7 +353,7 @@ sub action_upgrade { chomp $custom_upgrading_to; $custom_upgrading_to =~ s/\s+//g; last unless $custom_upgrading_to; - } while $custom_upgrading_to !~ /^\d+\.\d+\.\d+$/; + } while $custom_upgrading_to !~ /^\d+\.\d+\.\w+$/; if ( $custom_upgrading_to ) { return ( @@ -322,17 +383,23 @@ sub action_upgrade { print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n"; _yesno() or exit(-2) unless $args{'force'}; - foreach my $v ( @versions ) { + my ( $ret, $msg ); + foreach my $n ( 0..$#versions ) { + my $v = $versions[$n]; + my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions; print "Processing $v\n"; - my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef); + my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back); if ( -e "$base_dir/$v/schema.$db_type" ) { - action_schema( %tmp ); + ( $ret, $msg ) = action_schema( %tmp ); + return ( $ret, $msg ) unless $ret; } if ( -e "$base_dir/$v/acl.$db_type" ) { - action_acl( %tmp ); + ( $ret, $msg ) = action_acl( %tmp ); + return ( $ret, $msg ) unless $ret; } if ( -e "$base_dir/$v/content" ) { - action_insert( %tmp ); + ( $ret, $msg ) = action_insert( %tmp ); + return ( $ret, $msg ) unless $ret; } } return 1; @@ -346,7 +413,7 @@ sub get_versions_from_to { closedir $dh; return - grep RT::Handle::cmp_version($_, $to) <= 0, + grep defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1, grep RT::Handle::cmp_version($_, $from) > 0, sort RT::Handle::cmp_version @versions; } @@ -372,13 +439,10 @@ sub get_dba_password { return ($password); } -=head2 get_system_dbh - -Returns L<DBI> database handle connected to B<system> with DBA credentials. +# get_system_dbh +# Returns L<DBI> database handle connected to B<system> with DBA credentials. +# See also L<RT::Handle/SystemDSN>. -See also L<RT::Handle/SystemDSN>. - -=cut sub get_system_dbh { return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass ); @@ -388,13 +452,11 @@ sub get_admin_dbh { return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass ); } -=head2 get_rt_dbh [USER, PASSWORD] - -Returns L<DBI> database handle connected to RT database, -you may specify credentials(USER and PASSWORD) to connect -with. By default connects with credentials from RT config. +# get_rt_dbh [USER, PASSWORD] -=cut +# Returns L<DBI> database handle connected to RT database, +# you may specify credentials(USER and PASSWORD) to connect +# with. By default connects with credentials from RT config. sub get_rt_dbh { return _get_dbh( RT::Handle->DSN, $db_user, $db_pass ); @@ -423,54 +485,106 @@ sub _yesno { $x =~ /^y/i; } -sub help { +1; - print <<EOF; +__END__ -$0: Set up RT's database +=head1 NAME ---action init Initialize the database. This is combination of - multiple actions listed below. Create DB, schema, - setup acl, insert core data and initial data. +rt-setup-database - Set up RT's database - upgrade Apply all needed schema/acl/content updates (will ask - for version to upgrade from) +=head1 SYNOPSIS - create Create the database. + rt-setup-database --action ... - drop Drop the database. - This will ERASE ALL YOUR DATA +=head1 OPTIONS - schema Initialize only the database schema - To use a local or supplementary datafile, specify it - using the '--datadir' option below. +=over - acl Initialize only the database ACLs - To use a local or supplementary datafile, specify it - using the '--datadir' option below. +=item action - coredata Insert data into RT's database. This data is required - for normal functioning of any RT instance. +Several actions can be combined using comma separated list. - 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. +=over -Several actions can be combined using comma separated list. +=item init ---datafile /path/to/datafile ---datadir /path/to/ Used to specify a path to find the local - database schema and acls to be installed. +Initialize the database. This is combination of multiple actions listed below. +Create DB, schema, setup acl, insert core data and initial data. +=item upgrade ---dba dba's username ---dba-password dba's password ---prompt-for-dba-password Ask for the database administrator's password interactively +Apply all needed schema/acl/content updates (will ask for version to upgrade +from) +=item create -EOF +Create the database. -} +=item drop -1; +Drop the database. This will B<ERASE ALL YOUR DATA>. + +=item schema + +Initialize only the database schema + +To use a local or supplementary datafile, specify it using the '--datadir' +option below. + +=item acl + +Initialize only the database ACLs + +To use a local or supplementary datafile, specify it using the '--datadir' +option below. + +=item coredata + +Insert data into RT's database. This data is required for normal functioning of +any RT instance. + +=item 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. + +=back + +=item datafile + +file path of the data you want to action on + +e.g. C<--datafile /path/to/datafile> + +=item datadir + +Used to specify a path to find the local database schema and acls to be +installed. + +e.g. C<--datadir /path/to/> + +=item dba + +dba's username + +=item dba-password + +dba's password + +=item prompt-for-dba-password + +Ask for the database administrator's password interactively + +=item skip-create + +for 'init': skip creating the database and the user account, so we don't need +administrator privileges + +=item root-password-file + +for 'init' and 'insert': rather than using the default administrative password +for RT's "root" user, use the password in this file. + +=back diff --git a/rt/sbin/rt-shredder b/rt/sbin/rt-shredder index 3a9db9d24..7c577bfa9 100755 --- a/rt/sbin/rt-shredder +++ b/rt/sbin/rt-shredder @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -113,7 +113,7 @@ use warnings FATAL => 'all'; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -147,7 +147,7 @@ our %opt; parse_args(); RT::Shredder::Init( %opt ); -my $shredder = new RT::Shredder; +my $shredder = RT::Shredder->new; { my $plugin = eval { $shredder->AddDumpPlugin( Arguments => { @@ -257,7 +257,7 @@ sub process_plugins my @res; foreach my $str( @{ $opt{'plugin'} } ) { - my $plugin = new RT::Shredder::Plugin; + my $plugin = RT::Shredder::Plugin->new; my( $status, $msg ) = $plugin->LoadByString( $str ); unless( $status ) { print STDERR "Couldn't load plugin\n"; diff --git a/rt/sbin/rt-shredder.in b/rt/sbin/rt-shredder.in index 946121c75..c52116f82 100755 --- a/rt/sbin/rt-shredder.in +++ b/rt/sbin/rt-shredder.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -147,7 +147,7 @@ our %opt; parse_args(); RT::Shredder::Init( %opt ); -my $shredder = new RT::Shredder; +my $shredder = RT::Shredder->new; { my $plugin = eval { $shredder->AddDumpPlugin( Arguments => { @@ -257,7 +257,7 @@ sub process_plugins my @res; foreach my $str( @{ $opt{'plugin'} } ) { - my $plugin = new RT::Shredder::Plugin; + my $plugin = RT::Shredder::Plugin->new; my( $status, $msg ) = $plugin->LoadByString( $str ); unless( $status ) { print STDERR "Couldn't load plugin\n"; diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in index a964e2912..ebea86c66 100644 --- a/rt/sbin/rt-test-dependencies.in +++ b/rt/sbin/rt-test-dependencies.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -60,42 +60,49 @@ GetOptions( \%args, 'v|verbose', 'install', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', - 'with-ORACLE', 'with-FASTCGI', 'with-FASTCGI-SERVER', - 'with-SPEEDYCGI', 'with-MODPERL1', - 'with-MODPERL2', 'with-DEV', + 'with-ORACLE', 'with-FASTCGI', + 'with-MODPERL1', 'with-MODPERL2', 'with-STANDALONE', + 'with-DEV', + 'with-GPG', 'with-ICAL', 'with-SMTP', 'with-GRAPHVIZ', 'with-GD', 'with-DASHBOARDS', + 'with-USERLOGO', + 'with-SSL-MAILGATE', 'download=s', 'repository=s', - 'list-deps' + 'list-deps', + 'help|h', ); -unless (keys %args) { - help(); - exit(1); +if ( $args{help} ) { + require Pod::Usage; + Pod::Usage::pod2usage( { verbose => 2 } ); + exit; } # Set up defaults my %default = ( 'with-MASON' => 1, + 'with-PSGI' => 0, 'with-CORE' => 1, 'with-CLI' => 1, 'with-MAILGATE' => 1, 'with-DEV' => @RT_DEVEL_MODE@, - 'with-STANDALONE' => 1, 'with-GPG' => @RT_GPG@, 'with-ICAL' => 1, 'with-SMTP' => 1, 'with-GRAPHVIZ' => @RT_GRAPHVIZ@, 'with-GD' => @RT_GD@, - 'with-DASHBOARDS' => 1 + 'with-DASHBOARDS' => 1, + 'with-USERLOGO' => 1, + 'with-SSL-MAILGATE' => @RT_SSL_MAILGATE@, ); $args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default; @@ -156,44 +163,6 @@ sub conclude { } } - -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-oracle Database interface for Oracle - --with-sqlite Database interface and driver for SQLite (unsupported) - - --with-standalone Libraries needed to support the standalone simple pure perl server - --with-fastcgi-server Libraries needed to support the external fastcgi 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] ) { @@ -206,18 +175,21 @@ sub text_to_hash { } $deps{'CORE'} = [ text_to_hash( << '.') ]; +Class::Accessor 0.34 +DateTime 0.44 +DateTime::Locale 0.40 Digest::base Digest::MD5 2.27 Digest::SHA DBI 1.37 Class::ReturnValue 0.40 -DBIx::SearchBuilder 1.54 +DBIx::SearchBuilder 1.59 Text::Template 1.44 File::ShareDir File::Spec 0.8 -HTML::Entities +HTML::Quoted HTML::Scrubber 0.08 -Log::Dispatch 2.0 +Log::Dispatch 2.23 Sys::Syslog 0.16 Locale::Maketext 1.06 Locale::Maketext::Lexicon 0.32 @@ -236,15 +208,20 @@ Regexp::Common Scalar::Util Module::Versions::Report 1.05 Cache::Simple::TimedExpiry -Calendar::Simple -Encode 2.21 +Encode 2.39 CSS::Squish 0.06 File::Glob Devel::StackTrace 1.19 +Text::Password::Pronounceable +Devel::GlobalDestruction +List::MoreUtils +Net::CIDR +Regexp::Common::net::CIDR +Regexp::IPv6 . $deps{'MASON'} = [ text_to_hash( << '.') ]; -HTML::Mason 1.36 +HTML::Mason 1.43 Errno Digest::MD5 2.27 CGI::Cookie 1.20 @@ -254,12 +231,17 @@ XML::RSS 1.05 Text::WikiFormat 0.76 CSS::Squish 0.06 Devel::StackTrace 1.19 +JSON +IPC::Run3 . -$deps{'STANDALONE'} = [ text_to_hash( << '.') ]; -HTTP::Server::Simple 0.34 -HTTP::Server::Simple::Mason 0.14 -Net::Server +$deps{'PSGI'} = [ text_to_hash( << '.') ]; +CGI 3.38 +CGI::PSGI 0.12 +HTML::Mason::PSGIHandler 0.52 +Plack 0.9971 +Plack::Handler::Starlet +CGI::Emulate::PSGI . $deps{'MAILGATE'} = [ text_to_hash( << '.') ]; @@ -270,6 +252,14 @@ LWP::UserAgent Pod::Usage . +$deps{'SSL-MAILGATE'} = [ text_to_hash( << '.') ]; +Crypt::SSLeay +Net::SSL +LWP::UserAgent 6.0 +LWP::Protocol::https +Mozilla::CA +. + $deps{'CLI'} = [ text_to_hash( << '.') ]; Getopt::Long 2.24 LWP @@ -280,57 +270,39 @@ Term::ReadKey . $deps{'DEV'} = [ text_to_hash( << '.') ]; +Email::Abstract +Test::Email HTML::Form HTML::TokeParser -WWW::Mechanize -Test::WWW::Mechanize 1.04 +WWW::Mechanize 1.52 +Test::WWW::Mechanize 1.30 Module::Refresh 0.03 Test::Expect 0.31 XML::Simple File::Find Test::Deep 0 # needed for shredder tests String::ShellQuote 0 # needed for gnupg-incoming.t -Test::HTTP::Server::Simple 0.09 -Test::HTTP::Server::Simple::StashWarnings 0.02 Log::Dispatch::Perl Test::Warn -Test::Builder 0.77 # needed to fix TODO test -IPC::Run3 +Test::Builder 0.90 # needed for is_passing Test::MockTime -HTTP::Server::Simple::Mason 0.13 Log::Dispatch::Perl +Test::WWW::Mechanize::PSGI +Plack::Middleware::Test::StashWarnings +Test::LongString . $deps{'FASTCGI'} = [ text_to_hash( << '.') ]; -CGI 3.38 -FCGI 0.74 -CGI::Fast -. - -$deps{'FASTCGI-SERVER'} = [ text_to_hash( << '.') ]; -CGI 3.38 -CGI::Fast +FCGI FCGI::ProcManager -File::Basename -File::Spec -Getopt::Long -Pod::Usage . -$deps{'SPEEDYCGI'} = [ text_to_hash( << '.') ]; -CGI 3.38 -CGI::SpeedyCGI -. - - $deps{'MODPERL1'} = [ text_to_hash( << '.') ]; -CGI 3.38 Apache::Request Apache::DBI 0.92 . $deps{'MODPERL2'} = [ text_to_hash( << '.') ]; -CGI 3.38 Apache::DBI HTML::Mason 1.36 . @@ -365,14 +337,13 @@ Net::SMTP . $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ]; -HTML::RewriteAttributes 0.02 +HTML::RewriteAttributes 0.04 MIME::Types . $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ]; GraphViz IPC::Run -IPC::Run::SafeHandles . $deps{'GD'} = [ text_to_hash( << '.') ]; @@ -381,6 +352,10 @@ GD::Graph GD::Text . +$deps{'USERLOGO'} = [ text_to_hash( << '.') ]; +Convert::Color +. + my %AVOID = ( 'DBD::Oracle' => [qw(1.23)], ); @@ -396,7 +371,7 @@ check_users(); my %Missing_By_Type = (); foreach my $type (sort grep $args{$_}, keys %args) { - next unless ($type =~ /^with-(.*?)$/); + next unless ($type =~ /^with-(.*?)$/) and $deps{$1}; $type = $1; section("$type dependencies"); @@ -409,6 +384,16 @@ foreach my $type (sort grep $args{$_}, keys %args) { if ( $args{'install'} ) { for my $module (keys %missing) { resolve_dep($module, $missing{$module}{version}); + my $m = $module . '.pm'; + $m =~ s!::!/!g; + if ( delete $INC{$m} ) { + my $symtab = $module . '::'; + no strict 'refs'; + for my $symbol ( keys %{$symtab} ) { + next if substr( $symbol, -2, 2 ) eq '::'; + delete $symtab->{$symbol}; + } + } delete $missing{$module} if test_dep($module, $missing{$module}{version}, $AVOID{$module}); } @@ -474,7 +459,7 @@ sub resolve_dep { print "\nInstall module $module\n"; - my $ext = $ENV{'RT_FIX_DEPS_CMD'}; + my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'}; unless( $ext ) { my $configured = 1; { @@ -596,6 +581,81 @@ sub check_users { print_found("web group (@WEB_GROUP@)", defined getgrnam("@WEB_GROUP@")); } +1; +__END__ + +=head1 NAME + +rt-test-dependencies - test rt's dependencies + +=head1 SYNOPSIS + + rt-test-dependencies + rt-test-dependencies --install + rt-test-dependencies --with-mysql --with-fastcgi + +=head1 DESCRIPTION + +by default, C<rt-test-dependencies> determines whether you have installed all +the perl modules RT needs to run. + +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. + +=head1 OPTIONS + +=over + +=item install + + install missing modules + +=item verbose + +list the status of all dependencies, rather than just the missing ones. + +-v is equal to --verbose + +=item specify dependencies + +=over + +=item --with-mysql + + database interface for mysql + +=item --with-postgresql + + database interface for postgresql + +=item with-oracle + + database interface for oracle + +=item with-sqlite + + database interface and driver for sqlite (unsupported) + +=item with-fastcgi + + libraries needed to support the fastcgi handler + +=item with-modperl1 + + libraries needed to support the modperl 1 handler + +=item with-modperl2 + + libraries needed to support the modperl 2 handler + +=item with-dev + + tools needed for RT development + +=back + +=back -1; diff --git a/rt/sbin/rt-validator b/rt/sbin/rt-validator index d0ba1a71e..9ef191cbe 100755 --- a/rt/sbin/rt-validator +++ b/rt/sbin/rt-validator @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -52,7 +52,7 @@ use warnings; # fix lib paths, some may be relative BEGIN { require File::Spec; - my @libs = ("lib", "local/lib"); + my @libs = ("/opt/rt3/lib", "/opt/rt3/local/lib"); my $bin_path; for my $lib (@libs) { @@ -82,35 +82,17 @@ GetOptions( 'resolve', 'force', 'verbose|v', + 'help|h', ); -usage() unless $opt{'check'}; -usage_warning() if $opt{'resolve'} && !$opt{'force'}; - -sub usage { - print STDERR <<END; -Usage: $0 options - -Options: - - $0 --check - $0 --check --verbose - $0 --check --verbose --resolve - $0 --check --verbose --resolve --force - ---check - is mandatory argument, you can use -c, as well. ---verbose - print additional info to STDOUT ---resolve - enable resolver that can delete or create some records ---force - resolve without asking questions - -Description: +if ( $opt{help} || !$opt{check} ) { + require Pod::Usage; + print Pod::Usage::pod2usage( { verbose => 2 } ); + exit; +} -This script checks integrity of records in RT's DB. May delete some invalid -records or ressurect accidentally deleted. +usage_warning() if $opt{'resolve'} && !$opt{'force'}; -END - exit 1; -} sub usage_warning { print <<END; @@ -198,6 +180,9 @@ $redo_on{'Create'} = { GroupMembers => [ 'CGM vs. GM' ], CachedGroupMembers => [ 'CGM vs. GM' ], }; +$redo_on{'Update'} = { + Groups => ['User Defined Group Name uniqueness'], +}; my %describe_cb; %describe_cb = ( @@ -218,7 +203,7 @@ sub m2t($) { my $model = shift; return $cache{$model} if $cache{$model}; my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); return $cache{$model} = $object->Table; } } @@ -227,9 +212,9 @@ my (@do_check, %redo_check); my @CHECKS; foreach my $table ( qw(Users Groups) ) { push @CHECKS, "$table -> Principals" => sub { - my $msg = "A record in $table refers not existing record in Principals." - ." The script can either create missing record in Principals" - ." or delete record in $table."; + my $msg = "A record in $table refers to a nonexistent record in Principals." + ." The script can either create the missing record in Principals" + ." or delete the record in $table."; my ($type) = ($table =~ /^(.*)s$/); check_integrity( $table, 'id' => 'Principals', 'id', @@ -255,9 +240,9 @@ foreach my $table ( qw(Users Groups) ) { }; push @CHECKS, "Principals -> $table" => sub { - my $msg = "A record in Principals refers not existing record in $table." - ." In some cases it's possible to resurrect manually such records," - ." but this utility can only delete"; + my $msg = "A record in Principals refers to a nonexistent record in $table." + ." In some cases it's possible to manually resurrect such records," + ." but this utility can only delete records."; check_integrity( 'Principals', 'id' => $table, 'id', @@ -330,7 +315,7 @@ push @CHECKS, 'Queues <-> Role Groups' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found role group of not existant queue." + 'Delete', "Found a role group of a nonexistent queue." ); delete_record( 'Groups', $id ); @@ -355,7 +340,7 @@ push @CHECKS, 'Tickets <-> Role Groups' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a role group of not existant ticket." + 'Delete', "Found a role group of a nonexistent ticket." ); delete_record( 'Groups', $id ); @@ -374,11 +359,41 @@ push @CHECKS, 'Role Groups (Instance, Type) uniqueness' => sub { ); }; +push @CHECKS, 'System internal group uniqueness' => sub { + check_uniqueness( + 'Groups', + columns => ['Instance', 'Type'], + condition => '.Domain = ?', + bind_values => [ 'SystemInternal' ], + ); +}; + +# CHECK that user defined group names are unique +push @CHECKS, 'User Defined Group Name uniqueness' => sub { + check_uniqueness( + 'Groups', + columns => ['Name'], + condition => '.Domain = ?', + bind_values => [ 'UserDefined' ], + extra_tables => ['Principals sp', 'Principals tp'], + extra_condition => join(" and ", map { "$_.id = ${_}p.ObjectId and ${_}p.PrincipalType = ? and ${_}p.Disabled != 1" } qw(s t)), + extra_values => ['Group', 'Group'], + action => sub { + return unless prompt( + 'Rename', "Found a user defined group with a non-unique Name." + ); + + my $id = shift; + my %cols = @_; + update_records('Groups', { id => $id }, { Name => join('-', $cols{'Name'}, $id) }); + }, + ); +}; push @CHECKS, 'GMs -> Groups, Members' => sub { my $msg = "A record in GroupMembers references an object that doesn't exist." - ." May be you deleted a group or principal directly from DB?" - ." Usually it's ok to delete such records."; + ." Maybe you deleted a group or principal directly from the database?" + ." Usually it's OK to delete such records."; check_integrity( 'GroupMembers', 'GroupId' => 'Groups', 'id', action => sub { @@ -413,7 +428,7 @@ push @CHECKS, 'CGM vs. GM' => sub { "Found a record in GroupMembers that has no direct duplicate in CachedGroupMembers table." ); - my $gm = RT::GroupMember->new( $RT::SystemUser ); + my $gm = RT::GroupMember->new( RT->SystemUser ); $gm->Load( $id ); die "Couldn't load GM record #$id" unless $gm->id; my $cgm = create_record( 'CachedGroupMembers', @@ -434,7 +449,7 @@ push @CHECKS, 'CGM vs. GM' => sub { return unless prompt( 'Delete', "Found a record in CachedGroupMembers for a (Group, Member) pair" - ." that doesn't exist in GroupMembers table." + ." that doesn't exist in the GroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -453,7 +468,7 @@ push @CHECKS, 'CGM vs. GM' => sub { ." duplicate in CachedGroupMembers table." ); - my $g = RT::Group->new( $RT::SystemUser ); + my $g = RT::Group->new( RT->SystemUser ); $g->Load( $id ); die "Couldn't load group #$id" unless $g->id; die "Loaded group by $id has id ". $g->id unless $g->id == $id; @@ -490,7 +505,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers with Via referencing not existing record." + "Found a record in CachedGroupMembers with Via that references a nonexistent record." ); delete_record( 'CachedGroupMembers', $id ); @@ -508,7 +523,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." + "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -525,7 +540,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." + "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -581,7 +596,7 @@ push @CHECKS, 'Tickets -> other' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a ticket that's been merged into a ticket that don't exist anymore." + "Found a ticket that's been merged into a ticket that no longer exists." ); delete_record( 'Tickets', $id ); @@ -627,8 +642,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction regarding Owner changes," + ." but the User with id stored in OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -641,8 +656,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction regarding Owner changes," + ." but the User with id stored in NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -656,8 +671,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction describing watcher changes," + ." but the User with id stored in OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -671,8 +686,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction describing watcher changes," + ." but the User with id stored in NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -701,8 +716,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction describing a queue change," + ." but the Queue with id stored in the NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -715,8 +730,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction describing a queue change," + ." but the Queue with id stored in the OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -814,7 +829,7 @@ push @CHECKS, 'FIX: LastUpdatedBy and Creator' => sub { my %fix = (); foreach my $model ( @models ) { my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); foreach my $column ( qw(LastUpdatedBy Creator) ) { next unless $object->_Accessible( $column, 'auto' ); @@ -867,7 +882,7 @@ END push @CHECKS, 'LastUpdatedBy and Creator' => sub { foreach my $model ( @models ) { my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); my $table = $object->Table; foreach my $column ( qw(LastUpdatedBy Creator) ) { next unless $object->_Accessible( $column, 'auto' ); @@ -941,7 +956,7 @@ sub check_integrity { my $sth = execute_query( $query, @binds ); while ( my ($sid, @set) = $sth->fetchrow_array ) { - print STDERR "Record #$sid in $stable references not existent record in $ttable\n"; + print STDERR "Record #$sid in $stable references a nonexistent record in $ttable\n"; for ( my $i = 0; $i < @scols; $i++ ) { print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n"; } @@ -984,7 +999,7 @@ sub check_uniqueness { my @columns = @{ $args{'columns'} }; print "Checking uniqueness of ( ", join(', ', map "'$_'", @columns )," ) in table '$on'\n" - if $opt{'versbose'}; + if $opt{'verbose'}; my ($scond, $tcond); if ( $scond = $tcond = $args{'condition'} ) { @@ -996,19 +1011,23 @@ sub check_uniqueness { ." FROM $on s LEFT JOIN $on t " ." ON s.id != t.id AND ". join(' AND ', map "s.$_ = t.$_", @columns) . ($tcond? " AND ( $tcond )": "") + . ($args{'extra_tables'} ? join(", ", "", @{$args{'extra_tables'}}) : "") ." WHERE t.id IS NOT NULL " ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @columns); $query .= " AND ( $scond )" if $scond; + $query .= " AND ( $args{'extra_condition'} )" if $args{'extra_condition'}; my $sth = execute_query( $query, - $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): () + $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): (), + $args{'extra_values'}? (@{ $args{'extra_values'} }): () ); while ( my ($sid, $tid, @set) = $sth->fetchrow_array ) { print STDERR "Record #$tid in $on has the same set of values as $sid\n"; for ( my $i = 0; $i < @columns; $i++ ) { print STDERR "\t$columns[$i] => '$set[$i]'\n"; } + $args{'action'}->( $tid, map { $columns[$_] => $set[$_] } (0 .. (@columns-1)) ) if $args{'action'}; } } @@ -1117,3 +1136,47 @@ sub prompt_integer { } } 1; + +__END__ + +=head1 NAME + +rt-validator - check and correct validity of records in RT's database + +=head1 SYNOPSIS + + rt-validator --check + rt-validator --check --verbose + rt-validator --check --verbose --resolve + rt-validator --check --verbose --resolve --force + +=head1 DESCRIPTION + +This script checks integrity of records in RT's DB. May delete some invalid +records or ressurect accidentally deleted. + +=head1 OPTIONS + +=over + +=item check + + mandatory. + + it's equall to -c + +=item verbose + + print additional info to STDOUT + it's equall to -v + +=item resolve + + enable resolver that can delete or create some records + +=item force + + resolve without asking questions + +=back + diff --git a/rt/sbin/rt-validator.in b/rt/sbin/rt-validator.in index 9f8ff2927..331d5f379 100644 --- a/rt/sbin/rt-validator.in +++ b/rt/sbin/rt-validator.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -82,35 +82,17 @@ GetOptions( 'resolve', 'force', 'verbose|v', + 'help|h', ); -usage() unless $opt{'check'}; -usage_warning() if $opt{'resolve'} && !$opt{'force'}; - -sub usage { - print STDERR <<END; -Usage: $0 options - -Options: - - $0 --check - $0 --check --verbose - $0 --check --verbose --resolve - $0 --check --verbose --resolve --force - ---check - is mandatory argument, you can use -c, as well. ---verbose - print additional info to STDOUT ---resolve - enable resolver that can delete or create some records ---force - resolve without asking questions - -Description: +if ( $opt{help} || !$opt{check} ) { + require Pod::Usage; + print Pod::Usage::pod2usage( { verbose => 2 } ); + exit; +} -This script checks integrity of records in RT's DB. May delete some invalid -records or ressurect accidentally deleted. +usage_warning() if $opt{'resolve'} && !$opt{'force'}; -END - exit 1; -} sub usage_warning { print <<END; @@ -198,6 +180,9 @@ $redo_on{'Create'} = { GroupMembers => [ 'CGM vs. GM' ], CachedGroupMembers => [ 'CGM vs. GM' ], }; +$redo_on{'Update'} = { + Groups => ['User Defined Group Name uniqueness'], +}; my %describe_cb; %describe_cb = ( @@ -218,7 +203,7 @@ sub m2t($) { my $model = shift; return $cache{$model} if $cache{$model}; my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); return $cache{$model} = $object->Table; } } @@ -227,9 +212,9 @@ my (@do_check, %redo_check); my @CHECKS; foreach my $table ( qw(Users Groups) ) { push @CHECKS, "$table -> Principals" => sub { - my $msg = "A record in $table refers not existing record in Principals." - ." The script can either create missing record in Principals" - ." or delete record in $table."; + my $msg = "A record in $table refers to a nonexistent record in Principals." + ." The script can either create the missing record in Principals" + ." or delete the record in $table."; my ($type) = ($table =~ /^(.*)s$/); check_integrity( $table, 'id' => 'Principals', 'id', @@ -255,9 +240,9 @@ foreach my $table ( qw(Users Groups) ) { }; push @CHECKS, "Principals -> $table" => sub { - my $msg = "A record in Principals refers not existing record in $table." - ." In some cases it's possible to resurrect manually such records," - ." but this utility can only delete"; + my $msg = "A record in Principals refers to a nonexistent record in $table." + ." In some cases it's possible to manually resurrect such records," + ." but this utility can only delete records."; check_integrity( 'Principals', 'id' => $table, 'id', @@ -330,7 +315,7 @@ push @CHECKS, 'Queues <-> Role Groups' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found role group of not existant queue." + 'Delete', "Found a role group of a nonexistent queue." ); delete_record( 'Groups', $id ); @@ -355,7 +340,7 @@ push @CHECKS, 'Tickets <-> Role Groups' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a role group of not existant ticket." + 'Delete', "Found a role group of a nonexistent ticket." ); delete_record( 'Groups', $id ); @@ -374,11 +359,41 @@ push @CHECKS, 'Role Groups (Instance, Type) uniqueness' => sub { ); }; +push @CHECKS, 'System internal group uniqueness' => sub { + check_uniqueness( + 'Groups', + columns => ['Instance', 'Type'], + condition => '.Domain = ?', + bind_values => [ 'SystemInternal' ], + ); +}; + +# CHECK that user defined group names are unique +push @CHECKS, 'User Defined Group Name uniqueness' => sub { + check_uniqueness( + 'Groups', + columns => ['Name'], + condition => '.Domain = ?', + bind_values => [ 'UserDefined' ], + extra_tables => ['Principals sp', 'Principals tp'], + extra_condition => join(" and ", map { "$_.id = ${_}p.ObjectId and ${_}p.PrincipalType = ? and ${_}p.Disabled != 1" } qw(s t)), + extra_values => ['Group', 'Group'], + action => sub { + return unless prompt( + 'Rename', "Found a user defined group with a non-unique Name." + ); + + my $id = shift; + my %cols = @_; + update_records('Groups', { id => $id }, { Name => join('-', $cols{'Name'}, $id) }); + }, + ); +}; push @CHECKS, 'GMs -> Groups, Members' => sub { my $msg = "A record in GroupMembers references an object that doesn't exist." - ." May be you deleted a group or principal directly from DB?" - ." Usually it's ok to delete such records."; + ." Maybe you deleted a group or principal directly from the database?" + ." Usually it's OK to delete such records."; check_integrity( 'GroupMembers', 'GroupId' => 'Groups', 'id', action => sub { @@ -413,7 +428,7 @@ push @CHECKS, 'CGM vs. GM' => sub { "Found a record in GroupMembers that has no direct duplicate in CachedGroupMembers table." ); - my $gm = RT::GroupMember->new( $RT::SystemUser ); + my $gm = RT::GroupMember->new( RT->SystemUser ); $gm->Load( $id ); die "Couldn't load GM record #$id" unless $gm->id; my $cgm = create_record( 'CachedGroupMembers', @@ -434,7 +449,7 @@ push @CHECKS, 'CGM vs. GM' => sub { return unless prompt( 'Delete', "Found a record in CachedGroupMembers for a (Group, Member) pair" - ." that doesn't exist in GroupMembers table." + ." that doesn't exist in the GroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -453,7 +468,7 @@ push @CHECKS, 'CGM vs. GM' => sub { ." duplicate in CachedGroupMembers table." ); - my $g = RT::Group->new( $RT::SystemUser ); + my $g = RT::Group->new( RT->SystemUser ); $g->Load( $id ); die "Couldn't load group #$id" unless $g->id; die "Loaded group by $id has id ". $g->id unless $g->id == $id; @@ -490,7 +505,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers with Via referencing not existing record." + "Found a record in CachedGroupMembers with Via that references a nonexistent record." ); delete_record( 'CachedGroupMembers', $id ); @@ -508,7 +523,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." + "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -525,7 +540,7 @@ push @CHECKS, 'CGM vs. GM' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table." + "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table." ); delete_record( 'CachedGroupMembers', $id ); @@ -581,7 +596,7 @@ push @CHECKS, 'Tickets -> other' => sub { my $id = shift; return unless prompt( 'Delete', - "Found a ticket that's been merged into a ticket that don't exist anymore." + "Found a ticket that's been merged into a ticket that no longer exists." ); delete_record( 'Tickets', $id ); @@ -627,8 +642,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction regarding Owner changes," + ." but the User with id stored in OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -641,8 +656,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction regarding changes of Owner," - ." but User with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction regarding Owner changes," + ." but the User with id stored in NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -656,8 +671,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction describing watcher changes," + ." but the User with id stored in OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -671,8 +686,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing watchers change," - ." but User with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction describing watcher changes," + ." but the User with id stored in NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -701,8 +716,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in NewValue column doesn't exist anymore." + 'Delete', "Found a transaction describing a queue change," + ." but the Queue with id stored in the NewValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -715,8 +730,8 @@ push @CHECKS, 'Transactions -> other' => sub { action => sub { my $id = shift; return unless prompt( - 'Delete', "Found a transaction describing queue change," - ." but Queue with id stored in OldValue column doesn't exist anymore." + 'Delete', "Found a transaction describing a queue change," + ." but the Queue with id stored in the OldValue column doesn't exist anymore." ); delete_record( 'Transactions', $id ); @@ -814,7 +829,7 @@ push @CHECKS, 'FIX: LastUpdatedBy and Creator' => sub { my %fix = (); foreach my $model ( @models ) { my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); foreach my $column ( qw(LastUpdatedBy Creator) ) { next unless $object->_Accessible( $column, 'auto' ); @@ -867,7 +882,7 @@ END push @CHECKS, 'LastUpdatedBy and Creator' => sub { foreach my $model ( @models ) { my $class = "RT::$model"; - my $object = $class->new( $RT::SystemUser ); + my $object = $class->new( RT->SystemUser ); my $table = $object->Table; foreach my $column ( qw(LastUpdatedBy Creator) ) { next unless $object->_Accessible( $column, 'auto' ); @@ -941,7 +956,7 @@ sub check_integrity { my $sth = execute_query( $query, @binds ); while ( my ($sid, @set) = $sth->fetchrow_array ) { - print STDERR "Record #$sid in $stable references not existent record in $ttable\n"; + print STDERR "Record #$sid in $stable references a nonexistent record in $ttable\n"; for ( my $i = 0; $i < @scols; $i++ ) { print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n"; } @@ -984,7 +999,7 @@ sub check_uniqueness { my @columns = @{ $args{'columns'} }; print "Checking uniqueness of ( ", join(', ', map "'$_'", @columns )," ) in table '$on'\n" - if $opt{'versbose'}; + if $opt{'verbose'}; my ($scond, $tcond); if ( $scond = $tcond = $args{'condition'} ) { @@ -996,19 +1011,23 @@ sub check_uniqueness { ." FROM $on s LEFT JOIN $on t " ." ON s.id != t.id AND ". join(' AND ', map "s.$_ = t.$_", @columns) . ($tcond? " AND ( $tcond )": "") + . ($args{'extra_tables'} ? join(", ", "", @{$args{'extra_tables'}}) : "") ." WHERE t.id IS NOT NULL " ." AND ". join(' AND ', map "s.$_ IS NOT NULL", @columns); $query .= " AND ( $scond )" if $scond; + $query .= " AND ( $args{'extra_condition'} )" if $args{'extra_condition'}; my $sth = execute_query( $query, - $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): () + $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): (), + $args{'extra_values'}? (@{ $args{'extra_values'} }): () ); while ( my ($sid, $tid, @set) = $sth->fetchrow_array ) { print STDERR "Record #$tid in $on has the same set of values as $sid\n"; for ( my $i = 0; $i < @columns; $i++ ) { print STDERR "\t$columns[$i] => '$set[$i]'\n"; } + $args{'action'}->( $tid, map { $columns[$_] => $set[$_] } (0 .. (@columns-1)) ) if $args{'action'}; } } @@ -1117,3 +1136,47 @@ sub prompt_integer { } } 1; + +__END__ + +=head1 NAME + +rt-validator - check and correct validity of records in RT's database + +=head1 SYNOPSIS + + rt-validator --check + rt-validator --check --verbose + rt-validator --check --verbose --resolve + rt-validator --check --verbose --resolve --force + +=head1 DESCRIPTION + +This script checks integrity of records in RT's DB. May delete some invalid +records or ressurect accidentally deleted. + +=head1 OPTIONS + +=over + +=item check + + mandatory. + + it's equall to -c + +=item verbose + + print additional info to STDOUT + it's equall to -v + +=item resolve + + enable resolver that can delete or create some records + +=item force + + resolve without asking questions + +=back + diff --git a/rt/sbin/tweak-template-locstring b/rt/sbin/tweak-template-locstring deleted file mode 100644 index 457641986..000000000 --- a/rt/sbin/tweak-template-locstring +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/perl -w -# BEGIN BPS TAGGED BLOCK {{{ -# -# COPYRIGHT: -# -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC -# <sales@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/licenses/old-licenses/gpl-2.0.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; -# run this script with: -# perl -0pi sbin/tweak-template-locstring `ack -f share/html -G 'html$'` -s!\<\&\|\/l([^&]*)\&\>[\n\s]+(.*?)[\n\s]*\<\/\&\>!;my ($arg, $x) = ($1, $2); $x =~ s/\s*\n\s*/ /g;"<&|/l$arg&>$x</&>"!smge; - - -1; |