summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Action
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Action')
-rw-r--r--rt/lib/RT/Action/AutoOpen.pm3
-rw-r--r--rt/lib/RT/Action/AutoOpenInactive.pm105
-rwxr-xr-xrt/lib/RT/Action/Autoreply.pm14
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm113
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm.orig9
-rw-r--r--rt/lib/RT/Action/EscalatePriority.pm165
-rwxr-xr-xrt/lib/RT/Action/LinearEscalate.pm15
-rwxr-xr-xrt/lib/RT/Action/Notify.pm54
-rw-r--r--rt/lib/RT/Action/NotifyGroup.pm16
-rw-r--r--rt/lib/RT/Action/NotifyGroupAsComment.pm8
-rw-r--r--rt/lib/RT/Action/NotifyOwnerOrAdminCc.pm76
-rw-r--r--rt/lib/RT/Action/OpenOnStarted.pm87
-rw-r--r--rt/lib/RT/Action/RecordComment.pm23
-rw-r--r--rt/lib/RT/Action/RecordCorrespondence.pm24
-rwxr-xr-xrt/lib/RT/Action/SendEmail.pm181
-rwxr-xr-xrt/lib/RT/Action/SendEmail.pm.orig42
-rw-r--r--rt/lib/RT/Action/SendForward.pm138
-rw-r--r--rt/lib/RT/Action/SetStatus.pm2
18 files changed, 774 insertions, 301 deletions
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm
index 06721b7c4..156c1eea4 100644
--- a/rt/lib/RT/Action/AutoOpen.pm
+++ b/rt/lib/RT/Action/AutoOpen.pm
@@ -46,7 +46,6 @@
#
# END BPS TAGGED BLOCK }}}
-# This Action will open the BASE if a dependent is resolved.
package RT::Action::AutoOpen;
use strict;
@@ -87,7 +86,7 @@ sub Prepare {
# no change if the ticket is in initial status and the message is a mail
# from a requestor
- return 1 if $ticket->QueueObj->Lifecycle->IsInitial($ticket->Status)
+ return 1 if $ticket->LifecycleObj->IsInitial($ticket->Status)
&& $self->TransactionObj->IsInbound;
if ( my $msg = $self->TransactionObj->Message->First ) {
diff --git a/rt/lib/RT/Action/AutoOpenInactive.pm b/rt/lib/RT/Action/AutoOpenInactive.pm
new file mode 100644
index 000000000..a8a373939
--- /dev/null
+++ b/rt/lib/RT/Action/AutoOpenInactive.pm
@@ -0,0 +1,105 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 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 }}}
+
+package RT::Action::AutoOpenInactive;
+
+use strict;
+use warnings;
+use base qw(RT::Action);
+
+=head1 DESCRIPTION
+
+This action automatically moves an inactive ticket to an active status.
+
+Status is not changed if there is no active statuses in the lifecycle.
+
+Status is not changed if message's head has field C<RT-Control> with
+C<no-autoopen> substring.
+
+Status is set to the first possible active status. If the ticket's status is
+C<Resolved> then RT finds all possible transitions from C<Resolved> status and
+selects first one that results in the ticket having an active status.
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+ return 0 if $ticket->LifecycleObj->IsActive( $ticket->Status );
+
+ if ( my $msg = $self->TransactionObj->Message->First ) {
+ return 0
+ if ( $msg->GetHeader('RT-Control') || '' ) =~
+ /\bno-autoopen\b/i;
+ }
+
+ my $next = $ticket->FirstActiveStatus;
+ return 0 unless defined $next;
+
+ $self->{'set_status_to'} = $next;
+
+ return 1;
+}
+
+sub Commit {
+ my $self = shift;
+
+ return 1 unless my $new_status = $self->{'set_status_to'};
+
+ my ($val, $msg) = $self->TicketObj->SetStatus( $new_status );
+ unless ( $val ) {
+ $RT::Logger->error( "Couldn't auto-open-inactive ticket: ". $msg );
+ return 0;
+ }
+ return 1;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm
index 2b042224b..9bf6ab6d3 100755
--- a/rt/lib/RT/Action/Autoreply.pm
+++ b/rt/lib/RT/Action/Autoreply.pm
@@ -93,18 +93,18 @@ Set this message's return address to the apropriate queue address
sub SetReturnAddress {
my $self = shift;
-
+
my $friendly_name;
- if (RT->Config->Get('UseFriendlyFromLine')) {
- $friendly_name = $self->TicketObj->QueueObj->Description ||
- $self->TicketObj->QueueObj->Name;
- }
+ if (RT->Config->Get('UseFriendlyFromLine')) {
+ $friendly_name = $self->TicketObj->QueueObj->Description ||
+ $self->TicketObj->QueueObj->Name;
+ }
$self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name );
-
+
}
-
+
=head2 SetRTSpecialHeaders
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index 46791de58..03bc21255 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -53,6 +53,7 @@ use strict;
use warnings;
use MIME::Entity;
+use RT::Link;
=head1 NAME
@@ -128,18 +129,18 @@ A convoluted example:
my $groups = RT::Groups->new(RT->SystemUser);
$groups->LimitToUserDefinedGroups();
- $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name");
+ $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => $name, CASESENSITIVE => 0);
$groups->WithMember($TransactionObj->CreatorObj->Id);
my $groupid = $groups->First->Id;
my $adminccs = RT::Users->new(RT->SystemUser);
$adminccs->WhoHaveRight(
- Right => "AdminGroup",
- Object =>$groups->First,
- IncludeSystemRights => undef,
- IncludeSuperusers => 0,
- IncludeSubgroupMembers => 0,
+ Right => "AdminGroup",
+ Object =>$groups->First,
+ IncludeSystemRights => undef,
+ IncludeSuperusers => 0,
+ IncludeSubgroupMembers => 0,
);
our @admins;
@@ -241,47 +242,6 @@ all be treated as the same thing.
=cut
-my %LINKTYPEMAP = (
- MemberOf => {
- Type => 'MemberOf',
- Mode => 'Target',
- },
- Parents => {
- Type => 'MemberOf',
- Mode => 'Target',
- },
- Members => {
- Type => 'MemberOf',
- Mode => 'Base',
- },
- Children => {
- Type => 'MemberOf',
- Mode => 'Base',
- },
- HasMember => {
- Type => 'MemberOf',
- Mode => 'Base',
- },
- RefersTo => {
- Type => 'RefersTo',
- Mode => 'Target',
- },
- ReferredToBy => {
- Type => 'RefersTo',
- Mode => 'Base',
- },
- DependsOn => {
- Type => 'DependsOn',
- Mode => 'Target',
- },
- DependedOnBy => {
- Type => 'DependsOn',
- Mode => 'Base',
- },
-
-);
-
-
#Do what we need to do and send it out.
sub Commit {
my $self = shift;
@@ -388,10 +348,6 @@ sub CreateByTemplate {
}
$RT::Logger->debug("Assigned $template_id with $id");
- $T::Tickets{$template_id}->SetOriginObj( $self->TicketObj )
- if $self->TicketObj
- && $T::Tickets{$template_id}->can('SetOriginObj');
-
}
$self->PostProcess( \@links, \@postponed );
@@ -669,11 +625,6 @@ sub ParseLines {
if ($err) {
$RT::Logger->error( "Ticket creation failed: " . $err );
- while ( my ( $k, $v ) = each %T::X ) {
- $RT::Logger->debug(
- "Eliminating $template_id from ${k}'s parents.");
- delete $v->{$template_id};
- }
next;
}
}
@@ -718,7 +669,7 @@ sub ParseLines {
}
if (
($tag =~ /^(requestor|cc|admincc)(group)?$/i
- or grep {lc $_ eq $tag} keys %LINKTYPEMAP)
+ or grep {lc $_ eq $tag} keys %RT::Link::TYPEMAP)
and $args{$tag} =~ /,/
) {
$args{$tag} = [ split /,\s*/, $args{$tag} ];
@@ -736,7 +687,7 @@ sub ParseLines {
eval {
$dateobj->Set( Format => 'iso', Value => $args{$date} );
};
- if ($@ or $dateobj->Unix <= 0) {
+ if ($@ or not $dateobj->IsSet) {
$dateobj->Set( Format => 'unknown', Value => $args{$date} );
}
}
@@ -802,14 +753,22 @@ sub ParseLines {
$ticketargs{ "CustomField-" . $1 } = $args{$tag};
} elsif ( $orig_tag =~ /^(?:customfield|cf)-?(.+)$/i ) {
my $cf = RT::CustomField->new( $self->CurrentUser );
- $cf->LoadByName( Name => $1, Queue => $ticketargs{Queue} );
- $cf->LoadByName( Name => $1, Queue => 0 ) unless $cf->id;
+ $cf->LoadByName(
+ Name => $1,
+ LookupType => RT::Ticket->CustomFieldLookupType,
+ ObjectId => $ticketargs{Queue},
+ IncludeGlobal => 1,
+ );
next unless $cf->id;
$ticketargs{ "CustomField-" . $cf->id } = $args{$tag};
} elsif ($orig_tag) {
my $cf = RT::CustomField->new( $self->CurrentUser );
- $cf->LoadByName( Name => $orig_tag, Queue => $ticketargs{Queue} );
- $cf->LoadByName( Name => $orig_tag, Queue => 0 ) unless $cf->id;
+ $cf->LoadByName(
+ Name => $orig_tag,
+ LookupType => RT::Ticket->CustomFieldLookupType,
+ ObjectId => $ticketargs{Queue},
+ IncludeGlobal => 1,
+ );
next unless $cf->id;
$ticketargs{ "CustomField-" . $cf->id } = $args{$tag};
@@ -1012,19 +971,11 @@ sub GetUpdateTemplate {
$string .= "InitialPriority: " . $t->Priority . "\n";
$string .= "FinalPriority: " . $t->FinalPriority . "\n";
- foreach my $type ( sort keys %LINKTYPEMAP ) {
-
- # don't display duplicates
- if ( $type eq "HasMember"
- || $type eq "Members"
- || $type eq "MemberOf" )
- {
- next;
- }
+ foreach my $type ( RT::Link->DisplayTypes ) {
$string .= "$type: ";
- my $mode = $LINKTYPEMAP{$type}->{Mode};
- my $method = $LINKTYPEMAP{$type}->{Type};
+ my $mode = $RT::Link::TYPEMAP{$type}->{Mode};
+ my $method = $RT::Link::TYPEMAP{$type}->{Type};
my $links = '';
while ( my $link = $t->$method->Next ) {
@@ -1090,15 +1041,7 @@ sub GetCreateTemplate {
$string .= "InitialPriority: \n";
$string .= "FinalPriority: \n";
- foreach my $type ( keys %LINKTYPEMAP ) {
-
- # don't display duplicates
- if ( $type eq "HasMember"
- || $type eq 'Members'
- || $type eq 'MemberOf' )
- {
- next;
- }
+ foreach my $type ( RT::Link->DisplayTypes ) {
$string .= "$type: \n";
}
return $string;
@@ -1220,7 +1163,7 @@ sub PostProcess {
$RT::Logger->debug( "Handling links for " . $ticket->Id );
my %args = %{ shift(@$links) };
- foreach my $type ( keys %LINKTYPEMAP ) {
+ foreach my $type ( keys %RT::Link::TYPEMAP ) {
next unless ( defined $args{$type} );
foreach my $link (
ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
@@ -1247,8 +1190,8 @@ sub PostProcess {
}
my ( $wval, $wmsg ) = $ticket->AddLink(
- Type => $LINKTYPEMAP{$type}->{'Type'},
- $LINKTYPEMAP{$type}->{'Mode'} => $link,
+ Type => $RT::Link::TYPEMAP{$type}->{'Type'},
+ $RT::Link::TYPEMAP{$type}->{'Mode'} => $link,
Silent => 1
);
diff --git a/rt/lib/RT/Action/CreateTickets.pm.orig b/rt/lib/RT/Action/CreateTickets.pm.orig
index 542cbd27b..46791de58 100644
--- a/rt/lib/RT/Action/CreateTickets.pm.orig
+++ b/rt/lib/RT/Action/CreateTickets.pm.orig
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -960,6 +960,13 @@ sub GetDeferred {
my $links = shift;
my $postponed = shift;
+ # Unify the aliases for child/parent
+ $args->{$_} = [$args->{$_}]
+ for grep {$args->{$_} and not ref $args->{$_}} qw/members hasmember memberof/;
+ push @{$args->{'children'}}, @{delete $args->{'members'}} if $args->{'members'};
+ push @{$args->{'children'}}, @{delete $args->{'hasmember'}} if $args->{'hasmember'};
+ push @{$args->{'parents'}}, @{delete $args->{'memberof'}} if $args->{'memberof'};
+
# Deferred processing
push @$links,
(
diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm
index 04aa7eda9..8632d9d5c 100644
--- a/rt/lib/RT/Action/EscalatePriority.pm
+++ b/rt/lib/RT/Action/EscalatePriority.pm
@@ -71,6 +71,30 @@ Alternately, if you don't set a due date, the Priority will be incremented by 1
until it reaches the Final Priority. If a ticket without a due date has a Priority
greater than Final Priority, it will be decremented by 1.
+=head2 CONFIGURATION
+
+EsclatePriority's behavior can be controlled by two options:
+
+=over 4
+
+=item RecordTransaction
+
+If true (the default), the action casuses a transaction on the ticket
+when it is escalated. If false, the action updates the priority without
+running scrips or recording a transaction.
+
+=item UpdateLastUpdated
+
+If true (the default), the action updates the LastUpdated field when the
+ticket is escalated. You cannot set C<UpdateLastUpdated> to false unless
+C<RecordTransaction> is also false.
+
+=back
+
+To use these with C<rt-crontool>, specify them with C<--action-arg>:
+
+ --action-arg "RecordTransaction: 0, UpdateLastUpdated: 0"
+
=cut
@@ -88,67 +112,67 @@ sub Describe {
my $self = shift;
return (ref $self . " will move a ticket's priority toward its final priority.");
}
-
+
sub Prepare {
my $self = shift;
-
+
if ($self->TicketObj->Priority() == $self->TicketObj->FinalPriority()) {
- # no update necessary.
- return 0;
+ # no update necessary.
+ return 0;
}
-
+
#compute the number of days until the ticket is due
my $due = $self->TicketObj->DueObj();
-
+
# If we don't have a due date, adjust the priority by one
# until we hit the final priority
- if ($due->Unix() < 1) {
- if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){
- $self->{'prio'} = ($self->TicketObj->Priority - 1);
- return 1;
- }
- elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){
- $self->{'prio'} = ($self->TicketObj->Priority + 1);
- return 1;
- }
- # otherwise the priority is at the final priority. we don't need to
- # Continue
- else {
- return 0;
- }
+ if (not $due->IsSet) {
+ if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){
+ $self->{'prio'} = ($self->TicketObj->Priority - 1);
+ return 1;
+ }
+ elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){
+ $self->{'prio'} = ($self->TicketObj->Priority + 1);
+ return 1;
+ }
+ # otherwise the priority is at the final priority. we don't need to
+ # Continue
+ else {
+ return 0;
+ }
}
# we've got a due date. now there are other things we should do
- else {
+ else {
my $arg = $self->Argument || '';
my $now = time();
if ( $arg =~ /CurrentTime:\s*(\d+)/i ) {
$now = $1;
}
- my $diff_in_seconds = $due->Diff($now);
- my $diff_in_days = int( $diff_in_seconds / 86400);
-
- #if we haven't hit the due date yet
- if ($diff_in_days > 0 ) {
-
- # compute the difference between the current priority and the
- # final priority
-
- my $prio_delta =
- $self->TicketObj->FinalPriority() - $self->TicketObj->Priority;
-
- my $inc_priority_by = int( $prio_delta / $diff_in_days );
-
- #set the ticket's priority to that amount
- $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by;
-
- }
- #if $days is less than 1, set priority to final_priority
- else {
- $self->{'prio'} = $self->TicketObj->FinalPriority();
- }
+ my $diff_in_seconds = $due->Diff($now);
+ my $diff_in_days = int( $diff_in_seconds / 86400);
+
+ #if we haven't hit the due date yet
+ if ($diff_in_days > 0 ) {
+
+ # compute the difference between the current priority and the
+ # final priority
+
+ my $prio_delta =
+ $self->TicketObj->FinalPriority() - $self->TicketObj->Priority;
+
+ my $inc_priority_by = int( $prio_delta / $diff_in_days );
+
+ #set the ticket's priority to that amount
+ $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by;
+
+ }
+ #if $days is less than 1, set priority to final_priority
+ else {
+ $self->{'prio'} = $self->TicketObj->FinalPriority();
+ }
}
return 1;
@@ -156,11 +180,58 @@ sub Prepare {
sub Commit {
my $self = shift;
- my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'});
+ my $new_value = $self->{'prio'};
+ return 1 unless defined $new_value;
+
+ my $ticket = $self->TicketObj;
+ return 1 if $ticket->Priority == $new_value;
- unless ($val) {
- $RT::Logger->debug($self . " $msg");
- }
+ # Overide defaults from argument
+ my($record, $update) = (1, 1);
+ {
+ my $arg = $self->Argument || '';
+ if ( $arg =~ /RecordTransaction:\s*(\d+)/i ) {
+ $record = $1;
+ $RT::Logger->debug("Overrode RecordTransaction: $record");
+ }
+ if ( $arg =~ /UpdateLastUpdated:\s*(\d+)/i ) {
+ $update = $1;
+ $RT::Logger->debug("Overrode UpdateLastUpdated: $update");
+ }
+ # If creating a transaction, we have to update lastupdated
+ $update = 1 if $record;
+ }
+
+ $RT::Logger->debug(
+ 'Escalating priority of ticket #'. $ticket->Id
+ .' from '. $ticket->Priority .' to '. $new_value
+ .' and'. ($record? '': ' do not') .' record a transaction'
+ .' and'. ($update? '': ' do not') .' touch last updated field'
+ );
+
+ my ($val, $msg);
+ unless ( $record ) {
+ unless ( $update ) {
+ ( $val, $msg ) = $ticket->__Set(
+ Field => 'Priority',
+ Value => $new_value,
+ );
+ } else {
+ ( $val, $msg ) = $ticket->_Set(
+ Field => 'Priority',
+ Value => $new_value,
+ RecordTransaction => 0,
+ );
+ }
+ } else {
+ ($val, $msg) = $ticket->SetPriority($new_value);
+ }
+
+ unless ($val) {
+ $RT::Logger->error( "Couldn't set new priority value: $msg");
+ return (0, $msg);
+ }
+ return 1;
}
RT::Base->_ImportOverlays();
diff --git a/rt/lib/RT/Action/LinearEscalate.pm b/rt/lib/RT/Action/LinearEscalate.pm
index cc88b1dea..960703327 100755
--- a/rt/lib/RT/Action/LinearEscalate.pm
+++ b/rt/lib/RT/Action/LinearEscalate.pm
@@ -98,7 +98,7 @@ the Due date. Tickets without due date B<are not updated>.
=head1 CONFIGURATION
Initial and Final priorities are controlled by queue's options
-and can be defined using the web UI via Configuration tab. This
+and can be defined using the web UI via Admin tab. This
action should handle correctly situations when initial priority
is greater than final.
@@ -140,8 +140,6 @@ use strict;
use warnings;
use base qw(RT::Action);
-our $VERSION = '0.06';
-
#Do what we need to do and send it out.
#What does this type of Action does
@@ -157,8 +155,7 @@ sub Prepare {
my $ticket = $self->TicketObj;
- my $due = $ticket->DueObj->Unix;
- unless ( $due > 0 ) {
+ unless ( $ticket->DueObj->IsSet ) {
$RT::Logger->debug('Due is not set. Not escalating.');
return 1;
}
@@ -183,9 +180,8 @@ sub Prepare {
# now we know we have a due date. for every day that passes,
# increment priority according to the formula
- my $starts = $ticket->StartsObj->Unix;
- $starts = $ticket->CreatedObj->Unix unless $starts > 0;
- my $now = time;
+ my $starts = $ticket->StartsObj->IsSet ? $ticket->StartsObj->Unix : $ticket->CreatedObj->Unix;
+ my $now = time;
# do nothing if we didn't reach starts or created date
if ( $starts > $now ) {
@@ -193,12 +189,13 @@ sub Prepare {
return 1;
}
+ my $due = $ticket->DueObj->Unix;
$due = $starts + 1 if $due <= $starts; # +1 to avoid div by zero
my $percent_complete = ($now-$starts)/($due - $starts);
my $new_priority = int($percent_complete * $priority_range) + ($ticket->InitialPriority || 0);
- $new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority;
+ $new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority;
$self->{'new_priority'} = $new_priority;
return 1;
diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm
index 0b75b2084..633206ed2 100755
--- a/rt/lib/RT/Action/Notify.pm
+++ b/rt/lib/RT/Action/Notify.pm
@@ -71,8 +71,8 @@ sub Prepare {
=head2 SetRecipients
-Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All.
-Explicitly B<does not> notify the creator of the transaction by default
+Sets the recipients of this message to Owner, Requestor, AdminCc, Cc or All.
+Explicitly B<does not> notify the creator of the transaction by default.
=cut
@@ -107,6 +107,7 @@ sub SetRecipients {
if ( $arg =~ /\bOwner\b/
&& $ticket->OwnerObj->id != RT->Nobody->id
&& $ticket->OwnerObj->EmailAddress
+ && not $ticket->OwnerObj->Disabled
) {
# If we're not sending to Ccs or requestors,
# then the Owner can be the To.
@@ -131,24 +132,9 @@ sub SetRecipients {
}
}
- my $creatorObj = $self->TransactionObj->CreatorObj;
- my $creator = $creatorObj->EmailAddress() || '';
-
- #Strip the sender out of the To, Cc and AdminCc and set the
- # recipients fields used to build the message by the superclass.
- # unless a flag is set
- my $TransactionCurrentUser = RT::CurrentUser->new;
- $TransactionCurrentUser->LoadByName($creatorObj->Name);
- if (RT->Config->Get('NotifyActor',$TransactionCurrentUser)) {
- @{ $self->{'To'} } = @To;
- @{ $self->{'Cc'} } = @Cc;
- @{ $self->{'Bcc'} } = @Bcc;
- }
- else {
- @{ $self->{'To'} } = grep ( lc $_ ne lc $creator, @To );
- @{ $self->{'Cc'} } = grep ( lc $_ ne lc $creator, @Cc );
- @{ $self->{'Bcc'} } = grep ( lc $_ ne lc $creator, @Bcc );
- }
+ @{ $self->{'To'} } = @To;
+ @{ $self->{'Cc'} } = @Cc;
+ @{ $self->{'Bcc'} } = @Bcc;
@{ $self->{'PseudoTo'} } = @PseudoTo;
if ( $arg =~ /\bOtherRecipients\b/ ) {
@@ -161,6 +147,34 @@ sub SetRecipients {
}
}
+=head2 RemoveInappropriateRecipients
+
+Remove transaction creator as appropriate for the NotifyActor setting.
+
+To send email to the selected receipients regardless of RT's NotifyActor
+configuration, include AlwaysNotifyActor in the list of arguments.
+
+=cut
+
+sub RemoveInappropriateRecipients {
+ my $self = shift;
+
+ my $creatorObj = $self->TransactionObj->CreatorObj;
+ my $creator = $creatorObj->EmailAddress() || '';
+ my $TransactionCurrentUser = RT::CurrentUser->new;
+ $TransactionCurrentUser->LoadByName($creatorObj->Name);
+
+ $self->RecipientFilter(
+ Callback => sub {
+ return unless lc $_[0] eq lc $creator;
+ return "not sending to $creator, creator of the transaction, due to NotifyActor setting";
+ },
+ ) unless RT->Config->Get('NotifyActor',$TransactionCurrentUser)
+ || $self->Argument =~ /\bAlwaysNotifyActor\b/;
+
+ $self->SUPER::RemoveInappropriateRecipients();
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/rt/lib/RT/Action/NotifyGroup.pm b/rt/lib/RT/Action/NotifyGroup.pm
index 847d60bd0..5646d7e34 100644
--- a/rt/lib/RT/Action/NotifyGroup.pm
+++ b/rt/lib/RT/Action/NotifyGroup.pm
@@ -73,6 +73,10 @@ require RT::Group;
=head2 SetRecipients
Sets the recipients of this message to Groups and/or Users.
+Respects RT's NotifyActor configuration.
+
+To send email to the selected receipients regardless of RT's NotifyActor
+configuration, include AlwaysNotifyActor in the list of arguments.
=cut
@@ -84,16 +88,6 @@ sub SetRecipients {
$self->_HandleArgument( $_ );
}
- my $creatorObj = $self->TransactionObj->CreatorObj;
- my $creator = $creatorObj->EmailAddress();
-
- my $TransactionCurrentUser = RT::CurrentUser->new;
- $TransactionCurrentUser->LoadByName($creatorObj->Name);
-
- unless (RT->Config->Get('NotifyActor',$TransactionCurrentUser)) {
- @{ $self->{'To'} } = grep ( !/^\Q$creator\E$/, @{ $self->{'To'} } );
- }
-
$self->{'seen_ueas'} = {};
return 1;
@@ -103,6 +97,8 @@ sub _HandleArgument {
my $self = shift;
my $instance = shift;
+ return if ( $instance eq 'AlwaysNotifyActor' );
+
if ( $instance !~ /\D/ ) {
my $obj = RT::Principal->new( $self->CurrentUser );
$obj->Load( $instance );
diff --git a/rt/lib/RT/Action/NotifyGroupAsComment.pm b/rt/lib/RT/Action/NotifyGroupAsComment.pm
index a1c0d0851..dd69fa3de 100644
--- a/rt/lib/RT/Action/NotifyGroupAsComment.pm
+++ b/rt/lib/RT/Action/NotifyGroupAsComment.pm
@@ -62,14 +62,12 @@ package RT::Action::NotifyGroupAsComment;
use strict;
use warnings;
-use RT::Action::NotifyGroup;
-
use base qw(RT::Action::NotifyGroup);
sub SetReturnAddress {
- my $self = shift;
- $self->{'comment'} = 1;
- return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
+ my $self = shift;
+ $self->{'comment'} = 1;
+ return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
}
=head1 AUTHOR
diff --git a/rt/lib/RT/Action/NotifyOwnerOrAdminCc.pm b/rt/lib/RT/Action/NotifyOwnerOrAdminCc.pm
new file mode 100644
index 000000000..2d6e423b9
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyOwnerOrAdminCc.pm
@@ -0,0 +1,76 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 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 }}}
+
+package RT::Action::NotifyOwnerOrAdminCc;
+
+use strict;
+use warnings;
+
+use base qw(RT::Action::Notify);
+
+use Email::Address;
+
+=head1 Notify Owner or AdminCc
+
+If the owner of this ticket is Nobody, notify the AdminCcs. Otherwise, only notify the Owner.
+
+=cut
+
+sub Argument {
+ my $self = shift;
+ my $ticket = $self->TicketObj;
+ if ($ticket->Owner == RT->Nobody->id) {
+ return 'AdminCc';
+ } else {
+ return 'Owner';
+ }
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/rt/lib/RT/Action/OpenOnStarted.pm b/rt/lib/RT/Action/OpenOnStarted.pm
new file mode 100644
index 000000000..0995e9480
--- /dev/null
+++ b/rt/lib/RT/Action/OpenOnStarted.pm
@@ -0,0 +1,87 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 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 }}}
+
+=head1 NAME
+
+ RT::Action::OpenOnStarted
+
+=head1 DESCRIPTION
+
+OpenOnStarted is a ScripAction which sets a ticket status to open when the
+ticket is given a Started value. Before this commit, this functionality used to
+happen in RT::Ticket::SetStarted which made the functionality the policy for
+setting started. Moving the functionality to a scrip allows for it to be
+disabled if it is not desired.
+
+=cut
+
+package RT::Action::OpenOnStarted;
+use base 'RT::Action';
+use strict;
+use warnings;
+
+sub Prepare {
+ my $self = shift;
+ return 0 unless $self->TransactionObj->Type eq "Set";
+ return 0 unless $self->TransactionObj->Field eq "Started";
+ return 1;
+}
+
+sub Commit {
+ my $self = shift;
+ my $ticket = $self->TicketObj;
+
+ my $next = $ticket->FirstActiveStatus;
+ $ticket->SetStatus( $next ) if defined $next;
+
+ return 1;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm
index d9ee7f153..575c92edf 100644
--- a/rt/lib/RT/Action/RecordComment.pm
+++ b/rt/lib/RT/Action/RecordComment.pm
@@ -59,11 +59,12 @@ been started, to make a comment on the ticket.
=head1 SYNOPSIS
-my $action_obj = RT::Action::RecordComment->new('TicketObj' => $ticket_obj,
- 'TemplateObj' => $template_obj,
- );
-my $result = $action_obj->Prepare();
-$action_obj->Commit() if $result;
+ my $action_obj = RT::Action::RecordComment->new(
+ 'TicketObj' => $ticket_obj,
+ 'TemplateObj' => $template_obj,
+ );
+ my $result = $action_obj->Prepare();
+ $action_obj->Commit() if $result;
=head1 METHODS
@@ -79,8 +80,8 @@ will give us a loop.
sub Prepare {
my $self = shift;
if (defined $self->{'TransactionObj'} &&
- $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) {
- return undef;
+ $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) {
+ return undef;
}
return 1;
}
@@ -103,14 +104,14 @@ sub CreateTransaction {
my $self = shift;
my ($result, $msg) = $self->{'TemplateObj'}->Parse(
- TicketObj => $self->{'TicketObj'});
+ TicketObj => $self->{'TicketObj'});
return undef unless $result;
-
+
my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Comment(
- MIMEObj => $self->TemplateObj->MIMEObj);
+ MIMEObj => $self->TemplateObj->MIMEObj);
$self->{'TransactionObj'} = $transaction;
}
-
+
RT::Base->_ImportOverlays();
diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm
index 4dd8eba3e..e407b9ff7 100644
--- a/rt/lib/RT/Action/RecordCorrespondence.pm
+++ b/rt/lib/RT/Action/RecordCorrespondence.pm
@@ -59,12 +59,12 @@ been started, to create a correspondence on the ticket.
=head1 SYNOPSIS
-my $action_obj = RT::Action::RecordCorrespondence->new(
- 'TicketObj' => $ticket_obj,
- 'TemplateObj' => $template_obj,
- );
-my $result = $action_obj->Prepare();
-$action_obj->Commit() if $result;
+ my $action_obj = RT::Action::RecordCorrespondence->new(
+ 'TicketObj' => $ticket_obj,
+ 'TemplateObj' => $template_obj,
+ );
+ my $result = $action_obj->Prepare();
+ $action_obj->Commit() if $result;
=head1 METHODS
@@ -80,8 +80,8 @@ will give us a loop.
sub Prepare {
my $self = shift;
if (defined $self->{'TransactionObj'} &&
- $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) {
- return undef;
+ $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) {
+ return undef;
}
return 1;
}
@@ -104,14 +104,14 @@ sub CreateTransaction {
my $self = shift;
my ($result, $msg) = $self->{'TemplateObj'}->Parse(
- TicketObj => $self->{'TicketObj'});
+ TicketObj => $self->{'TicketObj'});
return undef unless $result;
-
+
my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Correspond(
- MIMEObj => $self->TemplateObj->MIMEObj);
+ MIMEObj => $self->TemplateObj->MIMEObj);
$self->{'TransactionObj'} = $transaction;
}
-
+
RT::Base->_ImportOverlays();
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
index af3a6bf8a..80b0054ee 100755
--- a/rt/lib/RT/Action/SendEmail.pm
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -135,13 +135,15 @@ Builds an outgoing email we're going to send using scrip's template.
sub Prepare {
my $self = shift;
- my ( $result, $message ) = $self->TemplateObj->Parse(
- Argument => $self->Argument,
- TicketObj => $self->TicketObj,
- TransactionObj => $self->TransactionObj
- );
- if ( !$result ) {
- return (undef);
+ unless ( $self->TemplateObj->MIMEObj ) {
+ my ( $result, $message ) = $self->TemplateObj->Parse(
+ Argument => $self->Argument,
+ TicketObj => $self->TicketObj,
+ TransactionObj => $self->TransactionObj
+ );
+ if ( !$result ) {
+ return (undef);
+ }
}
my $MIMEObj = $self->TemplateObj->MIMEObj;
@@ -179,12 +181,6 @@ sub Prepare {
&& !$MIMEObj->head->get('To')
&& ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
- # We should never have to set the MIME-Version header
- $self->SetHeader( 'MIME-Version', '1.0' );
-
- # fsck.com #5959: Since RT sends 8bit mail, we should say so.
- $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
-
# For security reasons, we only send out textual mails.
foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
my $type = $part->mime_type || 'text/plain';
@@ -195,9 +191,12 @@ sub Prepare {
$part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
}
- RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
- RT->Config->Get('EmailOutputEncoding'),
- 'mime_words_ok', );
+ RT::I18N::SetMIMEEntityToEncoding(
+ Entity => $MIMEObj,
+ Encoding => RT->Config->Get('EmailOutputEncoding'),
+ PreserveWords => 1,
+ IsOut => 1,
+ );
# Build up a MIME::Entity that looks like the original message.
$self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
@@ -218,7 +217,7 @@ sub Prepare {
'Success';
}
- return $result;
+ return 1;
}
=head2 To
@@ -407,6 +406,7 @@ sub AddAttachment {
Data => $attach->OriginalContent,
Disposition => $disp,
Filename => $self->MIMEEncodeString( $attach->Filename ),
+ Id => $attach->GetHeader('Content-ID'),
'RT-Attachment:' => $self->TicketObj->Id . "/"
. $self->TransactionObj->Id . "/"
. $attach->id,
@@ -462,11 +462,11 @@ sub AddTicket {
my $attachs = RT::Attachments->new( $self->TransactionObj->CreatorObj );
my $txn_alias = $attachs->TransactionAlias;
- $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
$attachs->Limit(
- ALIAS => $txn_alias,
- FIELD => 'Type',
- VALUE => 'Correspond'
+ ALIAS => $txn_alias,
+ FIELD => 'Type',
+ OPERATOR => 'IN',
+ VALUE => [qw(Create Correspond)],
);
$attachs->LimitByTicket($tid);
$attachs->LimitNotEmpty;
@@ -601,16 +601,10 @@ sub SetRTSpecialHeaders {
}
}
- if (my $precedence = RT->Config->Get('DefaultMailPrecedence')
- and !$self->TemplateObj->MIMEObj->head->get("Precedence")
- ) {
- $self->SetHeader( 'Precedence', $precedence );
- }
-
$self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
- $self->SetHeader( 'RT-Ticket',
+ $self->SetHeader( 'X-RT-Ticket',
RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
- $self->SetHeader( 'Managed-by',
+ $self->SetHeader( 'X-Managed-by',
"RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
# XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
@@ -619,7 +613,7 @@ sub SetRTSpecialHeaders {
and ! defined $self->TemplateObj->MIMEObj->head->get("RT-Originator")
and RT->Config->Get('UseOriginatorHeader')
) {
- $self->SetHeader( 'RT-Originator', $email );
+ $self->SetHeader( 'X-RT-Originator', $email );
}
}
@@ -739,15 +733,29 @@ Remove addresses that are RT addresses or that are on this transaction's blackli
=cut
+my %squelch_reasons = (
+ 'not privileged'
+ => "because autogenerated messages are configured to only be sent to privileged users (RedistributeAutoGeneratedMessages)",
+ 'squelch:attachment'
+ => "by RT-Squelch-Replies-To header in the incoming message",
+ 'squelch:transaction'
+ => "by notification checkboxes for this transaction",
+ 'squelch:ticket'
+ => "by notification checkboxes on this ticket's People page",
+);
+
+
sub RemoveInappropriateRecipients {
my $self = shift;
- my @blacklist = ();
+ my %blacklist = ();
# If there are no recipients, don't try to send the message.
# If the transaction has content and has the header RT-Squelch-Replies-To
my $msgid = Encode::decode( "UTF-8", $self->TemplateObj->MIMEObj->head->get('Message-Id') );
+ chomp $msgid;
+
if ( my $attachment = $self->TransactionObj->Attachments->First ) {
if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
@@ -756,7 +764,9 @@ sub RemoveInappropriateRecipients {
# caused by one of the watcher addresses being broken.
# Default ("true") is to redistribute, for historical reasons.
- if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
+ my $redistribute = RT->Config->Get('RedistributeAutoGeneratedMessages');
+
+ if ( !$redistribute ) {
# Don't send to any watchers.
@{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
@@ -764,16 +774,15 @@ sub RemoveInappropriateRecipients {
. " The incoming message was autogenerated. "
. "Not redistributing this message based on site configuration."
);
- } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
- 'privileged' )
- {
+ } elsif ( $redistribute eq 'privileged' ) {
# Only send to "privileged" watchers.
foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
foreach my $addr ( @{ $self->{$type} } ) {
my $user = RT::User->new(RT->SystemUser);
$user->LoadByEmail($addr);
- push @blacklist, $addr unless $user->id && $user->Privileged;
+ $blacklist{ $addr } ||= 'not privileged'
+ unless $user->id && $user->Privileged;
}
}
$RT::Logger->info( $msgid
@@ -784,48 +793,88 @@ sub RemoveInappropriateRecipients {
}
if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
- push @blacklist, split( /,/, $squelch );
+ $blacklist{ $_->address } ||= 'squelch:attachment'
+ foreach Email::Address->parse( $squelch );
}
}
- # Let's grab the SquelchMailTo attributes and push those entries into the @blacklisted
- push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo, $self->TransactionObj->SquelchMailTo;
+ # Let's grab the SquelchMailTo attributes and push those entries
+ # into the blacklisted
+ $blacklist{ $_->Content } ||= 'squelch:transaction'
+ foreach $self->TransactionObj->SquelchMailTo;
+ $blacklist{ $_->Content } ||= 'squelch:ticket'
+ foreach $self->TicketObj->SquelchMailTo;
+
+ # canonicalize emails
+ foreach my $address ( keys %blacklist ) {
+ my $reason = delete $blacklist{ $address };
+ $blacklist{ lc $_ } = $reason
+ foreach map RT::User->CanonicalizeEmailAddress( $_->address ),
+ Email::Address->parse( $address );
+ }
- # Cycle through the people we're sending to and pull out anyone on the
- # system blacklist
+ $self->RecipientFilter(
+ Callback => sub {
+ return unless RT::EmailParser->IsRTAddress( $_[0] );
+ return "$_[0] appears to point to this RT instance. Skipping";
+ },
+ All => 1,
+ );
- # Trim leading and trailing spaces.
- @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) }
- Email::Address->parse( join ', ', grep defined, @blacklist );
+ $self->RecipientFilter(
+ Callback => sub {
+ return unless $blacklist{ lc $_[0] };
+ return "$_[0] is blacklisted $squelch_reasons{ $blacklist{ lc $_[0] } }. Skipping";
+ },
+ );
- foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+
+ # Cycle through the people we're sending to and pull out anyone that meets any of the callbacks
+ for my $type (@EMAIL_RECIPIENT_HEADERS) {
my @addrs;
- foreach my $addr ( @{ $self->{$type} } ) {
- # Weed out any RT addresses. We really don't want to talk to ourselves!
- # If we get a reply back, that means it's not an RT address
- if ( !RT::EmailParser->CullRTAddresses($addr) ) {
- $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
- next;
- }
- if ( grep $addr eq $_, @blacklist ) {
- $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
- next;
+ ADDRESS:
+ for my $addr ( @{ $self->{$type} } ) {
+ for my $filter ( map {$_->{Callback}} @{$self->{RecipientFilter}} ) {
+ my $skip = $filter->($addr);
+ next unless $skip;
+ $RT::Logger->info( "$msgid $skip" );
+ next ADDRESS;
}
push @addrs, $addr;
}
- foreach my $addr ( @{ $self->{'NoSquelch'}{$type} || [] } ) {
- # never send email to itself
- if ( !RT::EmailParser->CullRTAddresses($addr) ) {
- $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
- next;
+
+ NOSQUELCH_ADDRESS:
+ for my $addr ( @{ $self->{NoSquelch}{$type} } ) {
+ for my $filter ( map {$_->{Callback}} grep {$_->{All}} @{$self->{RecipientFilter}} ) {
+ my $skip = $filter->($addr);
+ next unless $skip;
+ $RT::Logger->info( "$msgid $skip" );
+ next NOSQUELCH_ADDRESS;
}
push @addrs, $addr;
}
+
@{ $self->{$type} } = @addrs;
}
}
+=head2 RecipientFilter Callback => SUB, [All => 1]
+
+Registers a filter to be applied to addresses by
+L<RemoveInappropriateRecipients>. The C<Callback> will be called with
+one address at a time, and should return false if the address should
+receive mail, or a message explaining why it should not be. Passing a
+true value for C<All> will cause the filter to also be applied to
+NoSquelch (one-time Cc and Bcc) recipients as well.
+
+=cut
+
+sub RecipientFilter {
+ my $self = shift;
+ push @{ $self->{RecipientFilter}}, {@_};
+}
+
=head2 SetReturnAddress is_comment => BOOLEAN
Calculate and set From and Reply-To headers based on the is_comment flag.
@@ -1079,13 +1128,8 @@ Returns a fake Message-ID: header for the ticket to allow a base level of thread
=cut
sub PseudoReference {
-
my $self = shift;
- my $pseudo_ref
- = '<RT-Ticket-'
- . $self->TicketObj->id . '@'
- . RT->Config->Get('Organization') . '>';
- return $pseudo_ref;
+ return RT::Interface::Email::PseudoReference( $self->TicketObj );
}
=head2 SetHeaderAsEncoding($field_name, $charset_encoding)
@@ -1101,11 +1145,6 @@ sub SetHeaderAsEncoding {
my $head = $self->TemplateObj->MIMEObj->head;
- if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
- $head->replace( $field, Encode::encode( "UTF-8", RT->Config->Get('SMTPFrom') ) );
- return;
- }
-
my $value = Encode::decode("UTF-8", $head->get( $field ));
$value = $self->MIMEEncodeString( $value, $enc ); # Returns bytes
$head->replace( $field, $value );
diff --git a/rt/lib/RT/Action/SendEmail.pm.orig b/rt/lib/RT/Action/SendEmail.pm.orig
index 0f11cc141..af3a6bf8a 100755
--- a/rt/lib/RT/Action/SendEmail.pm.orig
+++ b/rt/lib/RT/Action/SendEmail.pm.orig
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -258,7 +258,7 @@ sub Bcc {
sub AddressesFromHeader {
my $self = shift;
my $field = shift;
- my $header = $self->TemplateObj->MIMEObj->head->get($field);
+ my $header = Encode::decode("UTF-8",$self->TemplateObj->MIMEObj->head->get($field));
my @addresses = Email::Address->parse($header);
return (@addresses);
@@ -277,7 +277,7 @@ sub SendMessage {
# ability to pass @_ to a 'post' routine.
my ( $self, $MIMEObj ) = @_;
- my $msgid = $MIMEObj->head->get('Message-ID');
+ my $msgid = Encode::decode( "UTF-8", $MIMEObj->head->get('Message-ID') );
chomp $msgid;
$self->ScripActionObj->{_Message_ID}++;
@@ -300,7 +300,7 @@ sub SendMessage {
my $success = $msgid . " sent ";
foreach (@EMAIL_RECIPIENT_HEADERS) {
- my $recipients = $MIMEObj->head->get($_);
+ my $recipients = Encode::decode( "UTF-8", $MIMEObj->head->get($_) );
$success .= " $_: " . $recipients if $recipients;
}
@@ -531,7 +531,7 @@ sub RecordOutgoingMailTransaction {
$type = 'EmailRecord';
}
- my $msgid = $MIMEObj->head->get('Message-ID');
+ my $msgid = Encode::decode( "UTF-8", $MIMEObj->head->get('Message-ID') );
chomp $msgid;
my ( $id, $msg ) = $transaction->Create(
@@ -616,6 +616,7 @@ sub SetRTSpecialHeaders {
# XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
# refactored into user's method.
if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress
+ and ! defined $self->TemplateObj->MIMEObj->head->get("RT-Originator")
and RT->Config->Get('UseOriginatorHeader')
) {
$self->SetHeader( 'RT-Originator', $email );
@@ -649,7 +650,7 @@ sub DeferDigestRecipients {
# Have to get the list of addresses directly from the MIME header
# at this point.
- $RT::Logger->debug( $self->TemplateObj->MIMEObj->head->as_string );
+ $RT::Logger->debug( Encode::decode( "UTF-8", $self->TemplateObj->MIMEObj->head->as_string ) );
foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) {
next unless $rcpt;
my $user_obj = RT::User->new(RT->SystemUser);
@@ -746,7 +747,7 @@ sub RemoveInappropriateRecipients {
# If there are no recipients, don't try to send the message.
# If the transaction has content and has the header RT-Squelch-Replies-To
- my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
+ my $msgid = Encode::decode( "UTF-8", $self->TemplateObj->MIMEObj->head->get('Message-Id') );
if ( my $attachment = $self->TransactionObj->Attachments->First ) {
if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
@@ -922,7 +923,8 @@ sub GetFriendlyName {
=head2 SetHeader FIELD, VALUE
-Set the FIELD of the current MIME object into VALUE.
+Set the FIELD of the current MIME object into VALUE, which should be in
+characters, not bytes. Returns the new header, in bytes.
=cut
@@ -935,7 +937,7 @@ sub SetHeader {
chomp $field;
my $head = $self->TemplateObj->MIMEObj->head;
$head->fold_length( $field, 10000 );
- $head->replace( $field, $val );
+ $head->replace( $field, Encode::encode( "UTF-8", $val ) );
return $head->get($field);
}
@@ -976,7 +978,7 @@ sub SetSubject {
$subject =~ s/(\r\n|\n|\s)/ /g;
- $self->SetHeader( 'Subject', Encode::encode_utf8( $subject ) );
+ $self->SetHeader( 'Subject', $subject );
}
@@ -992,11 +994,9 @@ sub SetSubjectToken {
my $head = $self->TemplateObj->MIMEObj->head;
$self->SetHeader(
Subject =>
- Encode::encode_utf8(
- RT::Interface::Email::AddSubjectTag(
- Encode::decode_utf8( $head->get('Subject') ),
- $self->TicketObj,
- ),
+ RT::Interface::Email::AddSubjectTag(
+ Encode::decode( "UTF-8", $head->get('Subject') ),
+ $self->TicketObj,
),
);
}
@@ -1090,7 +1090,8 @@ sub PseudoReference {
=head2 SetHeaderAsEncoding($field_name, $charset_encoding)
-This routine converts the field into specified charset encoding.
+This routine converts the field into specified charset encoding, then
+applies the MIME-Header transfer encoding.
=cut
@@ -1101,12 +1102,12 @@ sub SetHeaderAsEncoding {
my $head = $self->TemplateObj->MIMEObj->head;
if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
- $head->replace( $field, RT->Config->Get('SMTPFrom') );
+ $head->replace( $field, Encode::encode( "UTF-8", RT->Config->Get('SMTPFrom') ) );
return;
}
- my $value = $head->get( $field );
- $value = $self->MIMEEncodeString( $value, $enc );
+ my $value = Encode::decode("UTF-8", $head->get( $field ));
+ $value = $self->MIMEEncodeString( $value, $enc ); # Returns bytes
$head->replace( $field, $value );
}
@@ -1116,7 +1117,8 @@ sub SetHeaderAsEncoding {
Takes a perl string and optional encoding pass it over
L<RT::Interface::Email/EncodeToMIME>.
-Basicly encode a string using B encoding according to RFC2047.
+Basicly encode a string using B encoding according to RFC2047, returning
+bytes.
=cut
diff --git a/rt/lib/RT/Action/SendForward.pm b/rt/lib/RT/Action/SendForward.pm
new file mode 100644
index 000000000..5fad224b0
--- /dev/null
+++ b/rt/lib/RT/Action/SendForward.pm
@@ -0,0 +1,138 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2015 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 }}}
+
+#
+package RT::Action::SendForward;
+
+use strict;
+use warnings;
+
+use base qw(RT::Action::SendEmail);
+
+use Email::Address;
+
+=head2 Prepare
+
+=cut
+
+sub Prepare {
+ my $self = shift;
+
+ my $txn = $self->TransactionObj;
+
+ if ( $txn->Type eq 'Forward Transaction' ) {
+ my $forwarded_txn = RT::Transaction->new( $self->CurrentUser );
+ $forwarded_txn->Load( $txn->Field );
+ $self->{ForwardedTransactionObj} = $forwarded_txn;
+ }
+
+ my ( $result, $message ) = $self->TemplateObj->Parse(
+ Argument => $self->Argument,
+ Ticket => $self->TicketObj,
+ Transaction => $self->ForwardedTransactionObj,
+ ForwardTransaction => $self->TransactionObj,
+ );
+
+ if ( !$result ) {
+ return (undef);
+ }
+
+ my $mime = $self->TemplateObj->MIMEObj;
+ $mime->make_multipart unless $mime->is_multipart;
+
+ my $entity;
+ if ( $txn->Type eq 'Forward Transaction' ) {
+ $entity = $self->ForwardedTransactionObj->ContentAsMIME;
+ }
+ else {
+ my $txns = $self->TicketObj->Transactions;
+ $txns->Limit(
+ FIELD => 'Type',
+ OPERATOR => 'IN',
+ VALUE => [qw(Create Correspond)],
+ );
+
+ $entity = MIME::Entity->build(
+ Type => 'multipart/mixed',
+ Description => 'forwarded ticket',
+ );
+ $entity->add_part($_) foreach
+ map $_->ContentAsMIME,
+ @{ $txns->ItemsArrayRef };
+ }
+
+ $mime->add_part($entity);
+
+ my $txn_attachment = $self->TransactionObj->Attachments->First;
+ for my $header (qw/From To Cc Bcc/) {
+ if ( $txn_attachment->GetHeader( $header ) ) {
+ $mime->head->replace( $header => Encode::encode( "UTF-8", $txn_attachment->GetHeader($header) ) );
+ }
+ }
+
+ if ( RT->Config->Get('ForwardFromUser') ) {
+ $mime->head->replace( 'X-RT-Sign' => 0 );
+ }
+
+ $self->SUPER::Prepare();
+}
+
+sub SetSubjectToken {
+ my $self = shift;
+ return if RT->Config->Get('ForwardFromUser');
+ $self->SUPER::SetSubjectToken(@_);
+}
+
+sub ForwardedTransactionObj {
+ my $self = shift;
+ return $self->{'ForwardedTransactionObj'};
+}
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/rt/lib/RT/Action/SetStatus.pm b/rt/lib/RT/Action/SetStatus.pm
index 2f932ecfa..d763b9b7b 100644
--- a/rt/lib/RT/Action/SetStatus.pm
+++ b/rt/lib/RT/Action/SetStatus.pm
@@ -101,7 +101,7 @@ sub Prepare {
my $self = shift;
my $ticket = $self->TicketObj;
- my $lifecycle = $ticket->QueueObj->Lifecycle;
+ my $lifecycle = $ticket->LifecycleObj;
my $status = $ticket->Status;
my $argument = $self->Argument;