summaryrefslogtreecommitdiff
path: root/rt/lib
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2012-10-04 20:25:37 -0700
committerIvan Kohler <ivan@freeside.biz>2012-10-04 20:25:37 -0700
commit0af38652da3b3be7da2d35b048285ef6f2194e1a (patch)
treec43e871e406a11ad9ddca7f5af225f8e5e507000 /rt/lib
parenta8e1cb65cd92239721b8e81ef9fdf99f60fb3c3c (diff)
parent51b5bd15c154065a9a0f521565bd6187609c8348 (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'rt/lib')
-rw-r--r--rt/lib/RT.pm11
-rw-r--r--rt/lib/RT.pm.in719
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm3
-rw-r--r--rt/lib/RT/Articles.pm3
-rw-r--r--rt/lib/RT/Config.pm6
-rw-r--r--rt/lib/RT/Crypt/GnuPG.pm1
-rw-r--r--rt/lib/RT/Dashboard.pm30
-rw-r--r--rt/lib/RT/Generated.pm2
-rw-r--r--rt/lib/RT/I18N.pm6
-rwxr-xr-xrt/lib/RT/Interface/Email.pm39
-rw-r--r--rt/lib/RT/Interface/Web.pm44
-rwxr-xr-xrt/lib/RT/Record.pm4
-rwxr-xr-xrt/lib/RT/Scrip.pm2
-rwxr-xr-xrt/lib/RT/Scrips.pm80
-rw-r--r--rt/lib/RT/Search/Googleish.pm16
-rw-r--r--rt/lib/RT/SearchBuilder.pm40
-rw-r--r--rt/lib/RT/Shredder.pm6
-rw-r--r--rt/lib/RT/Test.pm6
-rwxr-xr-xrt/lib/RT/Ticket.pm76
-rwxr-xr-xrt/lib/RT/Tickets.pm11
-rwxr-xr-xrt/lib/RT/Transaction.pm11
-rw-r--r--rt/lib/RT/URI.pm19
-rwxr-xr-xrt/lib/RT/User.pm2
23 files changed, 281 insertions, 856 deletions
diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm
index 063f7f719..4372a564d 100644
--- a/rt/lib/RT.pm
+++ b/rt/lib/RT.pm
@@ -138,6 +138,8 @@ up logging|/InitLogging>, and L<loads plugins|/InitPlugins>.
sub Init {
+ my @arg = @_;
+
CheckPerlRequirements();
InitPluginPaths();
@@ -146,7 +148,7 @@ sub Init {
ConnectToDatabase();
InitSystemObjects();
InitClasses();
- InitLogging();
+ InitLogging(@arg);
InitPlugins();
RT::I18N->Init;
RT->Config->PostLoadCheck;
@@ -174,6 +176,8 @@ Create the Logger object and set up signal handlers.
sub InitLogging {
+ my %arg = @_;
+
# We have to set the record separator ($, man perlvar)
# or Log::Dispatch starts getting
# really pissy, as some other module we use unsets it.
@@ -309,11 +313,14 @@ sub InitLogging {
));
}
}
- InitSignalHandlers();
+ InitSignalHandlers(%arg);
}
sub InitSignalHandlers {
+ my %arg = @_;
+ return if $arg{'NoSignalHandlers'};
+
# Signal handlers
## This is the default handling of warnings and die'ings in the code
## (including other used modules - maybe except for errors catched by
diff --git a/rt/lib/RT.pm.in b/rt/lib/RT.pm.in
deleted file mode 100644
index fafd2b778..000000000
--- a/rt/lib/RT.pm.in
+++ /dev/null
@@ -1,719 +0,0 @@
-# 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 warnings;
-
-package RT;
-
-
-use File::Spec ();
-use Cwd ();
-
-use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_INSTALL_MODE);
-
-our $VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@';
-
-@DATABASE_ENV_PREF@
-
-our $BasePath = '@RT_PATH@';
-our $EtcPath = '@RT_ETC_PATH@';
-our $BinPath = '@RT_BIN_PATH@';
-our $SbinPath = '@RT_SBIN_PATH@';
-our $VarPath = '@RT_VAR_PATH@';
-our $PluginPath = '@RT_PLUGIN_PATH@';
-our $LocalPath = '@RT_LOCAL_PATH@';
-our $LocalEtcPath = '@LOCAL_ETC_PATH@';
-our $LocalLibPath = '@LOCAL_LIB_PATH@';
-our $LocalLexiconPath = '@LOCAL_LEXICON_PATH@';
-our $LocalPluginPath = $LocalPath."/plugins";
-
-
-# $MasonComponentRoot is where your rt instance keeps its mason html files
-
-our $MasonComponentRoot = '@MASON_HTML_PATH@';
-
-# $MasonLocalComponentRoot is where your rt instance keeps its site-local
-# mason html files.
-
-our $MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@';
-
-# $MasonDataDir Where mason keeps its datafiles
-
-our $MasonDataDir = '@MASON_DATA_PATH@';
-
-# RT needs to put session data (for preserving state between connections
-# via the web interface)
-our $MasonSessionDir = '@MASON_SESSION_PATH@';
-
-unless ( File::Spec->file_name_is_absolute($EtcPath) ) {
-
-# if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
-# otherwise RT.pm will make src dir(where we configure RT) be the BasePath
-# instead of the --prefix one
- unless ( -d $BasePath && File::Spec->file_name_is_absolute($BasePath) ) {
- my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
-
- # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
- # is not always absolute
- $BasePath =
- File::Spec->rel2abs(
- File::Spec->catdir( $pm_path, File::Spec->updir ) );
- }
-
- $BasePath = Cwd::realpath( $BasePath );
-
- for my $path ( qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
- LocalLibPath LocalLexiconPath PluginPath LocalPluginPath
- MasonComponentRoot MasonLocalComponentRoot MasonDataDir
- MasonSessionDir/ ) {
- no strict 'refs';
- # just change relative ones
- $$path = File::Spec->catfile( $BasePath, $$path )
- unless File::Spec->file_name_is_absolute( $$path );
- }
-}
-
-
-=head1 NAME
-
-RT - Request Tracker
-
-=head1 SYNOPSIS
-
-A fully featured request tracker package
-
-=head1 DESCRIPTION
-
-=head2 INITIALIZATION
-
-=head2 LoadConfig
-
-Load RT's config file. First, the site configuration file
-(F<RT_SiteConfig.pm>) is loaded, in order to establish overall site
-settings like hostname and name of RT instance. Then, the core
-configuration file (F<RT_Config.pm>) is loaded to set fallback values
-for all settings; it bases some values on settings from the site
-configuration file.
-
-In order for the core configuration to not override the site's
-settings, the function C<Set> is used; it only sets values if they
-have not been set already.
-
-=cut
-
-sub LoadConfig {
- require RT::Config;
- $Config = new RT::Config;
- $Config->LoadConfigs;
- require RT::I18N;
-
- # RT::Essentials mistakenly recommends that WebPath be set to '/'.
- # If the user does that, do what they mean.
- $RT::WebPath = '' if ($RT::WebPath eq '/');
-
- # fix relative LogDir and GnuPG homedir
- unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
- $Config->Set( LogDir =>
- File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
- }
-
- my $gpgopts = $Config->Get('GnuPGOptions');
- unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
- $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} );
- }
-
- RT::I18N->Init;
-}
-
-=head2 Init
-
-L<Connect to the database /ConnectToDatabase>, L<initilizes system objects /InitSystemObjects>,
-L<preloads classes /InitClasses> and L<set up logging /InitLogging>.
-
-=cut
-
-sub Init {
-
- my @arg = @_;
-
- CheckPerlRequirements();
-
- InitPluginPaths();
-
- #Get a database connection
- ConnectToDatabase();
- InitSystemObjects();
- InitClasses();
- InitLogging(@arg);
- InitPlugins();
- RT->Config->PostLoadCheck;
-
-}
-
-=head2 ConnectToDatabase
-
-Get a database connection. See also </Handle>.
-
-=cut
-
-sub ConnectToDatabase {
- require RT::Handle;
- $Handle = new RT::Handle unless $Handle;
- $Handle->Connect;
- return $Handle;
-}
-
-=head2 InitLogging
-
-Create the Logger object and set up signal handlers.
-
-=cut
-
-sub InitLogging {
-
- my %arg = @_;
-
- # We have to set the record separator ($, man perlvar)
- # or Log::Dispatch starts getting
- # really pissy, as some other module we use unsets it.
- $, = '';
- use Log::Dispatch 1.6;
-
- my %level_to_num = (
- map( { $_ => } 0..7 ),
- debug => 0,
- info => 1,
- notice => 2,
- warning => 3,
- error => 4, 'err' => 4,
- critical => 5, crit => 5,
- alert => 6,
- emergency => 7, emerg => 7,
- );
-
- unless ( $RT::Logger ) {
-
- $RT::Logger = Log::Dispatch->new;
-
- my $stack_from_level;
- if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
- # if option has old style '\d'(true) value
- $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
- $stack_from_level = $level_to_num{ $stack_from_level } || 0;
- } else {
- $stack_from_level = 99; # don't log
- }
-
- my $simple_cb = sub {
- # if this code throw any warning we can get segfault
- no warnings;
- my %p = @_;
-
- # skip Log::* stack frames
- my $frame = 0;
- $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
- my ($package, $filename, $line) = caller($frame);
-
- $p{'message'} =~ s/(?:\r*\n)+$//;
- return "[". gmtime(time) ."] [". $p{'level'} ."]: "
- . $p{'message'} ." ($filename:$line)\n";
- };
-
- my $syslog_cb = sub {
- # if this code throw any warning we can get segfault
- no warnings;
- my %p = @_;
-
- my $frame = 0; # stack frame index
- # skip Log::* stack frames
- $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
- my ($package, $filename, $line) = caller($frame);
-
- # syswrite() cannot take utf8; turn it off here.
- Encode::_utf8_off($p{message});
-
- $p{message} =~ s/(?:\r*\n)+$//;
- if ($p{level} eq 'debug') {
- return "$p{message}\n";
- } else {
- return "$p{message} ($filename:$line)\n";
- }
- };
-
- my $stack_cb = sub {
- no warnings;
- my %p = @_;
- return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
-
- require Devel::StackTrace;
- my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
- return $p{'message'} . $trace->as_string;
-
- # skip calling of the Log::* subroutins
- my $frame = 0;
- $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
- $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
-
- $p{'message'} .= "\nStack trace:\n";
- while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
- $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
- }
- return $p{'message'};
- };
-
- if ( $Config->Get('LogToFile') ) {
- my ($filename, $logdir) = (
- $Config->Get('LogToFileNamed') || 'rt.log',
- $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
- );
- if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
- ($logdir) = $filename =~ m{^(.*[/\\])};
- }
- else {
- $filename = File::Spec->catfile( $logdir, $filename );
- }
-
- unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
- # localizing here would be hard when we don't have a current user yet
- die "Log file '$filename' couldn't be written or created.\n RT can't run.";
- }
-
- require Log::Dispatch::File;
- $RT::Logger->add( Log::Dispatch::File->new
- ( name=>'file',
- min_level=> $Config->Get('LogToFile'),
- filename=> $filename,
- mode=>'append',
- callbacks => [ $simple_cb, $stack_cb ],
- ));
- }
- if ( $Config->Get('LogToScreen') ) {
- require Log::Dispatch::Screen;
- $RT::Logger->add( Log::Dispatch::Screen->new
- ( name => 'screen',
- min_level => $Config->Get('LogToScreen'),
- callbacks => [ $simple_cb, $stack_cb ],
- stderr => 1,
- ));
- }
- if ( $Config->Get('LogToSyslog') ) {
- require Log::Dispatch::Syslog;
- $RT::Logger->add(Log::Dispatch::Syslog->new
- ( name => 'syslog',
- ident => 'RT',
- min_level => $Config->Get('LogToSyslog'),
- callbacks => [ $syslog_cb, $stack_cb ],
- stderr => 1,
- $Config->Get('LogToSyslogConf'),
- ));
- }
- }
- InitSignalHandlers(%arg);
-}
-
-sub InitSignalHandlers {
-
- my %arg = @_;
-
-# Signal handlers
-## This is the default handling of warnings and die'ings in the code
-## (including other used modules - maybe except for errors catched by
-## Mason). It will log all problems through the standard logging
-## mechanism (see above).
-
- unless ( $arg{'NoSignalHandlers'} ) {
-
- $SIG{__WARN__} = sub {
- # The 'wide character' warnings has to be silenced for now, at least
- # until HTML::Mason offers a sane way to process both raw output and
- # unicode strings.
- # use 'goto &foo' syntax to hide ANON sub from stack
- if( index($_[0], 'Wide character in ') != 0 ) {
- unshift @_, $RT::Logger, qw(level warning message);
- goto &Log::Dispatch::log;
- }
- };
-
- #When we call die, trap it and log->crit with the value of the die.
-
- $SIG{__DIE__} = sub {
- # if we are not in eval and perl is not parsing code
- # then rollback transactions and log RT error
- unless ($^S || !defined $^S ) {
- $RT::Handle->Rollback(1) if $RT::Handle;
- $RT::Logger->crit("$_[0]") if $RT::Logger;
- }
- die $_[0];
- };
-
- }
-}
-
-
-sub CheckPerlRequirements {
- if ($^V < 5.008003) {
- die sprintf "RT requires Perl v5.8.3 or newer. Your current Perl is v%vd\n", $^V;
- }
-
- # use $error here so the following "die" can still affect the global $@
- my $error;
- {
- local $@;
- eval {
- my $x = '';
- my $y = \$x;
- require Scalar::Util;
- Scalar::Util::weaken($y);
- };
- $error = $@;
- }
-
- if ($error) {
- die <<"EOF";
-
-RT requires the Scalar::Util module be built with support for the 'weaken'
-function.
-
-It is sometimes the case that operating system upgrades will replace
-a working Scalar::Util with a non-working one. If your system was working
-correctly up until now, this is likely the cause of the problem.
-
-Please reinstall Scalar::Util, being careful to let it build with your C
-compiler. Ususally this is as simple as running the following command as
-root.
-
- perl -MCPAN -e'install Scalar::Util'
-
-EOF
-
- }
-}
-
-=head2 InitClasses
-
-Load all modules that define base classes.
-
-=cut
-
-sub InitClasses {
- shift if @_%2; # so we can call it as a function or method
- my %args = (@_);
- require RT::Tickets;
- require RT::Transactions;
- require RT::Attachments;
- require RT::Users;
- require RT::Principals;
- require RT::CurrentUser;
- require RT::Templates;
- require RT::Queues;
- require RT::ScripActions;
- require RT::ScripConditions;
- require RT::Scrips;
- require RT::Groups;
- require RT::GroupMembers;
- require RT::CustomFields;
- require RT::CustomFieldValues;
- require RT::ObjectCustomFields;
- require RT::ObjectCustomFieldValues;
- require RT::Attributes;
- require RT::Dashboard;
- require RT::Approval;
-
- # on a cold server (just after restart) people could have an object
- # in the session, as we deserialize it so we never call constructor
- # of the class, so the list of accessible fields is empty and we die
- # with "Method xxx is not implemented in RT::SomeClass"
- $_->_BuildTableAttributes foreach qw(
- RT::Ticket
- RT::Transaction
- RT::Attachment
- RT::User
- RT::Principal
- RT::Template
- RT::Queue
- RT::ScripAction
- RT::ScripCondition
- RT::Scrip
- RT::Group
- RT::GroupMember
- RT::CustomField
- RT::CustomFieldValue
- RT::ObjectCustomField
- RT::ObjectCustomFieldValue
- RT::Attribute
- );
-
- if ( $args{'Heavy'} ) {
- # load scrips' modules
- my $scrips = RT::Scrips->new($RT::SystemUser);
- $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
- while ( my $scrip = $scrips->Next ) {
- local $@;
- eval { $scrip->LoadModules } or
- $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ".
- "You should delete or repair this Scrip in the admin UI.\n$@\n");
- }
-
- foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
- local $@;
- eval "require $class; 1" or $RT::Logger->error(
- "Class '$class' is listed in CustomFieldValuesSources option"
- ." in the config, but we failed to load it:\n$@\n"
- );
- }
-
- RT::I18N->LoadLexicons;
- }
-}
-
-=head2 InitSystemObjects
-
-Initializes system objects: C<$RT::System>, C<$RT::SystemUser>
-and C<$RT::Nobody>.
-
-=cut
-
-sub InitSystemObjects {
-
- #RT's system user is a genuine database user. its id lives here
- require RT::CurrentUser;
- $SystemUser = new RT::CurrentUser;
- $SystemUser->LoadByName('RT_System');
-
- #RT's "nobody user" is a genuine database user. its ID lives here.
- $Nobody = new RT::CurrentUser;
- $Nobody->LoadByName('Nobody');
-
- require RT::System;
- $System = RT::System->new( $SystemUser );
-}
-
-=head1 CLASS METHODS
-
-=head2 Config
-
-Returns the current L<config object RT::Config>, but note that
-you must L<load config /LoadConfig> first otherwise this method
-returns undef.
-
-Method can be called as class method.
-
-=cut
-
-sub Config { return $Config }
-
-=head2 DatabaseHandle
-
-Returns the current L<database handle object RT::Handle>.
-
-See also L</ConnectToDatabase>.
-
-=cut
-
-sub DatabaseHandle { return $Handle }
-
-=head2 Logger
-
-Returns the logger. See also L</InitLogging>.
-
-=cut
-
-sub Logger { return $Logger }
-
-=head2 System
-
-Returns the current L<system object RT::System>. See also
-L</InitSystemObjects>.
-
-=cut
-
-sub System { return $System }
-
-=head2 SystemUser
-
-Returns the system user's object, it's object of
-L<RT::CurrentUser> class that represents the system. See also
-L</InitSystemObjects>.
-
-=cut
-
-sub SystemUser { return $SystemUser }
-
-=head2 Nobody
-
-Returns object of Nobody. It's object of L<RT::CurrentUser> class
-that represents a user who can own ticket and nothing else. See
-also L</InitSystemObjects>.
-
-=cut
-
-sub Nobody { return $Nobody }
-
-=head2 Plugins
-
-Returns a listref of all Plugins currently configured for this RT instance.
-You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
-
-=cut
-
-our @PLUGINS = ();
-sub Plugins {
- my $self = shift;
- unless (@PLUGINS) {
- $self->InitPluginPaths;
- @PLUGINS = $self->InitPlugins;
- }
- return \@PLUGINS;
-}
-
-=head2 PluginDirs
-
-Takes optional subdir (e.g. po, lib, etc.) and return plugins' dirs that exist.
-
-This code chacke plugins names or anything else and required when main config
-is loaded to load plugins' configs.
-
-=cut
-
-sub PluginDirs {
- my $self = shift;
- my $subdir = shift;
-
- require RT::Plugin;
-
- my @res;
- foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
- my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
- next unless -d $path;
- push @res, $path;
- }
- return @res;
-}
-
-=head2 InitPluginPaths
-
-Push plugins' lib paths into @INC right after F<local/lib>.
-In case F<local/lib> isn't in @INC, append them to @INC
-
-=cut
-
-sub InitPluginPaths {
- my $self = shift || __PACKAGE__;
-
- my @lib_dirs = $self->PluginDirs('lib');
-
- my @tmp_inc;
- my $added;
- for (@INC) {
- if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
- push @tmp_inc, $_, @lib_dirs;
- $added = 1;
- } else {
- push @tmp_inc, $_;
- }
- }
-
- # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
- push @tmp_inc, @lib_dirs unless $added;
-
- my %seen;
- @INC = grep !$seen{$_}++, @tmp_inc;
-}
-
-=head2 InitPlugins
-
-Initialze all Plugins found in the RT configuration file, setting up their lib and HTML::Mason component roots.
-
-=cut
-
-sub InitPlugins {
- my $self = shift;
- my @plugins;
- require RT::Plugin;
- foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
- $plugin->require;
- die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
- push @plugins, RT::Plugin->new(name =>$plugin);
- }
- return @plugins;
-}
-
-
-sub InstallMode {
- my $self = shift;
- if (@_) {
- $_INSTALL_MODE = shift;
- if($_INSTALL_MODE) {
- require RT::CurrentUser;
- $SystemUser = RT::CurrentUser->new();
- }
- }
- return $_INSTALL_MODE;
-}
-
-
-=head1 BUGS
-
-Please report them to rt-bugs@bestpractical.com, if you know what's
-broken and have at least some idea of what needs to be fixed.
-
-If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
-
-=head1 SEE ALSO
-
-L<RT::StyleGuide>
-L<DBIx::SearchBuilder>
-
-
-=cut
-
-require RT::Base;
-RT::Base->_ImportOverlays();
-
-1;
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index 31489c8ff..efd2bdaf6 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -567,7 +567,8 @@ sub Parse {
$self->_ParseMultilineTemplate(%args);
} elsif ( $args{'Content'} =~ /(?:\t|,)/i ) {
$self->_ParseXSVTemplate(%args);
-
+ } else {
+ RT->Logger->error("Invalid Template Content (Couldn't find ===, and is not a csv/tsv template) - unable to parse: $args{Content}");
}
}
diff --git a/rt/lib/RT/Articles.pm b/rt/lib/RT/Articles.pm
index 8dd661d2e..47d0ebea2 100644
--- a/rt/lib/RT/Articles.pm
+++ b/rt/lib/RT/Articles.pm
@@ -360,6 +360,7 @@ sub LimitCustomField {
QUOTEVALUE => $args{'QUOTEVALUE'},
ENTRYAGGREGATOR => 'AND', #$args{'ENTRYAGGREGATOR'},
SUBCLAUSE => $clause,
+ CASESENSITIVE => 0,
);
$self->SUPER::Limit(
ALIAS => $ObjectValuesAlias,
@@ -380,6 +381,7 @@ sub LimitCustomField {
QUOTEVALUE => $args{'QUOTEVALUE'},
ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'},
SUBCLAUSE => $clause,
+ CASESENSITIVE => 0,
);
$self->SUPER::Limit(
ALIAS => $ObjectValuesAlias,
@@ -389,6 +391,7 @@ sub LimitCustomField {
QUOTEVALUE => $args{'QUOTEVALUE'},
ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'},
SUBCLAUSE => $clause,
+ CASESENSITIVE => 0,
);
}
}
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index f87ef84c9..014c76468 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -411,8 +411,8 @@ our %META = (
Description => q|What tickets to display in the 'More about requestor' box|, #loc
Values => [qw(Active Inactive All None)],
ValuesLabel => {
- Active => "Show the Requestor's 10 highest priority open tickets", #loc
- Inactive => "Show the Requestor's 10 highest priority closed tickets", #loc
+ Active => "Show the Requestor's 10 highest priority active tickets", #loc
+ Inactive => "Show the Requestor's 10 highest priority inactive tickets", #loc
All => "Show the Requestor's 10 highest priority tickets", #loc
None => "Show no tickets for the Requestor", #loc
},
@@ -749,7 +749,7 @@ our %META = (
my %seen;
foreach my $encoding ( grep defined && length, splice @$value ) {
- next if $seen{ $encoding }++;
+ next if $seen{ $encoding };
if ( $encoding eq '*' ) {
unshift @$value, '*';
next;
diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm
index ab444d068..c5fb12bef 100644
--- a/rt/lib/RT/Crypt/GnuPG.pm
+++ b/rt/lib/RT/Crypt/GnuPG.pm
@@ -1683,6 +1683,7 @@ my %ignore_keyword = map { $_ => 1 } qw(
BEGIN_ENCRYPTION SIG_ID VALIDSIG
ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC
TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE
+ DECRYPTION_INFO
);
sub ParseStatus {
diff --git a/rt/lib/RT/Dashboard.pm b/rt/lib/RT/Dashboard.pm
index 14ffa6ad3..2e2bbc489 100644
--- a/rt/lib/RT/Dashboard.pm
+++ b/rt/lib/RT/Dashboard.pm
@@ -454,6 +454,36 @@ sub CurrentUserCanCreateAny {
return 0;
}
+=head2 Delete
+
+Deletes the dashboard and related subscriptions.
+Returns a tuple of status and message, where status is true upon success.
+
+=cut
+
+sub Delete {
+ my $self = shift;
+ my $id = $self->id;
+ my ( $status, $msg ) = $self->SUPER::Delete(@_);
+ if ( $status ) {
+ # delete all the subscriptions
+ my $subscriptions = RT::Attributes->new( RT->SystemUser );
+ $subscriptions->Limit(
+ FIELD => 'Name',
+ VALUE => 'Subscription',
+ );
+ $subscriptions->Limit(
+ FIELD => 'Description',
+ VALUE => "Subscription to dashboard $id",
+ );
+ while ( my $subscription = $subscriptions->Next ) {
+ $subscription->Delete();
+ }
+ }
+
+ return ( $status, $msg );
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm
index 2abcf3b6e..9fd946f5b 100644
--- a/rt/lib/RT/Generated.pm
+++ b/rt/lib/RT/Generated.pm
@@ -50,7 +50,7 @@ package RT;
use warnings;
use strict;
-our $VERSION = '4.0.6';
+our $VERSION = '4.0.7';
diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm
index cadf7cc7c..e453cfa04 100644
--- a/rt/lib/RT/I18N.pm
+++ b/rt/lib/RT/I18N.pm
@@ -227,7 +227,7 @@ sub SetMIMEEntityToEncoding {
my $body = $entity->bodyhandle;
- if ( $enc ne $charset && $body ) {
+ if ( $body && ($enc ne $charset || $enc =~ /^utf-?8(?:-strict)?$/i) ) {
my $string = $body->as_string or return;
$RT::Logger->debug( "Converting '$charset' to '$enc' for "
@@ -335,7 +335,7 @@ sub DecodeMIMEWordsToEncoding {
}
# now we have got a decoded subject, try to convert into the encoding
- unless ( $charset eq $to_charset ) {
+ if ( $charset ne $to_charset || $charset =~ /^utf-?8(?:-strict)?$/i ) {
Encode::from_to( $enc_str, $charset, $to_charset );
}
@@ -537,7 +537,7 @@ sub SetMIMEHeadToEncoding {
my @values = $head->get_all($tag);
$head->delete($tag);
foreach my $value (@values) {
- if ( $charset ne $enc ) {
+ if ( $charset ne $enc || $enc =~ /^utf-?8(?:-strict)?$/i ) {
Encode::_utf8_off($value);
Encode::from_to( $value, $charset => $enc );
}
diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm
index 02a1ec0c0..4c3ee9986 100755
--- a/rt/lib/RT/Interface/Email.pm
+++ b/rt/lib/RT/Interface/Email.pm
@@ -787,7 +787,7 @@ sub GetForwardFrom {
my $ticket = $args{Ticket} || $txn->Object;
if ( RT->Config->Get('ForwardFromUser') ) {
- return ( $txn || $ticket )->CurrentUser->UserObj->EmailAddress;
+ return ( $txn || $ticket )->CurrentUser->EmailAddress;
}
else {
return $ticket->QueueObj->CorrespondAddress
@@ -1221,8 +1221,16 @@ sub SetInReplyTo {
if @references > 10;
my $mail = $args{'Message'};
- $mail->head->set( 'In-Reply-To' => join ' ', @rtid? (@rtid) : (@id) ) if @id || @rtid;
- $mail->head->set( 'References' => join ' ', @references );
+ $mail->head->set( 'In-Reply-To' => Encode::encode_utf8(join ' ', @rtid? (@rtid) : (@id)) ) if @id || @rtid;
+ $mail->head->set( 'References' => Encode::encode_utf8(join ' ', @references) );
+}
+
+sub ExtractTicketId {
+ my $entity = shift;
+
+ my $subject = $entity->head->get('Subject') || '';
+ chomp $subject;
+ return ParseTicketId( $subject );
}
sub ParseTicketId {
@@ -1448,7 +1456,7 @@ sub Gateway {
}
# }}}
- $args{'ticket'} ||= ParseTicketId( $Subject );
+ $args{'ticket'} ||= ExtractTicketId( $Message );
$SystemTicket = RT::Ticket->new( RT->SystemUser );
$SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
@@ -1704,17 +1712,20 @@ sub _RunUnsafeAction {
return ( 0, "Ticket not taken" );
}
} elsif ( $args{'Action'} =~ /^resolve$/i ) {
- my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved');
- unless ($status) {
+ my $new_status = $args{'Ticket'}->FirstInactiveStatus;
+ if ($new_status) {
+ my ( $status, $msg ) = $args{'Ticket'}->SetStatus($new_status);
+ unless ($status) {
- #Warn the sender that we couldn't actually submit the comment.
- MailError(
- To => $args{'ErrorsTo'},
- Subject => "Ticket not resolved",
- Explanation => $msg,
- MIMEObj => $args{'Message'}
- );
- return ( 0, "Ticket not resolved" );
+ #Warn the sender that we couldn't actually submit the comment.
+ MailError(
+ To => $args{'ErrorsTo'},
+ Subject => "Ticket not resolved",
+ Explanation => $msg,
+ MIMEObj => $args{'Message'}
+ );
+ return ( 0, "Ticket not resolved" );
+ }
}
} else {
return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
index 94da3072d..1aae7581e 100644
--- a/rt/lib/RT/Interface/Web.pm
+++ b/rt/lib/RT/Interface/Web.pm
@@ -261,7 +261,15 @@ sub HandleRequest {
$HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS );
SendSessionCookie();
- $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new() unless _UserLoggedIn();
+
+ if ( _UserLoggedIn() ) {
+ # make user info up to date
+ $HTML::Mason::Commands::session{'CurrentUser'}
+ ->Load( $HTML::Mason::Commands::session{'CurrentUser'}->id );
+ }
+ else {
+ $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new();
+ }
# Process session-related callbacks before any auth attempts
$HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' );
@@ -287,7 +295,7 @@ sub HandleRequest {
my $m = $HTML::Mason::Commands::m;
# REST urls get a special 401 response
- if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
+ if ($m->request_comp->path =~ m{^/REST/\d+\.\d+/}) {
$HTML::Mason::Commands::r->content_type("text/plain");
$m->error_format("text");
$m->out("RT/$RT::VERSION 401 Credentials required\n");
@@ -457,7 +465,7 @@ sub MaybeShowInstallModePage {
my $m = $HTML::Mason::Commands::m;
if ( $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex') ) {
$m->call_next();
- } elsif ( $m->request_comp->path !~ '^(/+)Install/' ) {
+ } elsif ( $m->request_comp->path !~ m{^(/+)Install/} ) {
RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "Install/index.html" );
} else {
$m->call_next();
@@ -557,7 +565,7 @@ sub ShowRequestedPage {
unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) {
# if the user is trying to access a ticket, redirect them
- if ( $m->request_comp->path =~ '^(/+)Ticket/Display.html' && $ARGS->{'id'} ) {
+ if ( $m->request_comp->path =~ m{^(/+)Ticket/Display.html} && $ARGS->{'id'} ) {
RT::Interface::Web::Redirect( RT->Config->Get('WebURL') . "SelfService/Display.html?id=" . $ARGS->{'id'} );
}
@@ -659,7 +667,7 @@ sub AttemptExternalAuth {
delete $HTML::Mason::Commands::session{'CurrentUser'};
$user = $orig_user;
- if ( RT->Config->Get('WebExternalOnly') ) {
+ unless ( RT->Config->Get('WebFallbackToInternalAuth') ) {
TangentForLoginWithError('You are not an authorized user');
}
}
@@ -970,7 +978,7 @@ sub MobileClient {
my $self = shift;
-if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60)/io && !$HTML::Mason::Commands::session{'NotMobile'}) {
+if (($ENV{'HTTP_USER_AGENT'} || '') =~ /(?:hiptop|Blazer|Novarra|Vagabond|SonyEricsson|Symbian|NetFront|UP.Browser|UP.Link|Windows CE|MIDP|J2ME|DoCoMo|J-PHONE|PalmOS|PalmSource|iPhone|iPod|AvantGo|Nokia|Android|WebOS|S60|Mobile)/io && !$HTML::Mason::Commands::session{'NotMobile'}) {
return 1;
} else {
return undef;
@@ -1183,6 +1191,14 @@ our %is_whitelisted_component = (
# information for the search. Because it's a straight-up read, in
# addition to embedding its own auth, it's fine.
'/NoAuth/rss/dhandler' => 1,
+
+ # While these can be used for denial-of-service against RT
+ # (construct a very inefficient query and trick lots of users into
+ # running them against RT) it's incredibly useful to be able to link
+ # to a search result or bookmark a result page.
+ '/Search/Results.html' => 1,
+ '/Search/Simple.html' => 1,
+ '/m/tickets/search' => 1,
);
sub IsCompCSRFWhitelisted {
@@ -1237,7 +1253,19 @@ sub IsRefererCSRFWhitelisted {
my $configs;
for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) {
push @$configs,$config;
- return 1 if $referer->host_port eq $config;
+
+ my $host_port = $referer->host_port;
+ if ($config =~ /\*/) {
+ # Turn a literal * into a domain component or partial component match.
+ # Refer to http://tools.ietf.org/html/rfc2818#page-5
+ my $regex = join "[a-zA-Z0-9\-]*",
+ map { quotemeta($_) }
+ split /\*/, $config;
+
+ return 1 if $host_port =~ /^$regex$/i;
+ } else {
+ return 1 if $host_port eq $config;
+ }
}
return (0,$referer,$configs);
@@ -1962,7 +1990,7 @@ sub MakeMIMEEntity {
);
my $Message = MIME::Entity->build(
Type => 'multipart/mixed',
- "Message-Id" => RT::Interface::Email::GenMessageId,
+ "Message-Id" => Encode::encode_utf8( RT::Interface::Email::GenMessageId ),
map { $_ => Encode::encode_utf8( $args{ $_} ) }
grep defined $args{$_}, qw(Subject From Cc)
);
diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm
index e134178be..fd238de16 100755
--- a/rt/lib/RT/Record.pm
+++ b/rt/lib/RT/Record.pm
@@ -639,6 +639,8 @@ sub __Value {
my $value = $self->SUPER::__Value($field);
+ return undef if (!defined $value);
+
if ( $args{'decode_utf8'} ) {
if ( !utf8::is_utf8($value) ) {
utf8::decode($value);
@@ -1675,7 +1677,7 @@ sub _AddCustomFieldValue {
0,
$self->loc(
"Custom field [_1] does not apply to this object",
- $args{'Field'}
+ ref $args{'Field'} ? $args{'Field'}->id : $args{'Field'}
)
);
}
diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm
index 950661624..8f97e747f 100755
--- a/rt/lib/RT/Scrip.pm
+++ b/rt/lib/RT/Scrip.pm
@@ -545,7 +545,7 @@ sub _Set {
}
}
- return $self->__Set(@_);
+ return $self->SUPER::_Set(@_);
}
diff --git a/rt/lib/RT/Scrips.pm b/rt/lib/RT/Scrips.pm
index 13a4b7d7d..fa33f7ec7 100755
--- a/rt/lib/RT/Scrips.pm
+++ b/rt/lib/RT/Scrips.pm
@@ -178,16 +178,6 @@ Commit all of this object's prepared scrips
sub Commit {
my $self = shift;
- # RT::Scrips->_SetupSourceObjects will clobber
- # the CurrentUser, but we need to keep this ticket
- # so that the _TransactionBatch cache is maintained
- # and doesn't run twice. sigh.
- $self->_StashCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj};
-
- #We're really going to need a non-acled ticket for the scrips to work
- $self->_SetupSourceObjects( TicketObj => $self->{'TicketObj'},
- TransactionObj => $self->{'TransactionObj'} );
-
foreach my $scrip (@{$self->Prepared}) {
$RT::Logger->debug(
"Committing scrip #". $scrip->id
@@ -199,8 +189,6 @@ sub Commit {
TransactionObj => $self->{'TransactionObj'} );
}
- # Apply the bandaid.
- $self->_RestoreCurrentUser( TicketObj => $self->{TicketObj} ) if $self->{TicketObj};
}
@@ -221,12 +209,6 @@ sub Prepare {
Type => undef,
@_ );
- # RT::Scrips->_SetupSourceObjects will clobber
- # the CurrentUser, but we need to keep this ticket
- # so that the _TransactionBatch cache is maintained
- # and doesn't run twice. sigh.
- $self->_StashCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj};
-
#We're really going to need a non-acled ticket for the scrips to work
$self->_SetupSourceObjects( TicketObj => $args{'TicketObj'},
Ticket => $args{'Ticket'},
@@ -259,10 +241,6 @@ sub Prepare {
}
- # Apply the bandaid.
- $self->_RestoreCurrentUser( TicketObj => $args{TicketObj} ) if $args{TicketObj};
-
-
return (@{$self->Prepared});
};
@@ -279,40 +257,6 @@ sub Prepared {
return ($self->{'prepared_scrips'} || []);
}
-=head2 _StashCurrentUser TicketObj => RT::Ticket
-
-Saves aside the current user of the original ticket that was passed to these scrips.
-This is used to make sure that we don't accidentally leak the RT_System current user
-back to the calling code.
-
-=cut
-
-sub _StashCurrentUser {
- my $self = shift;
- my %args = @_;
-
- $self->{_TicketCurrentUser} = $args{TicketObj}->CurrentUser;
-}
-
-=head2 _RestoreCurrentUser TicketObj => RT::Ticket
-
-Uses the current user saved by _StashCurrentUser to reset a Ticket object
-back to the caller's current user and avoid leaking an RT_System ticket to
-calling code.
-
-=cut
-
-sub _RestoreCurrentUser {
- my $self = shift;
- my %args = @_;
- unless ( $self->{_TicketCurrentUser} ) {
- RT->Logger->debug("Called _RestoreCurrentUser without a stashed current user object");
- return;
- }
- $args{TicketObj}->CurrentUser($self->{_TicketCurrentUser});
-
-}
-
=head2 _SetupSourceObjects { TicketObj , Ticket, Transaction, TransactionObj }
Setup a ticket and transaction for this Scrip collection to work with as it runs through the
@@ -334,14 +278,22 @@ sub _SetupSourceObjects {
@_ );
- if ( $self->{'TicketObj'} = $args{'TicketObj'} ) {
- # This clobbers the passed in TicketObj by turning it into one
- # whose current user is RT_System. Anywhere in the Web UI
- # currently calling into this is thus susceptable to a privilege
- # leak; the only current call site is ->Apply, which bandaids
- # over the top of this by re-asserting the CurrentUser
- # afterwards.
- $self->{'TicketObj'}->CurrentUser( $self->CurrentUser );
+ if ( $args{'TicketObj'} ) {
+ # This loads a clean copy of the Ticket object to ensure that we
+ # don't accidentally escalate the privileges of the passed in
+ # ticket (this function can be invoked from the UI).
+ # We copy the TransactionBatch transactions so that Scrips
+ # running against the new Ticket will have access to them. We
+ # use RanTransactionBatch to guard against running
+ # TransactionBatch Scrips more than once.
+ $self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
+ $self->{'TicketObj'}->Load( $args{'TicketObj'}->Id );
+ if ( $args{'TicketObj'}->TransactionBatch ) {
+ # try to ensure that we won't infinite loop if something dies, triggering DESTROY while
+ # we have the _TransactionBatch objects;
+ $self->{'TicketObj'}->RanTransactionBatch(1);
+ $self->{'TicketObj'}->{'_TransactionBatch'} = $args{'TicketObj'}->{'_TransactionBatch'};
+ }
}
else {
$self->{'TicketObj'} = RT::Ticket->new( $self->CurrentUser );
diff --git a/rt/lib/RT/Search/Googleish.pm b/rt/lib/RT/Search/Googleish.pm
index a1254836a..1b4071f4d 100644
--- a/rt/lib/RT/Search/Googleish.pm
+++ b/rt/lib/RT/Search/Googleish.pm
@@ -110,7 +110,7 @@ sub QueryToSQL {
(\w+) # A straight word
(?:\. # With an optional .foo
($RE{delimited}{-delim=>q['"]}
- |\w+
+ |[\w-]+ # Allow \w + dashes
) # Which could be ."foo bar", too
)?
)
@@ -225,6 +225,11 @@ sub GuessType {
return "default";
}
+# $_[0] is $self
+# $_[1] is escaped value without surrounding single quotes
+# $_[2] is a boolean of "was quoted by the user?"
+# ensure this is false before you do smart matching like $_[1] eq "me"
+# $_[3] is escaped subkey, if any (see HandleCf)
sub HandleDefault { return subject => "Subject LIKE '$_[1]'"; }
sub HandleSubject { return subject => "Subject LIKE '$_[1]'"; }
sub HandleFulltext { return content => "Content LIKE '$_[1]'"; }
@@ -242,7 +247,14 @@ sub HandleStatus {
}
}
sub HandleOwner {
- return owner => (!$_[2] and $_[1] eq "me") ? "Owner.id = '__CurrentUser__'" : "Owner = '$_[1]'";
+ if (!$_[2] and $_[1] eq "me") {
+ return owner => "Owner.id = '__CurrentUser__'";
+ }
+ elsif (!$_[2] and $_[1] =~ /\w+@\w+/) {
+ return owner => "Owner.EmailAddress = '$_[1]'";
+ } else {
+ return owner => "Owner = '$_[1]'";
+ }
}
sub HandleWatcher {
return watcher => (!$_[2] and $_[1] eq "me") ? "Watcher.id = '__CurrentUser__'" : "Watcher = '$_[1]'";
diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm
index 3e9855110..4278f7587 100644
--- a/rt/lib/RT/SearchBuilder.pm
+++ b/rt/lib/RT/SearchBuilder.pm
@@ -211,29 +211,35 @@ sub LimitCustomField {
@_ );
my $alias = $self->Join(
- TYPE => 'left',
- ALIAS1 => 'main',
- FIELD1 => 'id',
- TABLE2 => 'ObjectCustomFieldValues',
- FIELD2 => 'ObjectId'
+ TYPE => 'left',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'ObjectCustomFieldValues',
+ FIELD2 => 'ObjectId'
);
$self->Limit(
- ALIAS => $alias,
- FIELD => 'CustomField',
- OPERATOR => '=',
- VALUE => $args{'CUSTOMFIELD'},
+ ALIAS => $alias,
+ FIELD => 'CustomField',
+ OPERATOR => '=',
+ VALUE => $args{'CUSTOMFIELD'},
) if ($args{'CUSTOMFIELD'});
$self->Limit(
- ALIAS => $alias,
- FIELD => 'ObjectType',
- OPERATOR => '=',
- VALUE => $self->_SingularClass,
+ ALIAS => $alias,
+ FIELD => 'ObjectType',
+ OPERATOR => '=',
+ VALUE => $self->_SingularClass,
);
$self->Limit(
- ALIAS => $alias,
- FIELD => 'Content',
- OPERATOR => $args{'OPERATOR'},
- VALUE => $args{'VALUE'},
+ ALIAS => $alias,
+ FIELD => 'Content',
+ OPERATOR => $args{'OPERATOR'},
+ VALUE => $args{'VALUE'},
+ );
+ $self->Limit(
+ ALIAS => $alias,
+ FIELD => 'Disabled',
+ OPERATOR => '=',
+ VALUE => 0,
);
}
diff --git a/rt/lib/RT/Shredder.pm b/rt/lib/RT/Shredder.pm
index 40c73b36d..4f96e162d 100644
--- a/rt/lib/RT/Shredder.pm
+++ b/rt/lib/RT/Shredder.pm
@@ -539,9 +539,9 @@ sub WipeoutAll
{
my $self = $_[0];
- while ( my ($k, $v) = each %{ $self->{'cache'} } ) {
- next if $v->{'State'} & (WIPED | IN_WIPING);
- $self->Wipeout( Object => $v->{'Object'} );
+ foreach my $cache_val ( values %{ $self->{'cache'} } ) {
+ next if $cache_val->{'State'} & (WIPED | IN_WIPING);
+ $self->Wipeout( Object => $cache_val->{'Object'} );
}
}
diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm
index 7d69dd60d..3e7c910ec 100644
--- a/rt/lib/RT/Test.pm
+++ b/rt/lib/RT/Test.pm
@@ -131,14 +131,14 @@ sub import {
if (RT->Config->Get('DevelMode')) { require Module::Refresh; }
- $class->bootstrap_db( %args );
-
RT::InitPluginPaths();
+ RT::InitClasses();
+
+ $class->bootstrap_db( %args );
__reconnect_rt()
unless $args{nodb};
- RT::InitClasses();
RT::InitLogging();
RT->Plugins;
diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm
index 00f88b657..577c44429 100755
--- a/rt/lib/RT/Ticket.pm
+++ b/rt/lib/RT/Ticket.pm
@@ -1124,7 +1124,7 @@ sub AddWatcher {
return (0, $self->loc("Couldn't parse address from '[_1]' string", $args{'Email'} ))
unless $addr;
- if ( lc $self->CurrentUser->UserObj->EmailAddress
+ if ( lc $self->CurrentUser->EmailAddress
eq lc RT::User->CanonicalizeEmailAddress( $addr->address ) )
{
$args{'PrincipalId'} = $self->CurrentUser->id;
@@ -1305,7 +1305,7 @@ sub DeleteWatcher {
}
}
else {
- $RT::Logger->warn("$self -> DeleteWatcher got passed a bogus type");
+ $RT::Logger->warning("$self -> DeleteWatcher got passed a bogus type");
return ( 0,
$self->loc('Error in parameters to Ticket->DeleteWatcher') );
}
@@ -1989,6 +1989,31 @@ sub FirstActiveStatus {
return $next;
}
+=head2 FirstInactiveStatus
+
+Returns the first inactive status that the ticket could transition to,
+according to its current Queue's lifecycle. May return undef if there
+is no such possible status to transition to, or we are already in it.
+This is used in resolve action in UnsafeEmailCommands, for instance.
+
+=cut
+
+sub FirstInactiveStatus {
+ my $self = shift;
+
+ my $lifecycle = $self->QueueObj->Lifecycle;
+ my $status = $self->Status;
+ my @inactive = $lifecycle->Inactive;
+ # no change if no inactive statuses in the lifecycle
+ return undef unless @inactive;
+
+ # no change if the ticket is already has first status from the list of inactive
+ return undef if lc $status eq lc $inactive[0];
+
+ my ($next) = grep $lifecycle->IsInactive($_), $lifecycle->Transitions($status);
+ return $next;
+}
+
=head2 SetStarted
Takes a date in ISO format or undef
@@ -2315,7 +2340,9 @@ sub _RecordNote {
my $msgid = $args{'MIMEObj'}->head->get('Message-ID');
unless (defined $msgid && $msgid =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>/) {
$args{'MIMEObj'}->head->set(
- 'RT-Message-ID' => RT::Interface::Email::GenMessageId( Ticket => $self )
+ 'RT-Message-ID' => Encode::encode_utf8(
+ RT::Interface::Email::GenMessageId( Ticket => $self )
+ )
);
}
@@ -3340,6 +3367,28 @@ sub SeenUpTo {
return $txns->First;
}
+=head2 RanTransactionBatch
+
+Acts as a guard around running TransactionBatch scrips.
+
+Should be false until you enter the code that runs TransactionBatch scrips
+
+Accepts an optional argument to indicate that TransactionBatch Scrips should no longer be run on this object.
+
+=cut
+
+sub RanTransactionBatch {
+ my $self = shift;
+ my $val = shift;
+
+ if ( defined $val ) {
+ return $self->{_RanTransactionBatch} = $val;
+ } else {
+ return $self->{_RanTransactionBatch};
+ }
+
+}
+
=head2 TransactionBatch
@@ -3376,6 +3425,22 @@ sub ApplyTransactionBatch {
sub _ApplyTransactionBatch {
my $self = shift;
+
+ return if $self->RanTransactionBatch;
+ $self->RanTransactionBatch(1);
+
+ my $still_exists = RT::Ticket->new( RT->SystemUser );
+ $still_exists->Load( $self->Id );
+ if (not $still_exists->Id) {
+ # The ticket has been removed from the database, but we still
+ # have pending TransactionBatch txns for it. Unfortunately,
+ # because it isn't in the DB anymore, attempting to run scrips
+ # on it may produce unpredictable results; simply drop the
+ # batched transactions.
+ $RT::Logger->warning("TransactionBatch was fired on a ticket that no longer exists; unable to run scrips! Call ->ApplyTransactionBatch before shredding the ticket, for consistent results.");
+ return;
+ }
+
my $batch = $self->TransactionBatch;
my %seen;
@@ -3423,10 +3488,7 @@ sub DESTROY {
return;
}
- my $batch = $self->TransactionBatch;
- return unless $batch && @$batch;
-
- return $self->_ApplyTransactionBatch;
+ return $self->ApplyTransactionBatch;
}
diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm
index 485d7df53..c9986f41e 100755
--- a/rt/lib/RT/Tickets.pm
+++ b/rt/lib/RT/Tickets.pm
@@ -436,6 +436,10 @@ sub _LinkLimit {
my $is_null = 0;
$is_null = 1 if !$value || $value =~ /^null$/io;
+ unless ($is_null) {
+ $value = RT::URI->new( $sb->CurrentUser )->CanonicalizeURI( $value );
+ }
+
my $direction = $meta->[1] || '';
my ($matchfield, $linkfield) = ('', '');
if ( $direction eq 'To' ) {
@@ -1651,6 +1655,7 @@ sub _CustomFieldLimit {
FIELD => $column,
OPERATOR => $op,
VALUE => $value,
+ CASESENSITIVE => 0,
%rest
) );
$self->_CloseParen;
@@ -1713,6 +1718,7 @@ sub _CustomFieldLimit {
FIELD => 'Content',
OPERATOR => $op,
VALUE => $value,
+ CASESENSITIVE => 0,
%rest
);
}
@@ -1739,6 +1745,7 @@ sub _CustomFieldLimit {
OPERATOR => $op,
VALUE => $value,
ENTRYAGGREGATOR => 'AND',
+ CASESENSITIVE => 0,
) );
}
}
@@ -1748,6 +1755,7 @@ sub _CustomFieldLimit {
FIELD => 'Content',
OPERATOR => $op,
VALUE => $value,
+ CASESENSITIVE => 0,
%rest
);
@@ -1774,6 +1782,7 @@ sub _CustomFieldLimit {
OPERATOR => $op,
VALUE => $value,
ENTRYAGGREGATOR => 'AND',
+ CASESENSITIVE => 0,
) );
$self->_CloseParen;
}
@@ -1830,6 +1839,7 @@ sub _CustomFieldLimit {
FIELD => $column,
OPERATOR => $op,
VALUE => $value,
+ CASESENSITIVE => 0,
) );
}
else {
@@ -1839,6 +1849,7 @@ sub _CustomFieldLimit {
FIELD => 'Content',
OPERATOR => $op,
VALUE => $value,
+ CASESENSITIVE => 0,
);
}
$self->_SQLLimit(
diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm
index bd4d83546..3344687da 100755
--- a/rt/lib/RT/Transaction.pm
+++ b/rt/lib/RT/Transaction.pm
@@ -133,12 +133,6 @@ sub Create {
return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
}
-
- # Set up any custom fields passed at creation. Has to happen
- # before scrips.
-
- $self->UpdateCustomFields(%{ $args{'CustomFields'} });
-
#lets create our transaction
my %params = (
Type => $args{'Type'},
@@ -169,6 +163,11 @@ sub Create {
}
}
+ # Set up any custom fields passed at creation. Has to happen
+ # before scrips.
+
+ $self->UpdateCustomFields(%{ $args{'CustomFields'} });
+
$self->AddAttribute(
Name => 'SquelchMailTo',
Content => RT::User->CanonicalizeEmailAddress($_)
diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm
index fce04598a..284a75ee0 100644
--- a/rt/lib/RT/URI.pm
+++ b/rt/lib/RT/URI.pm
@@ -91,7 +91,26 @@ sub new {
return ($self);
}
+=head2 CanonicalizeURI <URI>
+Returns the canonical form of the given URI by calling L</FromURI> and then L</URI>.
+
+If the URI is unparseable by FromURI the passed in URI is simply returned untouched.
+
+=cut
+
+sub CanonicalizeURI {
+ my $self = shift;
+ my $uri = shift;
+ if ($self->FromURI($uri)) {
+ my $canonical = $self->URI;
+ if ($canonical and $uri ne $canonical) {
+ RT->Logger->debug("Canonicalizing URI '$uri' to '$canonical'");
+ $uri = $canonical;
+ }
+ }
+ return $uri;
+}
=head2 FromObject <Object>
diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm
index 9b4a82683..e7f7c2ad6 100755
--- a/rt/lib/RT/User.pm
+++ b/rt/lib/RT/User.pm
@@ -932,7 +932,7 @@ sub IsPassword {
# crypt() output
return 0 unless crypt(encode_utf8($value), $stored) eq $stored;
} else {
- $RT::Logger->warn("Unknown password form");
+ $RT::Logger->warning("Unknown password form");
return 0;
}