diff options
Diffstat (limited to 'rt/lib/RT/Action')
-rw-r--r-- | rt/lib/RT/Action/AutoOpen.pm | 3 | ||||
-rw-r--r-- | rt/lib/RT/Action/AutoOpenInactive.pm | 105 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Autoreply.pm | 14 | ||||
-rw-r--r-- | rt/lib/RT/Action/CreateTickets.pm | 113 | ||||
-rw-r--r-- | rt/lib/RT/Action/CreateTickets.pm.orig | 9 | ||||
-rw-r--r-- | rt/lib/RT/Action/EscalatePriority.pm | 165 | ||||
-rwxr-xr-x | rt/lib/RT/Action/LinearEscalate.pm | 15 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Notify.pm | 54 | ||||
-rw-r--r-- | rt/lib/RT/Action/NotifyGroup.pm | 16 | ||||
-rw-r--r-- | rt/lib/RT/Action/NotifyGroupAsComment.pm | 8 | ||||
-rw-r--r-- | rt/lib/RT/Action/NotifyOwnerOrAdminCc.pm | 76 | ||||
-rw-r--r-- | rt/lib/RT/Action/OpenOnStarted.pm | 87 | ||||
-rw-r--r-- | rt/lib/RT/Action/RecordComment.pm | 23 | ||||
-rw-r--r-- | rt/lib/RT/Action/RecordCorrespondence.pm | 24 | ||||
-rwxr-xr-x | rt/lib/RT/Action/SendEmail.pm | 181 | ||||
-rwxr-xr-x | rt/lib/RT/Action/SendEmail.pm.orig | 42 | ||||
-rw-r--r-- | rt/lib/RT/Action/SendForward.pm | 138 | ||||
-rw-r--r-- | rt/lib/RT/Action/SetStatus.pm | 2 |
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; |