summaryrefslogtreecommitdiff
path: root/rt/lib
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2012-09-23 14:56:51 -0700
committerIvan Kohler <ivan@freeside.biz>2012-09-23 14:56:51 -0700
commit0bff2e665b3a6389b47510e4c04a5a454f6dd7d4 (patch)
treee7f3368c067594b98699c5cf62f0991e8ceb2e4f /rt/lib
parent806d426d106efea2b2b13314108c4ac046511e1c (diff)
rt 4.0.7
Diffstat (limited to 'rt/lib')
-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
-rw-r--r--rt/lib/RT/URI.pm19
-rwxr-xr-xrt/lib/RT/User.pm2
20 files changed, 267 insertions, 129 deletions
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/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;
}