summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Action
diff options
context:
space:
mode:
authorivan <ivan>2009-12-31 13:16:41 +0000
committerivan <ivan>2009-12-31 13:16:41 +0000
commitb4b0c7e72d7eaee2fbfc7022022c9698323203dd (patch)
treeba4cd21399e412c32fe3737eaa8478e3271509f9 /rt/lib/RT/Action
parent2dfda73eeb3eae2d4f894099754794ef07d060dd (diff)
import rt 3.8.7
Diffstat (limited to 'rt/lib/RT/Action')
-rw-r--r--rt/lib/RT/Action/AutoOpen.pm52
-rwxr-xr-xrt/lib/RT/Action/Autoreply.pm63
-rw-r--r--rt/lib/RT/Action/CreateTickets.pm265
-rw-r--r--rt/lib/RT/Action/EscalatePriority.pm11
-rw-r--r--rt/lib/RT/Action/ExtractSubjectTag.pm103
-rwxr-xr-xrt/lib/RT/Action/Generic.pm177
-rwxr-xr-xrt/lib/RT/Action/LinearEscalate.pm279
-rwxr-xr-xrt/lib/RT/Action/Notify.pm76
-rwxr-xr-xrt/lib/RT/Action/NotifyAsComment.pm10
-rw-r--r--rt/lib/RT/Action/NotifyGroup.pm209
-rw-r--r--rt/lib/RT/Action/NotifyGroupAsComment.pm91
-rw-r--r--rt/lib/RT/Action/RecordComment.pm9
-rw-r--r--rt/lib/RT/Action/RecordCorrespondence.pm9
-rw-r--r--rt/lib/RT/Action/ResolveMembers.pm9
-rwxr-xr-xrt/lib/RT/Action/SendEmail.pm1055
-rw-r--r--rt/lib/RT/Action/SetPriority.pm9
-rw-r--r--rt/lib/RT/Action/UserDefined.pm9
17 files changed, 1398 insertions, 1038 deletions
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm
index 004ed13cc..e1cf0ae7c 100644
--- a/rt/lib/RT/Action/AutoOpen.pm
+++ b/rt/lib/RT/Action/AutoOpen.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,28 +45,25 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
-# This Action will open the BASE if a dependent is resolved.
+# This Action will open the BASE if a dependent is resolved.
package RT::Action::AutoOpen;
-require RT::Action::Generic;
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
+use warnings;
-#Do what we need to do and send it out.
+use base qw(RT::Action);
-#What does this type of Action does
+=head1 DESCRIPTION
-# {{{ sub Describe
-sub Describe {
- my $self = shift;
- return (ref $self );
-}
-# }}}
+Opens a ticket unless it's allready open, but only unless transaction
+L<RT::Transaction/IsInbound is inbound>.
+Doesn't open a ticket if message's head has field C<RT-Control> with
+C<no-autoopen> substring.
+
+=cut
-# {{{ sub Prepare
sub Prepare {
my $self = shift;
@@ -83,22 +80,21 @@ sub Prepare {
return 1;
}
-# }}}
sub Commit {
my $self = shift;
- my $oldstatus = $self->TicketObj->Status();
- $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
- $self->TicketObj->_NewTransaction(
- Type => 'Status',
- Field => 'Status',
- OldValue => $oldstatus,
- NewValue => 'open',
- Data => 'Ticket auto-opened on incoming correspondence'
- );
-
-
- return(1);
+
+ my $oldstatus = $self->TicketObj->Status;
+ $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
+ $self->TicketObj->_NewTransaction(
+ Type => 'Status',
+ Field => 'Status',
+ OldValue => $oldstatus,
+ NewValue => 'open',
+ Data => 'Ticket auto-opened on incoming correspondence'
+ );
+
+ return 1;
}
eval "require RT::Action::AutoOpen_Vendor";
diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm
index ea56b9f5b..3734d819a 100755
--- a/rt/lib/RT/Action/Autoreply.pm
+++ b/rt/lib/RT/Action/Autoreply.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,12 +45,13 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::Autoreply;
-require RT::Action::SendEmail;
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
+
+use base qw(RT::Action::SendEmail);
=head2 Prepare
@@ -95,43 +96,37 @@ Set this message\'s return address to the apropriate queue address
sub SetReturnAddress {
my $self = shift;
- my %args = ( is_comment => 0,
- @_
- );
- my $replyto;
- if ($args{'is_comment'}) {
- $replyto = $self->TicketObj->QueueObj->CommentAddress ||
- $RT::CommentAddress;
- }
- else {
- $replyto = $self->TicketObj->QueueObj->CorrespondAddress ||
- $RT::CorrespondAddress;
- }
-
- unless ($self->TemplateObj->MIMEObj->head->get('From')) {
- if ($RT::UseFriendlyFromLine) {
- my $friendly_name = $self->TicketObj->QueueObj->Description ||
+ my $friendly_name;
+
+ if (RT->Config->Get('UseFriendlyFromLine')) {
+ $friendly_name = $self->TicketObj->QueueObj->Description ||
$self->TicketObj->QueueObj->Name;
- $friendly_name =~ s/"/\\"/g;
- $self->SetHeader( 'From',
- sprintf($RT::FriendlyFromLineFormat,
- $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
- );
}
- else {
- $self->SetHeader( 'From', $replyto );
- }
- }
-
- unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) {
- $self->SetHeader('Reply-To', "$replyto");
- }
+
+ $self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name );
}
# }}}
+# {{{{ sub SetRTSpecialHeaders
+
+=head2 SetRTSpecialHeaders
+
+Set the C<Auto-Generated> header to C<auto-replied>, in accordance
+with RFC3834.
+
+=cut
+
+sub SetRTSpecialHeaders {
+ my $self = shift;
+ $self->SUPER::SetRTSpecialHeaders(@_);
+ $self->SetHeader( 'Auto-Submitted', 'auto-replied' );
+}
+
+# }}}
+
eval "require RT::Action::Autoreply_Vendor";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Vendor.pm});
eval "require RT::Action::Autoreply_Local";
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index 40d18d357..4883ae3a8 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,13 +45,12 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::CreateTickets;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
use warnings;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
use MIME::Entity;
@@ -106,10 +105,12 @@ of perl inside the Text::Template using {} delimiters, but that
such sections absolutely can not span a ===Create-Ticket boundary.
After each ticket is created, it's stuffed into a hash called %Tickets
-so as to be available during the creation of other tickets during the same
-ScripAction. The hash is prepopulated with the ticket which triggered the
-ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
-shorthand TOP.
+so as to be available during the creation of other tickets during the
+same ScripAction, using the key 'create-identifier', where
+C<identifier> is the id you put after C<===Create-Ticket:>. The hash
+is prepopulated with the ticket which triggered the ScripAction as
+$Tickets{'TOP'}; you can also access that ticket using the shorthand
+TOP.
A simple example:
@@ -164,8 +165,9 @@ A convoluted example
ENDOFCONTENT
===Create-Ticket: two
Subject: Manager approval
+ Type: approval
Depended-On-By: TOP
- Refers-On: {$Tickets{"approval"}->Id}
+ Refers-To: {$Tickets{"create-approval"}->Id}
Queue: ___Approvals
Content-Type: text/plain
Content:
@@ -236,236 +238,6 @@ Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
be treated as the same thing.
-=begin testing
-
-ok (require RT::Action::CreateTickets);
-use_ok(RT::Scrip);
-use_ok(RT::Template);
-use_ok(RT::ScripAction);
-use_ok(RT::ScripCondition);
-use_ok(RT::Ticket);
-
-my $approvalsq = RT::Queue->new($RT::SystemUser);
-$approvalsq->Create(Name => 'Approvals');
-ok ($approvalsq->Id, "Created Approvals test queue");
-
-
-my $approvals =
-'===Create-Ticket: approval
-Queue: ___Approvals
-Type: approval
-AdminCc: {join ("\nAdminCc: ",@admins) }
-Depended-On-By: {$Tickets{"TOP"}->Id}
-Refers-To: TOP
-Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
-Due: {time + 86400}
-Content-Type: text/plain
-Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject}
-Blah
-Blah
-ENDOFCONTENT
-===Create-Ticket: two
-Subject: Manager approval.
-Depended-On-By: approval
-Queue: ___Approvals
-Content-Type: text/plain
-Content:
-Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that?
-ENDOFCONTENT
-';
-
-ok ($approvals =~ /Content/, "Read in the approvals template");
-
-my $apptemp = RT::Template->new($RT::SystemUser);
-$apptemp->Create( Content => $approvals, Name => "Approvals", Queue => "0");
-
-ok ($apptemp->Id);
-
-my $q = RT::Queue->new($RT::SystemUser);
-$q->Create(Name => 'WorkflowTest');
-ok ($q->Id, "Created workflow test queue");
-
-my $scrip = RT::Scrip->new($RT::SystemUser);
-my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Transaction',
- ScripAction => 'Create Tickets',
- Template => 'Approvals',
- Queue => $q->Id);
-ok ($sval, $smsg);
-ok ($scrip->Id, "Created the scrip");
-ok ($scrip->TemplateObj->Id, "Created the scrip template");
-ok ($scrip->ConditionObj->Id, "Created the scrip condition");
-ok ($scrip->ActionObj->Id, "Created the scrip action");
-
-my $t = RT::Ticket->new($RT::SystemUser);
-my($tid, $ttrans, $tmsg) = $t->Create(Subject => "Sample workflow test",
- Owner => "root",
- Queue => $q->Id);
-
-ok ($tid,$tmsg);
-
-my $deps = $t->DependsOn;
-is ($deps->Count, 1, "The ticket we created depends on one other ticket");
-my $dependson= $deps->First->TargetObj;
-ok ($dependson->Id, "It depends on a real ticket");
-unlike ($dependson->Subject, qr/{/, "The subject doesn't have braces in it. that means we're interpreting expressions");
-is ($t->ReferredToBy->Count,1, "It's only referred to by one other ticket");
-is ($t->ReferredToBy->First->BaseObj->Id,$t->DependsOn->First->TargetObj->Id, "The same ticket that depends on it refers to it.");
-use RT::Action::CreateTickets;
-my $action = RT::Action::CreateTickets->new( CurrentUser => $RT::SystemUser);;
-
-# comma-delimited templates
-my $commas = <<"EOF";
-id,Queue,Subject,Owner,Content
-ticket1,General,"foo, bar",root,blah
-ticket2,General,foo bar,root,blah
-ticket3,General,foo' bar,root,blah'boo
-ticket4,General,foo' bar,,blah'boo
-EOF
-
-
-# Comma delimited templates with missing data
-my $sparse_commas = <<"EOF";
-id,Queue,Subject,Owner,Requestor
-ticket14,General,,,bobby
-ticket15,General,,,tommy
-ticket16,General,,suzie,tommy
-ticket17,General,Foo "bar" baz,suzie,tommy
-ticket18,General,'Foo "bar" baz',suzie,tommy
-ticket19,General,'Foo bar' baz,suzie,tommy
-EOF
-
-
-# tab-delimited templates
-my $tabs = <<"EOF";
-id\tQueue\tSubject\tOwner\tContent
-ticket10\tGeneral\t"foo' bar"\troot\tblah'
-ticket11\tGeneral\tfoo, bar\troot\tblah
-ticket12\tGeneral\tfoo' bar\troot\tblah'boo
-ticket13\tGeneral\tfoo' bar\t\tblah'boo
-EOF
-
-my %expected;
-
-$expected{ticket1} = <<EOF;
-Queue: General
-Subject: foo, bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket2} = <<EOF;
-Queue: General
-Subject: foo bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket3} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket4} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner:
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket10} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'
-ENDOFCONTENT
-EOF
-
-$expected{ticket11} = <<EOF;
-Queue: General
-Subject: foo, bar
-Owner: root
-Content: blah
-ENDOFCONTENT
-EOF
-
-$expected{ticket12} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner: root
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-$expected{ticket13} = <<EOF;
-Queue: General
-Subject: foo' bar
-Owner:
-Content: blah'boo
-ENDOFCONTENT
-EOF
-
-
-$expected{'ticket14'} = <<EOF;
-Queue: General
-Subject:
-Owner:
-Requestor: bobby
-EOF
-$expected{'ticket15'} = <<EOF;
-Queue: General
-Subject:
-Owner:
-Requestor: tommy
-EOF
-$expected{'ticket16'} = <<EOF;
-Queue: General
-Subject:
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket17'} = <<EOF;
-Queue: General
-Subject: Foo "bar" baz
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket18'} = <<EOF;
-Queue: General
-Subject: Foo "bar" baz
-Owner: suzie
-Requestor: tommy
-EOF
-$expected{'ticket19'} = <<EOF;
-Queue: General
-Subject: 'Foo bar' baz
-Owner: suzie
-Requestor: tommy
-EOF
-
-
-
-
-$action->Parse(Content =>$commas);
-$action->Parse(Content =>$sparse_commas);
-$action->Parse(Content => $tabs);
-
-my %got;
-foreach (@{ $action->{'create_tickets'} }) {
- $got{$_} = $action->{'templates'}->{$_};
-}
-
-foreach my $id ( sort keys %expected ) {
- ok(exists($got{"create-$id"}), "template exists for $id");
- is($got{"create-$id"}, $expected{$id}, "template is correct for $id");
-}
-
-=end testing
=head1 AUTHOR
@@ -541,16 +313,16 @@ sub Prepare {
my $self = shift;
unless ( $self->TemplateObj ) {
- $RT::Logger->warning("No template object handed to $self\n");
+ $RT::Logger->warning("No template object handed to $self");
}
unless ( $self->TransactionObj ) {
- $RT::Logger->warning("No transaction object handed to $self\n");
+ $RT::Logger->warning("No transaction object handed to $self");
}
unless ( $self->TicketObj ) {
- $RT::Logger->warning("No ticket object handed to $self\n");
+ $RT::Logger->warning("No ticket object handed to $self");
}
@@ -575,7 +347,6 @@ sub CreateByTemplate {
my @results;
# XXX: cargo cult programming that works. i'll be back.
- use bytes;
local %T::Tickets = %T::Tickets;
local $T::TOP = $T::TOP;
@@ -637,7 +408,6 @@ sub UpdateByTemplate {
my $top = shift;
# XXX: cargo cult programming that works. i'll be back.
- use bytes;
my @results;
local %T::Tickets = %T::Tickets;
@@ -894,7 +664,7 @@ sub ParseLines {
}
);
- $RT::Logger->debug("Workflow: yielding\n$content");
+ $RT::Logger->debug("Workflow: yielding $content");
if ($err) {
$RT::Logger->error( "Ticket creation failed: " . $err );
@@ -990,6 +760,7 @@ sub ParseLines {
TimeLeft => $args{'timeleft'},
InitialPriority => $args{'initialpriority'} || 0,
FinalPriority => $args{'finalpriority'} || 0,
+ SquelchMailTo => $args{'squelchmailto'},
Type => $args{'type'},
);
@@ -1223,7 +994,7 @@ sub GetUpdateTemplate {
my $mode = $LINKTYPEMAP{$type}->{Mode};
my $method = $LINKTYPEMAP{$type}->{Type};
- my $links;
+ my $links = '';
while ( my $link = $t->$method->Next ) {
$links .= ", " if $links;
diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm
index 46635df05..bf9de92c2 100644
--- a/rt/lib/RT/Action/EscalatePriority.pm
+++ b/rt/lib/RT/Action/EscalatePriority.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
=head1 NAME
RT::Action::EscalatePriority
@@ -71,11 +72,9 @@ as the ticket heads toward its due date.
package RT::Action::EscalatePriority;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
#Do what we need to do and send it out.
@@ -155,7 +154,7 @@ sub Commit {
my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'});
unless ($val) {
- $RT::Logger->debug($self . " $msg\n");
+ $RT::Logger->debug($self . " $msg");
}
}
diff --git a/rt/lib/RT/Action/ExtractSubjectTag.pm b/rt/lib/RT/Action/ExtractSubjectTag.pm
new file mode 100644
index 000000000..4a173ce76
--- /dev/null
+++ b/rt/lib/RT/Action/ExtractSubjectTag.pm
@@ -0,0 +1,103 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Action::ExtractSubjectTag;
+use base 'RT::Action';
+use strict;
+
+sub Describe {
+ my $self = shift;
+ return ( ref $self );
+}
+
+sub Prepare {
+ return (1);
+}
+
+sub Commit {
+ my $self = shift;
+ my $Transaction = $self->TransactionObj;
+ my $FirstAttachment = $Transaction->Attachments->First;
+ return 1 unless ($FirstAttachment);
+
+ my $Ticket = $self->TicketObj;
+
+ my $TicketSubject = $self->TicketObj->Subject;
+ my $origTicketSubject = $TicketSubject;
+ my $TransactionSubject = $FirstAttachment->Subject;
+
+ my $match = RT->Config->Get('ExtractSubjectTagMatch');
+ my $nomatch = RT->Config->Get('ExtractSubjectTagNoMatch');
+ TAGLIST: while ( $TransactionSubject =~ /($match)/g ) {
+ my $tag = $1;
+ next if $tag =~ /$nomatch/;
+ foreach my $subject_tag ( RT->System->SubjectTag ) {
+ if ($tag =~ /\[\Q$subject_tag\E\s+\#(\d+)\s*\]/) {
+ next TAGLIST;
+ }
+ }
+ $TicketSubject .= " $tag" unless ( $TicketSubject =~ /\Q$tag\E/ );
+ }
+
+ $self->TicketObj->SetSubject($TicketSubject)
+ if ( $TicketSubject ne $origTicketSubject );
+
+ return (1);
+}
+
+eval "require RT::Action::ExtractSubjectTag_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::ExtractSubjectTag_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm
index 3232d4898..5e8ef32ce 100755
--- a/rt/lib/RT/Action/Generic.pm
+++ b/rt/lib/RT/Action/Generic.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,9 +45,10 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
=head1 NAME
- RT::Action::Generic - a generic baseclass for RT Actions
+ RT::Action::Generic - deprecated, see RT::Action
=head1 SYNOPSIS
@@ -55,177 +56,25 @@
=head1 DESCRIPTION
-=head1 METHODS
-
-=begin testing
+This module is provided only for backwards compatibility.
-ok (require RT::Action::Generic);
+=head1 METHODS
-=end testing
=cut
-package RT::Action::Generic;
-
use strict;
-use Scalar::Util;
-
-use base qw/RT::Base/;
-
-# {{{ sub new
-sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
- bless ($self, $class);
- $self->_Init(@_);
- return $self;
-}
-# }}}
-
-# {{{ sub _Init
-sub _Init {
- my $self = shift;
- my %args = ( Argument => undef,
- CurrentUser => undef,
- ScripActionObj => undef,
- ScripObj => undef,
- TemplateObj => undef,
- TicketObj => undef,
- TransactionObj => undef,
- Type => undef,
-
- @_ );
-
- $self->{'Argument'} = $args{'Argument'};
- $self->CurrentUser( $args{'CurrentUser'});
- $self->{'ScripActionObj'} = $args{'ScripActionObj'};
- $self->{'ScripObj'} = $args{'ScripObj'};
- $self->{'TemplateObj'} = $args{'TemplateObj'};
- $self->{'TicketObj'} = $args{'TicketObj'};
- $self->{'TransactionObj'} = $args{'TransactionObj'};
- $self->{'Type'} = $args{'Type'};
-
- Scalar::Util::weaken($self->{'ScripActionObj'});
- Scalar::Util::weaken($self->{'ScripObj'});
- Scalar::Util::weaken($self->{'TemplateObj'});
- Scalar::Util::weaken($self->{'TicketObj'});
- Scalar::Util::weaken($self->{'TransactionObj'});
-
-}
-# }}}
-
-# Access Scripwide data
-
-# {{{ sub Argument
-sub Argument {
- my $self = shift;
- return($self->{'Argument'});
-}
-# }}}
-
-# {{{ sub TicketObj
-sub TicketObj {
- my $self = shift;
- return($self->{'TicketObj'});
-}
-# }}}
-
-# {{{ sub TransactionObj
-sub TransactionObj {
- my $self = shift;
- return($self->{'TransactionObj'});
-}
-# }}}
-
-# {{{ sub TemplateObj
-sub TemplateObj {
- my $self = shift;
- return($self->{'TemplateObj'});
-}
-# }}}
-
-# {{{ sub ScripObj
-sub ScripObj {
- my $self = shift;
- return($self->{'ScripObj'});
-}
-# }}}
-
-# {{{ sub ScripActionObj
-sub ScripActionObj {
- my $self = shift;
- return($self->{'ScripActionObj'});
-}
-# }}}
-
-# {{{ sub Type
-sub Type {
- my $self = shift;
- return($self->{'Type'});
-}
-# }}}
-
-
-# Scrip methods
-
-#Do what we need to do and send it out.
-
-# {{{ sub Commit
-sub Commit {
- my $self = shift;
- return(0, $self->loc("Commit Stubbed"));
-}
-# }}}
-
-
-#What does this type of Action does
-
-# {{{ sub Describe
-sub Describe {
- my $self = shift;
- return $self->loc("No description for [_1]", ref $self);
-}
-# }}}
-
-
-#Parse the templates, get things ready to go.
-
-# {{{ sub Prepare
-sub Prepare {
- my $self = shift;
- return (0, $self->loc("Prepare Stubbed"));
-}
-# }}}
-
-
-#If this rule applies to this transaction, return true.
-
-# {{{ sub IsApplicable
-sub IsApplicable {
- my $self = shift;
- return(undef);
-}
-# }}}
-
-# {{{ sub DESTROY
-sub DESTROY {
- my $self = shift;
-
- # We need to clean up all the references that might maybe get
- # oddly circular
- $self->{'ScripActionObj'} = undef;
- $self->{'ScripObj'} = undef;
- $self->{'TemplateObj'} =undef
- $self->{'TicketObj'} = undef;
- $self->{'TransactionObj'} = undef;
-}
-
-# }}}
+use warnings;
+package RT::Action::Generic;
+use base 'RT::Action';
eval "require RT::Action::Generic_Vendor";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Vendor.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Vendor file at " . $INC{"RT/Action/Generic_Vendor.pm"} if !$@;
+
eval "require RT::Action::Generic_Local";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Local.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Local file at " . $INC{"RT/Action/Generic_Local.pm"} if !$@;
1;
+
diff --git a/rt/lib/RT/Action/LinearEscalate.pm b/rt/lib/RT/Action/LinearEscalate.pm
new file mode 100755
index 000000000..9130f40ca
--- /dev/null
+++ b/rt/lib/RT/Action/LinearEscalate.pm
@@ -0,0 +1,279 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::LinearEscalate - will move a ticket's priority toward its final priority.
+
+=head1 This vs. RT::Action::EscalatePriority
+
+This action doesn't change priority if due date is not set.
+
+This action honor the Starts date.
+
+This action can apply changes silently.
+
+This action can replace EscalatePriority completly. If you want to tickets
+that have been created without Due date then you can add scrip that sets
+default due date. For example a week then priorities of your tickets will
+escalate linearly during the week from intial value towards final.
+
+=head1 This vs. LinearEscalate from the CPAN
+
+This action is an integration of the module from the CPAN into RT's core
+that's happened in RT 3.8. If you're upgrading from 3.6 and have been using
+module from the CPAN with old version of RT then you should uninstall it
+and use this one.
+
+However, this action doesn't support control over config. Read </CONFIGURATION>
+to find out ways to deal with it.
+
+=head1 DESCRIPTION
+
+LinearEscalate is a ScripAction that will move a ticket's priority
+from its initial priority to its final priority linearly as
+the ticket approaches its due date.
+
+It's intended to be called by an RT escalation tool. One such tool is called
+rt-crontool and is located in $RTHOME/bin (see C<rt-crontool -h> for more details).
+
+=head1 USAGE
+
+Once the ScripAction is installed, the following script in "cron"
+will get tickets to where they need to be:
+
+ rt-crontool --search RT::Search::FromSQL --search-arg \
+ "(Status='new' OR Status='open' OR Status = 'stalled')" \
+ --action RT::Action::LinearEscalate
+
+The Starts date is associated with intial ticket's priority or
+the Created field if the former is not set. End of interval is
+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
+action should handle correctly situations when initial priority
+is greater than final.
+
+LinearEscalate's behavior can be controlled by two options:
+
+=over 4
+
+=item RecordTransaction - defaults to false and if option is true then
+causes the tool to create a transaction on the ticket when it is escalated.
+
+=item UpdateLastUpdated - which defaults to true and updates the LastUpdated
+field when the ticket is escalated, otherwise don't touch anything.
+
+=back
+
+You cannot set "UpdateLastUpdated" to false unless "RecordTransaction"
+is also false. Well, you can, but we'll just ignore you.
+
+You can set this options using either in F<RT_SiteConfig.pm>, as action
+argument in call to the rt-crontool or in DB if you want to use the action
+in scrips.
+
+From a shell you can use the following command:
+
+ rt-crontool --search RT::Search::FromSQL --search-arg \
+ "(Status='new' OR Status='open' OR Status = 'stalled')" \
+ --action RT::Action::LinearEscalate \
+ --action-arg "RecordTransaction: 1"
+
+This ScripAction uses RT's internal _Set or __Set calls to set ticket
+priority without running scrips or recording a transaction on each
+update, if it's been said to.
+
+=cut
+
+package RT::Action::LinearEscalate;
+
+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
+
+sub Describe {
+ my $self = shift;
+ my $class = ref($self) || $self;
+ return "$class will move a ticket's priority toward its final priority.";
+}
+
+sub Prepare {
+ my $self = shift;
+
+ my $ticket = $self->TicketObj;
+
+ my $due = $ticket->DueObj->Unix;
+ unless ( $due > 0 ) {
+ $RT::Logger->debug('Due is not set. Not escalating.');
+ return 1;
+ }
+
+ my $priority_range = ($ticket->FinalPriority ||0) - ($ticket->InitialPriority ||0);
+ unless ( $priority_range ) {
+ $RT::Logger->debug('Final and Initial priorities are equal. Not escalating.');
+ return 1;
+ }
+
+ if ( $ticket->Priority >= $ticket->FinalPriority && $priority_range > 0 ) {
+ $RT::Logger->debug('Current priority is greater than final. Not escalating.');
+ return 1;
+ }
+ elsif ( $ticket->Priority <= $ticket->FinalPriority && $priority_range < 0 ) {
+ $RT::Logger->debug('Current priority is lower than final. Not escalating.');
+ return 1;
+ }
+
+ # TODO: compute the number of business days until the ticket is due
+
+ # 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;
+
+ # do nothing if we didn't reach starts or created date
+ if ( $starts > $now ) {
+ $RT::Logger->debug('Starts(Created) is in future. Not escalating.');
+ return 1;
+ }
+
+ $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;
+ $self->{'new_priority'} = $new_priority;
+
+ return 1;
+}
+
+sub Commit {
+ my $self = shift;
+
+ my $new_value = $self->{'new_priority'};
+ return 1 unless defined $new_value;
+
+ my $ticket = $self->TicketObj;
+ # if the priority hasn't changed do nothing
+ return 1 if $ticket->Priority == $new_value;
+
+ # override defaults from argument
+ my ($record, $update) = (0, 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");
+ }
+ $update = 1 if $record;
+ }
+
+ $RT::Logger->debug(
+ 'Linearly 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;
+}
+
+eval "require RT::Action::LinearEscalate_Vendor";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Vendor.pm} );
+eval "require RT::Action::LinearEscalate_Local";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Local.pm} );
+
+1;
+
+=head1 AUTHORS
+
+Kevin Riggle E<lt>kevinr@bestpractical.comE<gt>
+
+Ruslan Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+=cut
diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm
index 82cad1e58..30238fd61 100755
--- a/rt/lib/RT/Action/Notify.pm
+++ b/rt/lib/RT/Action/Notify.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,16 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
#
package RT::Action::Notify;
-require RT::Action::SendEmail;
-use Mail::Address;
+
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
+use base qw(RT::Action::SendEmail);
+
+use Email::Address;
=head2 Prepare
@@ -67,8 +69,6 @@ sub Prepare {
$self->SUPER::Prepare();
}
-# {{{ sub SetRecipients
-
=head2 SetRecipients
Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All.
@@ -79,73 +79,63 @@ Explicitly B<does not> notify the creator of the transaction by default
sub SetRecipients {
my $self = shift;
- my $arg = $self->Argument;
+ my $ticket = $self->TicketObj;
+ my $arg = $self->Argument;
$arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/;
my ( @To, @PseudoTo, @Cc, @Bcc );
if ( $arg =~ /\bOtherRecipients\b/ ) {
- if ( $self->TransactionObj->Attachments->First ) {
- my @cc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
- foreach my $addr (@cc_addresses) {
- push @Cc, $addr->address;
- }
- my @bcc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
-
- foreach my $addr (@bcc_addresses) {
- push @Bcc, $addr->address;
- }
-
+ if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+ push @Cc, map { $_->address } Email::Address->parse(
+ $attachment->GetHeader('RT-Send-Cc')
+ );
+ push @Bcc, map { $_->address } Email::Address->parse(
+ $attachment->GetHeader('RT-Send-Bcc')
+ );
}
}
if ( $arg =~ /\bRequestor\b/ ) {
- push ( @To, $self->TicketObj->Requestors->MemberEmailAddresses );
+ push @To, $ticket->Requestors->MemberEmailAddresses;
}
-
-
if ( $arg =~ /\bCc\b/ ) {
#If we have a To, make the Ccs, Ccs, otherwise, promote them to To
if (@To) {
- push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
- push ( @Cc, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses );
+ push ( @Cc, $ticket->Cc->MemberEmailAddresses );
+ push ( @Cc, $ticket->QueueObj->Cc->MemberEmailAddresses );
}
else {
- push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
- push ( @To, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses );
+ push ( @Cc, $ticket->Cc->MemberEmailAddresses );
+ push ( @To, $ticket->QueueObj->Cc->MemberEmailAddresses );
}
}
- if ( ( $arg =~ /\bOwner\b/ )
- && ( $self->TicketObj->OwnerObj->id != $RT::Nobody->id ) )
- {
-
- # If we're not sending to Ccs or requestors,
+ if ( $arg =~ /\bOwner\b/ && $ticket->OwnerObj->id != $RT::Nobody->id ) {
+ # If we're not sending to Ccs or requestors,
# then the Owner can be the To.
if (@To) {
- push ( @Bcc, $self->TicketObj->OwnerObj->EmailAddress );
+ push ( @Bcc, $ticket->OwnerObj->EmailAddress );
}
else {
- push ( @To, $self->TicketObj->OwnerObj->EmailAddress );
+ push ( @To, $ticket->OwnerObj->EmailAddress );
}
}
if ( $arg =~ /\bAdminCc\b/ ) {
- push ( @Bcc, $self->TicketObj->AdminCc->MemberEmailAddresses );
- push ( @Bcc, $self->TicketObj->QueueObj->AdminCc->MemberEmailAddresses );
+ push ( @Bcc, $ticket->AdminCc->MemberEmailAddresses );
+ push ( @Bcc, $ticket->QueueObj->AdminCc->MemberEmailAddresses );
}
- if ($RT::UseFriendlyToLine) {
+ if ( RT->Config->Get('UseFriendlyToLine') ) {
unless (@To) {
- push (
- @PseudoTo,
- sprintf($RT::FriendlyToLineFormat, $arg, $self->TicketObj->id),
- );
+ push @PseudoTo,
+ sprintf RT->Config->Get('FriendlyToLineFormat'), $arg, $ticket->id;
}
}
@@ -154,7 +144,7 @@ sub SetRecipients {
#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
- if ($RT::NotifyActor) {
+ if (RT->Config->Get('NotifyActor')) {
@{ $self->{'To'} } = @To;
@{ $self->{'Cc'} } = @Cc;
@{ $self->{'Bcc'} } = @Bcc;
@@ -169,8 +159,6 @@ sub SetRecipients {
}
-# }}}
-
eval "require RT::Action::Notify_Vendor";
die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Vendor.pm});
eval "require RT::Action::Notify_Local";
diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm
index 215f453d3..b2eb5acd8 100755
--- a/rt/lib/RT/Action/NotifyAsComment.pm
+++ b/rt/lib/RT/Action/NotifyAsComment.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,13 +45,13 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::NotifyAsComment;
require RT::Action::Notify;
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Notify);
-
+use warnings;
+use base qw(RT::Action::Notify);
=head2 SetReturnAddress
diff --git a/rt/lib/RT/Action/NotifyGroup.pm b/rt/lib/RT/Action/NotifyGroup.pm
new file mode 100644
index 000000000..6b830cb86
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroup.pm
@@ -0,0 +1,209 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroup - RT Action that sends notifications to groups and/or users
+
+=head1 DESCRIPTION
+
+RT action module that allow you to notify particular groups and/or users.
+Distribution is shipped with C<rt-email-group-admin> script that
+is command line tool for managing NotifyGroup scrip actions. For more
+more info see its documentation.
+
+=cut
+
+package RT::Action::NotifyGroup;
+
+use strict;
+use warnings;
+use base qw(RT::Action::Notify);
+
+require RT::User;
+require RT::Group;
+
+=head1 METHODS
+
+=head2 SetRecipients
+
+Sets the recipients of this message to Groups and/or Users.
+
+=cut
+
+sub SetRecipients {
+ my $self = shift;
+
+ my $arg = $self->Argument;
+ foreach( $self->__SplitArg( $arg ) ) {
+ $self->_HandleArgument( $_ );
+ }
+
+ my $creator = $self->TransactionObj->CreatorObj->EmailAddress();
+ unless( $RT::NotifyActor ) {
+ @{ $self->{'To'} } = grep ( !/^\Q$creator\E$/, @{ $self->{'To'} } );
+ }
+
+ $self->{'seen_ueas'} = {};
+
+ return 1;
+}
+
+sub _HandleArgument {
+ my $self = shift;
+ my $instance = shift;
+
+ if ( $instance !~ /\D/ ) {
+ my $obj = RT::Principal->new( $self->CurrentUser );
+ $obj->Load( $instance );
+ return $self->_HandlePrincipal( $obj );
+ }
+
+ my $group = RT::Group->new( $self->CurrentUser );
+ $group->LoadUserDefinedGroup( $instance );
+ # to check disabled and so on
+ return $self->_HandlePrincipal( $group->PrincipalObj )
+ if $group->id;
+
+ require Email::Address;
+
+ my $user = RT::User->new( $self->CurrentUser );
+ if ( $instance =~ /^$Email::Address::addr_spec$/ ) {
+ $user->LoadByEmail( $instance );
+ return $self->__PushUserAddress( $instance )
+ unless $user->id;
+ } else {
+ $user->Load( $instance );
+ }
+ return $self->_HandlePrincipal( $user->PrincipalObj )
+ if $user->id;
+
+ $RT::Logger->error(
+ "'$instance' is not principal id, group name, user name,"
+ ." user email address or any email address"
+ );
+
+ return;
+}
+
+sub _HandlePrincipal {
+ my $self = shift;
+ my $obj = shift;
+ unless( $obj->id ) {
+ $RT::Logger->error( "Couldn't load principal #$obj" );
+ return;
+ }
+ if( $obj->Disabled ) {
+ $RT::Logger->info( "Principal #$obj is disabled => skip" );
+ return;
+ }
+ if( !$obj->PrincipalType ) {
+ $RT::Logger->crit( "Principal #$obj has empty type" );
+ } elsif( lc $obj->PrincipalType eq 'user' ) {
+ $self->__HandleUserArgument( $obj->Object );
+ } elsif( lc $obj->PrincipalType eq 'group' ) {
+ $self->__HandleGroupArgument( $obj->Object );
+ } else {
+ $RT::Logger->info( "Principal #$obj has unsupported type" );
+ }
+ return;
+}
+
+sub __HandleUserArgument {
+ my $self = shift;
+ my $obj = shift;
+
+ my $uea = $obj->EmailAddress;
+ unless( $uea ) {
+ $RT::Logger->warning( "User #". $obj->id ." has no email address" );
+ return;
+ }
+ $self->__PushUserAddress( $uea );
+}
+
+sub __HandleGroupArgument {
+ my $self = shift;
+ my $obj = shift;
+
+ my $members = $obj->UserMembersObj;
+ while( my $m = $members->Next ) {
+ $self->__HandleUserArgument( $m );
+ }
+}
+
+sub __SplitArg {
+ return grep length, map {s/^\s+//; s/\s+$//; $_} split /,/, $_[1];
+}
+
+sub __PushUserAddress {
+ my $self = shift;
+ my $uea = shift;
+ push @{ $self->{'To'} }, $uea unless $self->{'seen_ueas'}{ $uea }++;
+ return;
+}
+
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+L<RT::Action::NotifyGroupAsComment>, F<rt-email-group-admin>
+
+=cut
+
+eval "require RT::Action::NotifyGroup_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::NotifyGroup_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/NotifyGroupAsComment.pm b/rt/lib/RT/Action/NotifyGroupAsComment.pm
new file mode 100644
index 000000000..bee0a01a1
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroupAsComment.pm
@@ -0,0 +1,91 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroupAsComment - RT Action that sends notifications to groups and/or users as comment
+
+=head1 DESCRIPTION
+
+This is subclass of L<RT::Action::NotifyGroup> that send comments instead of replies.
+See C<rt-email-group-admin> and L<RT::Action::NotifyGroup> docs for more info.
+
+=cut
+
+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 );
+}
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov E<lt>ruz@bestpractical.comE<gt>
+
+=cut
+
+eval "require RT::Action::NotifyGroupAsComment_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Vendor.pm}) {
+ die $@;
+};
+
+eval "require RT::Action::NotifyGroupAsComment_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Local.pm}) {
+ die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm
index c0256d6d7..bac17e96e 100644
--- a/rt/lib/RT/Action/RecordComment.pm
+++ b/rt/lib/RT/Action/RecordComment.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::RecordComment;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head1 NAME
diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm
index 10a890e4e..044893b97 100644
--- a/rt/lib/RT/Action/RecordCorrespondence.pm
+++ b/rt/lib/RT/Action/RecordCorrespondence.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::RecordCorrespondence;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head1 NAME
diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm
index fab049b0a..ff826ccc1 100644
--- a/rt/lib/RT/Action/ResolveMembers.pm
+++ b/rt/lib/RT/Action/ResolveMembers.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,14 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
# This Action will resolve all members of a resolved group ticket
package RT::Action::ResolveMembers;
-require RT::Action::Generic;
+use base 'RT::Action';
require RT::Links;
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
#Do what we need to do and send it out.
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
index ed5ec4fd6..a09bd3e56 100755
--- a/rt/lib/RT/Action/SendEmail.pm
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,20 +45,21 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
# Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
package RT::Action::SendEmail;
-require RT::Action::Generic;
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
+use warnings;
-use MIME::Words qw(encode_mimeword);
+use base qw(RT::Action);
use RT::EmailParser;
-use Mail::Address;
-use Date::Format qw(strftime);
+use RT::Interface::Email;
+use Email::Address;
+our @EMAIL_RECIPIENT_HEADERS = qw(To Cc Bcc);
+
=head1 NAME
@@ -68,53 +69,85 @@ RT::Action::AutoReply is a good example subclass.
=head1 SYNOPSIS
- require RT::Action::SendEmail;
- @ISA = qw(RT::Action::SendEmail);
-
+ use base 'RT::Action::SendEmail';
=head1 DESCRIPTION
Basically, you create another module RT::Action::YourAction which ISA
RT::Action::SendEmail.
-=begin testing
+=head1 METHODS
-ok (require RT::Action::SendEmail);
+=head2 CleanSlate
-=end testing
+Cleans class-wide options, like L</SquelchMailTo> or L</AttachTickets>.
+=cut
-=head1 AUTHOR
-
-Jesse Vincent <jesse@bestpractical.com> and Tobias Brox <tobix@cpan.org>
+sub CleanSlate {
+ my $self = shift;
+ $self->SquelchMailTo(undef);
+ $self->AttachTickets(undef);
+}
-=head1 SEE ALSO
+=head2 Commit
-perl(1).
+Sends the prepared message and writes outgoing record into DB if the feature is
+activated in the config.
=cut
-# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
+sub Commit {
+ my $self = shift;
+ $self->DeferDigestRecipients() if RT->Config->Get('RecordOutgoingEmail');
+ my $message = $self->TemplateObj->MIMEObj;
-# {{{ sub Commit
+ my $orig_message;
+ if ( RT->Config->Get('RecordOutgoingEmail')
+ && RT->Config->Get('GnuPG')->{'Enable'} )
+ {
-sub Commit {
- # DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
- # ability to pass @_ to a 'post' routine.
- my $self = $_[0];
+ # it's hacky, but we should know if we're going to crypt things
+ my $attachment = $self->TransactionObj->Attachments->First;
+
+ my %crypt;
+ foreach my $argument (qw(Sign Encrypt)) {
+ if ( $attachment
+ && defined $attachment->GetHeader("X-RT-$argument") )
+ {
+ $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
+ } else {
+ $crypt{$argument} = $self->TicketObj->QueueObj->$argument();
+ }
+ }
+ if ( $crypt{'Sign'} || $crypt{'Encrypt'} ) {
+ $orig_message = $message->dup;
+ }
+ }
- my ($ret) = $self->SendMessage( $self->TemplateObj->MIMEObj );
- if ( $ret > 0 ) {
- $self->RecordOutgoingMailTransaction( $self->TemplateObj->MIMEObj )
- if ($RT::RecordOutgoingEmail);
+ my ($ret) = $self->SendMessage($message);
+ if ( $ret > 0 && RT->Config->Get('RecordOutgoingEmail') ) {
+ if ($orig_message) {
+ $message->attach(
+ Type => 'application/x-rt-original-message',
+ Disposition => 'inline',
+ Data => $orig_message->as_string,
+ );
+ }
+ $self->RecordOutgoingMailTransaction($message);
+ $self->RecordDeferredRecipients();
}
- return (abs $ret);
+
+
+ return ( abs $ret );
}
-# }}}
+=head2 Prepare
+
+Builds an outgoing email we're going to send using scrip's template.
-# {{{ sub Prepare
+=cut
sub Prepare {
my $self = shift;
@@ -136,116 +169,116 @@ sub Prepare {
$self->RemoveInappropriateRecipients();
my %seen;
- foreach my $type qw(To Cc Bcc) {
- @{ $self->{ $type } } =
- grep defined && length && !$seen{ lc $_ }++,
- @{ $self->{ $type } };
+ foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+ @{ $self->{$type} }
+ = grep defined && length && !$seen{ lc $_ }++,
+ @{ $self->{$type} };
}
# Go add all the Tos, Ccs and Bccs that we need to to the message to
# make it happy, but only if we actually have values in those arrays.
- # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
+# TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
- $self->SetHeader( 'To', join ( ', ', @{ $self->{'To'} } ) )
- if ( ! $MIMEObj->head->get('To') && $self->{'To'} && @{ $self->{'To'} } );
- $self->SetHeader( 'Cc', join ( ', ', @{ $self->{'Cc'} } ) )
- if ( !$MIMEObj->head->get('Cc') && $self->{'Cc'} && @{ $self->{'Cc'} } );
- $self->SetHeader( 'Bcc', join ( ', ', @{ $self->{'Bcc'} } ) )
- if ( !$MIMEObj->head->get('Bcc') && $self->{'Bcc'} && @{ $self->{'Bcc'} } );
+ for my $header (@EMAIL_RECIPIENT_HEADERS) {
- # PseudoTo (fake to headers) shouldn't get matched for message recipients.
+ $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) )
+ if ( !$MIMEObj->head->get($header)
+ && $self->{$header}
+ && @{ $self->{$header} } );
+}
+ # PseudoTo (fake to headers) shouldn't get matched for message recipients.
# If we don't have any 'To' header (but do have other recipients), drop in
# the pseudo-to header.
- $self->SetHeader( 'To', join ( ', ', @{ $self->{'PseudoTo'} } ) )
- if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
- and ( !$MIMEObj->head->get('To') ) ) and ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc'));
+ $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) )
+ if $self->{'PseudoTo'}
+ && @{ $self->{'PseudoTo'} }
+ && !$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');
+ $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
# For security reasons, we only send out textual mails.
- my @parts = $MIMEObj;
- while (my $part = shift @parts) {
- if ($part->is_multipart) {
- push @parts, $part->parts;
- }
- else {
- if ( RT::I18N::IsTextualContentType( $part->mime_type ) ) {
- $part->head->mime_attr( "Content-Type" => $part->mime_type )
- } else {
- $part->head->mime_attr( "Content-Type" => 'text/plain' );
- }
- $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
- }
+ foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
+ my $type = $part->mime_type || 'text/plain';
+ $type = 'text/plain' unless RT::I18N::IsTextualContentType($type);
+ $part->head->mime_attr( "Content-Type" => $type );
+ $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
}
-
- RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
+ RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
+ RT->Config->Get('EmailOutputEncoding'),
+ 'mime_words_ok', );
# Build up a MIME::Entity that looks like the original message.
- $self->AddAttachments() if ( $MIMEObj->head->get('RT-Attach-Message') );
+ $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
+ && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) );
+
+ $self->AddTickets;
+
+ my $attachment = $self->TransactionObj->Attachments->First;
+ if ($attachment
+ && !(
+ $attachment->GetHeader('X-RT-Encrypt')
+ || $self->TicketObj->QueueObj->Encrypt
+ )
+ )
+ {
+ $attachment->SetHeader( 'X-RT-Encrypt' => 1 )
+ if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq
+ 'Success';
+ }
return $result;
-
}
-# }}}
-
-# }}}
-
-
-
=head2 To
-Returns an array of Mail::Address objects containing all the To: recipients for this notification
+Returns an array of L<Email::Address> objects containing all the To: recipients for this notification
=cut
sub To {
my $self = shift;
- return ($self->_AddressesFromHeader('To'));
+ return ( $self->AddressesFromHeader('To') );
}
=head2 Cc
-Returns an array of Mail::Address objects containing all the Cc: recipients for this notification
+Returns an array of L<Email::Address> objects containing all the Cc: recipients for this notification
=cut
-sub Cc {
+sub Cc {
my $self = shift;
- return ($self->_AddressesFromHeader('Cc'));
+ return ( $self->AddressesFromHeader('Cc') );
}
=head2 Bcc
-Returns an array of Mail::Address objects containing all the Bcc: recipients for this notification
+Returns an array of L<Email::Address> objects containing all the Bcc: recipients for this notification
=cut
-
sub Bcc {
my $self = shift;
- return ($self->_AddressesFromHeader('Bcc'));
+ return ( $self->AddressesFromHeader('Bcc') );
}
-sub _AddressesFromHeader {
- my $self = shift;
- my $field = shift;
- my $header = $self->TemplateObj->MIMEObj->head->get($field);
- my @addresses = Mail::Address->parse($header);
+sub AddressesFromHeader {
+ my $self = shift;
+ my $field = shift;
+ my $header = $self->TemplateObj->MIMEObj->head->get($field);
+ my @addresses = Email::Address->parse($header);
return (@addresses);
}
-
-# {{{ SendMessage
-
=head2 SendMessage MIMEObj
sends the message using RT's preferred API.
@@ -254,6 +287,7 @@ TODO: Break this out to a separate module
=cut
sub SendMessage {
+
# DO NOT SHIFT @_ in this subroutine. It breaks Hook::LexWrap's
# ability to pass @_ to a 'post' routine.
my ( $self, $MIMEObj ) = @_;
@@ -262,36 +296,36 @@ sub SendMessage {
chomp $msgid;
$self->ScripActionObj->{_Message_ID}++;
-
- $RT::Logger->info( $msgid . " #"
- . $self->TicketObj->id . "/"
- . $self->TransactionObj->id
- . " - Scrip "
- . $self->ScripObj->id . " "
- . $self->ScripObj->Description );
-
- #If we don't have any recipients to send to, don't send a message;
- unless ( $MIMEObj->head->get('To')
- || $MIMEObj->head->get('Cc')
- || $MIMEObj->head->get('Bcc') )
- {
- $RT::Logger->info( $msgid . " No recipients found. Not sending.\n" );
- return (-1);
- }
- unless ($MIMEObj->head->get('Date')) {
- # We coerce localtime into an array since strftime has a flawed prototype that only accepts
- # a list
- $MIMEObj->head->replace(Date => strftime('%a, %d %b %Y %H:%M:%S %z', @{[localtime()]}));
- }
+ $RT::Logger->info( $msgid . " #"
+ . $self->TicketObj->id . "/"
+ . $self->TransactionObj->id
+ . " - Scrip "
+ . ($self->ScripObj->id || '#rule'). " "
+ . ( $self->ScripObj->Description || '' ) );
+
+ my $status = RT::Interface::Email::SendEmail(
+ Entity => $MIMEObj,
+ Ticket => $self->TicketObj,
+ Transaction => $self->TransactionObj,
+ );
- return (0) unless ($self->OutputMIMEObject($MIMEObj));
+
+ return $status unless ($status > 0 || exists $self->{'Deferred'});
my $success = $msgid . " sent ";
- foreach( qw(To Cc Bcc) ) {
+ foreach (@EMAIL_RECIPIENT_HEADERS) {
my $recipients = $MIMEObj->head->get($_);
- $success .= " $_: ". $recipients if $recipients;
+ $success .= " $_: " . $recipients if $recipients;
}
+
+ if( exists $self->{'Deferred'} ) {
+ for (qw(daily weekly susp)) {
+ $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } )
+ if exists $self->{'Deferred'}{ $_ };
+ }
+ }
+
$success =~ s/\n//g;
$RT::Logger->info($success);
@@ -299,140 +333,166 @@ sub SendMessage {
return (1);
}
+=head2 AddAttachments
-=head2 OutputMIMEObject MIME::Entity
-
-Sends C<MIME::Entity> as an email message according to RT's mailer configuration.
-
-=cut
-
+Takes any attachments to this transaction and attaches them to the message
+we're building.
+=cut
-sub OutputMIMEObject {
+sub AddAttachments {
my $self = shift;
- my $MIMEObj = shift;
-
- my $msgid = $MIMEObj->head->get('Message-ID');
- chomp $msgid;
-
- my $SendmailArguments = $RT::SendmailArguments;
- if (defined $RT::VERPPrefix && defined $RT::VERPDomain) {
- my $EnvelopeFrom = $self->TransactionObj->CreatorObj->EmailAddress;
- $EnvelopeFrom =~ s/@/=/g;
- $EnvelopeFrom =~ s/\s//g;
- $SendmailArguments .= " -f ${RT::VERPPrefix}${EnvelopeFrom}\@${RT::VERPDomain}";
- }
-
- if ( $RT::MailCommand eq 'sendmailpipe' ) {
- eval {
- # don't ignore CHLD signal to get proper exit code
- local $SIG{'CHLD'} = 'DEFAULT';
+ my $MIMEObj = $self->TemplateObj->MIMEObj;
- my $mail;
- unless( open $mail, "|$RT::SendmailPath $SendmailArguments" ) {
- die "Couldn't run $RT::SendmailPath: $!";
- }
+ $MIMEObj->head->delete('RT-Attach-Message');
- # if something wrong with $mail->print we will get PIPE signal, handle it
- local $SIG{'PIPE'} = sub { die "$RT::SendmailPath closed pipe" };
- $MIMEObj->print($mail);
+ my $attachments = RT::Attachments->new($RT::SystemUser);
+ $attachments->Limit(
+ FIELD => 'TransactionId',
+ VALUE => $self->TransactionObj->Id
+ );
- unless ( close $mail ) {
- die "Close failed: $!" if $!; # system error
- # sendmail exit statuses mostly errors with data not software
- # TODO: status parsing: core dump, exit on signal or EX_*
- $RT::Logger->warning( "$RT::SendmailPath exitted with status $?" );
- }
- };
- if ($@) {
- $RT::Logger->crit( $msgid . "Could not send mail: " . $@ );
- return 0;
- }
- }
- else {
- my @mailer_args = ($RT::MailCommand);
- my $method = 'send';
+ # Don't attach anything blank
+ $attachments->LimitNotEmpty;
+ $attachments->OrderBy( FIELD => 'id' );
- local $ENV{MAILADDRESS};
+ # We want to make sure that we don't include the attachment that's
+ # being used as the "Content" of this message" unless that attachment's
+ # content type is not like text/...
+ my $transaction_content_obj = $self->TransactionObj->ContentObj;
- if ( $RT::MailCommand eq 'sendmail' ) {
- push @mailer_args, split(/\s+/, $SendmailArguments);
- }
- elsif ( $RT::MailCommand eq 'smtp' ) {
- $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
- push @mailer_args, ( Host => $RT::SMTPServer );
- push @mailer_args, ( Debug => $RT::SMTPDebug );
- $method = 'smtpsend';
- }
- else {
- push @mailer_args, $RT::MailParams;
+ if ( $transaction_content_obj
+ && $transaction_content_obj->ContentType =~ m{text/}i )
+ {
+ # If this was part of a multipart/alternative, skip all of the kids
+ my $parent = $transaction_content_obj->ParentObj;
+ if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") {
+ $attachments->Limit(
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => 'parent',
+ OPERATOR => '!=',
+ VALUE => $parent->Id,
+ );
+ } else {
+ $attachments->Limit(
+ ENTRYAGGREGATOR => 'AND',
+ FIELD => 'id',
+ OPERATOR => '!=',
+ VALUE => $transaction_content_obj->Id,
+ );
}
+ }
- unless ( $MIMEObj->$method(@mailer_args) ) {
- $RT::Logger->crit( $msgid . "Could not send mail." );
- return (0);
+ # attach any of this transaction's attachments
+ my $seen_attachment = 0;
+ while ( my $attach = $attachments->Next ) {
+ if ( !$seen_attachment ) {
+ $MIMEObj->make_multipart( 'mixed', Force => 1 );
+ $seen_attachment = 1;
}
+ $self->AddAttachment($attach);
}
- return 1;
}
-# }}}
-
-# {{{ AddAttachments
+=head2 AddAttachment $attachment
-=head2 AddAttachments
-
-Takes any attachments to this transaction and attaches them to the message
+Takes one attachment object of L<RT::Attachmment> class and attaches it to the message
we're building.
=cut
+sub AddAttachment {
+ my $self = shift;
+ my $attach = shift;
+ my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
+
+ $MIMEObj->attach(
+ Type => $attach->ContentType,
+ Charset => $attach->OriginalEncoding,
+ Data => $attach->OriginalContent,
+ Filename => $self->MIMEEncodeString( $attach->Filename ),
+ 'RT-Attachment:' => $self->TicketObj->Id . "/"
+ . $self->TransactionObj->Id . "/"
+ . $attach->id,
+ Encoding => '-SUGGEST',
+ );
+}
-sub AddAttachments {
- my $self = shift;
+=head2 AttachTickets [@IDs]
- my $MIMEObj = $self->TemplateObj->MIMEObj;
+Returns or set list of ticket's IDs that should be attached to an outgoing message.
- $MIMEObj->head->delete('RT-Attach-Message');
+B<Note> this method works as a class method and setup things global, so you have to
+clean list by passing undef as argument.
- my $attachments = RT::Attachments->new($RT::SystemUser);
- $attachments->Limit(
- FIELD => 'TransactionId',
- VALUE => $self->TransactionObj->Id
- );
- $attachments->OrderBy( FIELD => 'id');
-
- my $transaction_content_obj = $self->TransactionObj->ContentObj;
+=cut
- # attach any of this transaction's attachments
- while ( my $attach = $attachments->Next ) {
+{
+ my $list = [];
- # Don't attach anything blank
- next unless ( $attach->ContentLength );
-
-# We want to make sure that we don't include the attachment that's being used as the "Content" of this message.
- next
- if ( $transaction_content_obj
- && $transaction_content_obj->Id == $attach->Id
- && $transaction_content_obj->ContentType =~ qr{text/plain}i );
- $MIMEObj->make_multipart('mixed');
- $MIMEObj->attach(
- Type => $attach->ContentType,
- Charset => $attach->OriginalEncoding,
- Data => $attach->OriginalContent,
- Filename => $self->MIMEEncodeString( $attach->Filename,
- $RT::EmailOutputEncoding ),
- 'RT-Attachment:' => $self->TicketObj->Id."/".$self->TransactionObj->Id."/".$attach->id,
- Encoding => '-SUGGEST'
- );
+ sub AttachTickets {
+ my $self = shift;
+ $list = [ grep defined, @_ ] if @_;
+ return @$list;
}
+}
+
+=head2 AddTickets
+
+Attaches tickets to the current message, list of tickets' ids get from
+L</AttachTickets> method.
+=cut
+
+sub AddTickets {
+ my $self = shift;
+ $self->AddTicket($_) foreach $self->AttachTickets;
+ return;
}
-# }}}
+=head2 AddTicket $ID
-# {{{ RecordOutgoingMailTransaction
+Attaches a ticket with ID to the message.
+
+Each ticket is attached as multipart entity and all its messages and attachments
+are attached as sub entities in order of creation, but only if transaction type
+is Create or Correspond.
+
+=cut
+
+sub AddTicket {
+ my $self = shift;
+ my $tid = shift;
+
+ # XXX: we need a current user here, but who is current user?
+ my $attachs = RT::Attachments->new($RT::SystemUser);
+ my $txn_alias = $attachs->TransactionAlias;
+ $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
+ $attachs->Limit(
+ ALIAS => $txn_alias,
+ FIELD => 'Type',
+ VALUE => 'Correspond'
+ );
+ $attachs->LimitByTicket($tid);
+ $attachs->LimitNotEmpty;
+ $attachs->OrderBy( FIELD => 'Created' );
+
+ my $ticket_mime = MIME::Entity->build(
+ Type => 'multipart/mixed',
+ Top => 0,
+ Description => "ticket #$tid",
+ );
+ while ( my $attachment = $attachs->Next ) {
+ $self->AddAttachment( $attachment, $ticket_mime );
+ }
+ if ( $ticket_mime->parts ) {
+ my $email_mime = $self->TemplateObj->MIMEObj;
+ $email_mime->make_multipart;
+ $email_mime->add_part($ticket_mime);
+ }
+ return;
+}
=head2 RecordOutgoingMailTransaction MIMEObj
@@ -440,12 +500,9 @@ Record a transaction in RT with this outgoing message for future record-keeping
=cut
-
-
sub RecordOutgoingMailTransaction {
- my $self = shift;
+ my $self = shift;
my $MIMEObj = shift;
-
my @parts = $MIMEObj->parts;
my @attachments;
@@ -453,26 +510,28 @@ sub RecordOutgoingMailTransaction {
foreach my $part (@parts) {
my $attach = $part->head->get('RT-Attachment');
if ($attach) {
- $RT::Logger->debug("We found an attachment. we want to not record it.");
+ $RT::Logger->debug(
+ "We found an attachment. we want to not record it.");
push @attachments, $attach;
} else {
$RT::Logger->debug("We found a part. we want to record it.");
push @keep, $part;
}
}
- $MIMEObj->parts(\@keep);
+ $MIMEObj->parts( \@keep );
foreach my $attachment (@attachments) {
- $MIMEObj->head->add('RT-Attachment', $attachment);
+ $MIMEObj->head->add( 'RT-Attachment', $attachment );
}
RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
- my $transaction = RT::Transaction->new($self->TransactionObj->CurrentUser);
+ my $transaction
+ = RT::Transaction->new( $self->TransactionObj->CurrentUser );
- # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
+# XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
my $type;
- if ($self->TransactionObj->Type eq 'Comment') {
+ if ( $self->TransactionObj->Type eq 'Comment' ) {
$type = 'CommentEmailRecord';
} else {
$type = 'EmailRecord';
@@ -489,19 +548,15 @@ sub RecordOutgoingMailTransaction {
ActivateScrips => 0
);
- if( $id ) {
- $self->{'OutgoingMailTransaction'} = $id;
+ if ($id) {
+ $self->{'OutgoingMailTransaction'} = $id;
} else {
- $RT::Logger->warning( "Could not record outgoing message transaction: $msg" );
+ $RT::Logger->warning(
+ "Could not record outgoing message transaction: $msg");
}
return $id;
}
-# }}}
-#
-
-# {{{ sub SetRTSpecialHeaders
-
=head2 SetRTSpecialHeaders
This routine adds all the random headers that RT wants in a mail message
@@ -514,161 +569,266 @@ sub SetRTSpecialHeaders {
$self->SetSubject();
$self->SetSubjectToken();
- $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding )
- if ($RT::EmailOutputEncoding);
+ $self->SetHeaderAsEncoding( 'Subject',
+ RT->Config->Get('EmailOutputEncoding') )
+ if ( RT->Config->Get('EmailOutputEncoding') );
$self->SetReturnAddress();
$self->SetReferencesHeaders();
- unless ($self->TemplateObj->MIMEObj->head->get('Message-ID')) {
- # Get Message-ID for this txn
- my $msgid = "";
- $msgid = $self->TransactionObj->Message->First->GetHeader("RT-Message-ID")
- || $self->TransactionObj->Message->First->GetHeader("Message-ID")
- if $self->TransactionObj->Message && $self->TransactionObj->Message->First;
+ unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
- # If there is one, and we can parse it, then base our Message-ID on it
- if ($msgid
- and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\Q$RT::Organization\E>$/
+ # Get Message-ID for this txn
+ my $msgid = "";
+ if ( my $msg = $self->TransactionObj->Message->First ) {
+ $msgid = $msg->GetHeader("RT-Message-ID")
+ || $msg->GetHeader("Message-ID");
+ }
+
+ # If there is one, and we can parse it, then base our Message-ID on it
+ if ( $msgid
+ and $msgid
+ =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/
"<$1." . $self->TicketObj->id
. "-" . $self->ScripObj->id
. "-" . $self->ScripActionObj->{_Message_ID}
- . "@" . $RT::Organization . ">"/eg
- and $2 == $self->TicketObj->id) {
- $self->SetHeader( "Message-ID" => $msgid );
- } else {
- $self->SetHeader( 'Message-ID',
- "<rt-"
- . $RT::VERSION . "-"
- . $$ . "-"
- . CORE::time() . "-"
- . int(rand(2000)) . '.'
- . $self->TicketObj->id . "-"
- . $self->ScripObj->id . "-" # Scrip
- . $self->ScripActionObj->{_Message_ID} . "@" # Email sent
- . $RT::Organization
- . ">" );
- }
+ . "@" . RT->Config->Get('Organization') . ">"/eg
+ and $2 == $self->TicketObj->id
+ )
+ {
+ $self->SetHeader( "Message-ID" => $msgid );
+ } else {
+ $self->SetHeader(
+ 'Message-ID' => RT::Interface::Email::GenMessageId(
+ Ticket => $self->TicketObj,
+ Scrip => $self->ScripObj,
+ ScripAction => $self->ScripActionObj
+ ),
+ );
+ }
}
$self->SetHeader( 'Precedence', "bulk" )
- unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
+ unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
- $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
+ $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
$self->SetHeader( 'RT-Ticket',
- $RT::rtname . " #" . $self->TicketObj->id() );
+ RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
$self->SetHeader( 'Managed-by',
"RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
- $self->SetHeader( 'RT-Originator',
- $self->TransactionObj->CreatorObj->EmailAddress );
+# 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 ) {
+ $self->SetHeader( 'RT-Originator', $email );
+ }
}
-# }}}
+sub DeferDigestRecipients {
+ my $self = shift;
+ $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id );
+
+ # The digest attribute will be an array of notifications that need to
+ # be sent for this transaction. The array will have the following
+ # format for its objects.
+ # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc}
+ # -> sent -> {true|false}
+ # The "sent" flag will be used by the cron job to indicate that it has
+ # run on this transaction.
+ # In a perfect world we might move this hash construction to the
+ # extension module itself.
+ my $digest_hash = {};
+
+ foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) {
+ # If we have a "PseudoTo", the "To" contains it, so we don't need to access it
+ next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) );
+ $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) );
+
+ # Store the 'daily digest' folk in an array.
+ my ( @send_now, @daily_digest, @weekly_digest, @suspended );
+
+ # Have to get the list of addresses directly from the MIME header
+ # at this point.
+ $RT::Logger->debug( $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);
+ $user_obj->LoadByEmail($rcpt);
+ if ( ! $user_obj->id ) {
+ # If there's an email address in here without an associated
+ # RT user, pass it on through.
+ $RT::Logger->debug( "User $rcpt is not associated with an RT user object. Send mail.");
+ push( @send_now, $rcpt );
+ next;
+ }
-# }}}
+ my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || '';
+ $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt");
-# {{{ RemoveInappropriateRecipients
+ if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) }
+ elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) }
+ elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) }
+ else { push( @send_now, $rcpt ) }
+ }
-=head2 RemoveInappropriateRecipients
+ # Reset the relevant mail field.
+ $RT::Logger->debug( "Removing deferred recipients from $mailfield: line");
+ if (@send_now) {
+ $self->SetHeader( $mailfield, join( ', ', @send_now ) );
+ } else { # No recipients! Remove the header.
+ $self->TemplateObj->MIMEObj->head->delete($mailfield);
+ }
-Remove addresses that are RT addresses or that are on this transaction's blacklist
+ # Push the deferred addresses into the appropriate field in
+ # our attribute hash, with the appropriate mail header.
+ $RT::Logger->debug(
+ "Setting deferred recipients for attribute creation");
+ $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0} for (@daily_digest);
+ $digest_hash->{'weekly'}->{$_} ={'header' => $mailfield, _sent => 0} for (@weekly_digest);
+ $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 } for (@suspended);
+ }
-=cut
+ if ( scalar keys %$digest_hash ) {
-sub RemoveInappropriateRecipients {
+ # Save the hash so that we can add it as an attribute to the
+ # outgoing email transaction.
+ $self->{'Deferred'} = $digest_hash;
+ } else {
+ $RT::Logger->debug( "No recipients found for deferred delivery on "
+ . "transaction #"
+ . $self->TransactionObj->id );
+ }
+}
+
+
+
+sub RecordDeferredRecipients {
my $self = shift;
+ return unless exists $self->{'Deferred'};
+
+ my $txn_id = $self->{'OutgoingMailTransaction'};
+ return unless $txn_id;
- my $msgid = $self->TemplateObj->MIMEObj->head->get ('Message-Id');
+ my $txn_obj = RT::Transaction->new( $self->CurrentUser );
+ $txn_obj->Load( $txn_id );
+ my( $ret, $msg ) = $txn_obj->AddAttribute(
+ Name => 'DeferredRecipients',
+ Content => $self->{'Deferred'}
+ );
+ $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" )
+ unless $ret;
+ return ($ret,$msg);
+}
+=head2 SquelchMailTo [@ADDRESSES]
- my @blacklist;
+Mark ADDRESSES to be removed from list of the recipients. Returns list of the addresses.
+To empty list pass undefined argument.
+
+B<Note> that this method can be called as class method and works globaly. Don't forget to
+clean this list when blocking is not required anymore, pass undef to do this.
+
+=cut
- my @types = qw/To Cc Bcc/;
+{
+ my $squelch = [];
- # Weed out any RT addresses. We really don't want to talk to ourselves!
- foreach my $type (@types) {
- @{ $self->{$type} } =
- RT::EmailParser::CullRTAddresses( "", @{ $self->{$type} } );
+ sub SquelchMailTo {
+ my $self = shift;
+ if (@_) {
+ $squelch = [ grep defined, @_ ];
+ }
+ return @$squelch;
}
+}
+
+=head2 RemoveInappropriateRecipients
+
+Remove addresses that are RT addresses or that are on this transaction's blacklist
+
+=cut
+
+sub RemoveInappropriateRecipients {
+ my $self = shift;
+
+ 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
- if ( $self->TransactionObj->Attachments->First() ) {
- if (
- $self->TransactionObj->Attachments->First->GetHeader(
- 'RT-DetectedAutoGenerated')
- )
- {
+ my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
+ if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+
+ if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
# What do we want to do with this? It's probably (?) a bounce
# caused by one of the watcher addresses being broken.
# Default ("true") is to redistribute, for historical reasons.
- if ( !$RT::RedistributeAutoGeneratedMessages ) {
+ if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
# Don't send to any watchers.
- @{ $self->{'To'} } = ();
- @{ $self->{'Cc'} } = ();
- @{ $self->{'Bcc'} } = ();
-
- $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message based on site configuration.\n");
- }
- elsif ( $RT::RedistributeAutoGeneratedMessages eq 'privileged' ) {
+ @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
+ $RT::Logger->info( $msgid
+ . " The incoming message was autogenerated. "
+ . "Not redistributing this message based on site configuration."
+ );
+ } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
+ 'privileged' )
+ {
# Only send to "privileged" watchers.
- #
-
- foreach my $type (@types) {
-
+ foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
foreach my $addr ( @{ $self->{$type} } ) {
my $user = RT::User->new($RT::SystemUser);
$user->LoadByEmail($addr);
- @{ $self->{$type} } =
- grep ( !/^\Q$addr\E$/, @{ $self->{$type} } )
- if ( !$user->Privileged );
-
+ push @blacklist, $addr if ( !$user->Privileged );
}
}
- $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message to unprivileged users based on site configuration.\n");
-
+ $RT::Logger->info( $msgid
+ . " The incoming message was autogenerated. "
+ . "Not redistributing this message to unprivileged users based on site configuration."
+ );
}
-
}
- my $squelch =
- $self->TransactionObj->Attachments->First->GetHeader(
- 'RT-Squelch-Replies-To');
-
- if ($squelch) {
- @blacklist = split( /,/, $squelch );
+ if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
+ push @blacklist, split( /,/, $squelch );
}
}
- # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
- my @non_recipients = $self->TicketObj->SquelchMailTo;
- foreach my $attribute (@non_recipients) {
- push @blacklist, $attribute->Content;
- }
+# Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
+ push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo;
+ push @blacklist, $self->SquelchMailTo;
# Cycle through the people we're sending to and pull out anyone on the
# system blacklist
- foreach my $person_to_yank (@blacklist) {
- $person_to_yank =~ s/\s//g;
- foreach my $type (@types) {
- @{ $self->{$type} } =
- grep ( !/^\Q$person_to_yank\E$/, @{ $self->{$type} } );
+ # Trim leading and trailing spaces.
+ @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse(join(', ', grep {defined} @blacklist));
+
+ foreach 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 /^\Q$addr\E$/, @blacklist ) {
+ $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
+ next;
+ }
+ push @addrs, $addr;
}
+ @{ $self->{$type} } = @addrs;
}
}
-# }}}
-# {{{ sub SetReturnAddress
-
=head2 SetReturnAddress is_comment => BOOLEAN
Calculate and set From and Reply-To headers based on the is_comment flag.
@@ -680,6 +840,7 @@ sub SetReturnAddress {
my $self = shift;
my %args = (
is_comment => 0,
+ friendly_name => undef,
@_
);
@@ -689,33 +850,35 @@ sub SetReturnAddress {
if ( $args{'is_comment'} ) {
$replyto = $self->TicketObj->QueueObj->CommentAddress
- || $RT::CommentAddress;
- }
- else {
+ || RT->Config->Get('CommentAddress');
+ } else {
$replyto = $self->TicketObj->QueueObj->CorrespondAddress
- || $RT::CorrespondAddress;
+ || RT->Config->Get('CorrespondAddress');
}
unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
- if ($RT::UseFriendlyFromLine) {
- my $friendly_name = $self->TransactionObj->CreatorObj->RealName
- || $self->TransactionObj->CreatorObj->Name;
- if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
- $friendly_name = $1;
+ if ( RT->Config->Get('UseFriendlyFromLine') ) {
+ my $friendly_name = $args{friendly_name};
+
+ unless ( $friendly_name ) {
+ $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName;
+ if ( $friendly_name =~ /^"(.*)"$/ ) { # a quoted string
+ $friendly_name = $1;
+ }
}
$friendly_name =~ s/"/\\"/g;
$self->SetHeader(
'From',
sprintf(
- $RT::FriendlyFromLineFormat,
- $self->MIMEEncodeString( $friendly_name,
- $RT::EmailOutputEncoding ),
+ RT->Config->Get('FriendlyFromLineFormat'),
+ $self->MIMEEncodeString(
+ $friendly_name, RT->Config->Get('EmailOutputEncoding')
+ ),
$replyto
),
);
- }
- else {
+ } else {
$self->SetHeader( 'From', $replyto );
}
}
@@ -726,10 +889,6 @@ sub SetReturnAddress {
}
-# }}}
-
-# {{{ sub SetHeader
-
=head2 SetHeader FIELD, VALUE
Set the FIELD of the current MIME object into VALUE.
@@ -743,20 +902,16 @@ sub SetHeader {
chomp $val;
chomp $field;
- $self->TemplateObj->MIMEObj->head->fold_length( $field, 10000 );
- $self->TemplateObj->MIMEObj->head->replace( $field, $val );
- return $self->TemplateObj->MIMEObj->head->get($field);
+ my $head = $self->TemplateObj->MIMEObj->head;
+ $head->fold_length( $field, 10000 );
+ $head->replace( $field, $val );
+ return $head->get($field);
}
-# }}}
-
-
-# {{{ sub SetSubject
-
=head2 SetSubject
-This routine sets the subject. it does not add the rt tag. that gets done elsewhere
-If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
+This routine sets the subject. it does not add the rt tag. That gets done elsewhere
+If subject is already defined via template, it uses that. otherwise, it tries to get
the transaction's subject.
=cut
@@ -765,39 +920,29 @@ sub SetSubject {
my $self = shift;
my $subject;
- my $message = $self->TransactionObj->Attachments;
if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
return ();
}
+
+ my $message = $self->TransactionObj->Attachments;
+ $message->RowsPerPage(1);
if ( $self->{'Subject'} ) {
$subject = $self->{'Subject'};
+ } elsif ( my $first = $message->First ) {
+ my $tmp = $first->GetHeader('Subject');
+ $subject = defined $tmp ? $tmp : $self->TicketObj->Subject;
+ } else {
+ $subject = $self->TicketObj->Subject;
}
- elsif ( ( $message->First() ) && ( $message->First->Headers ) ) {
- my $header = $message->First->Headers();
- $header =~ s/\n\s+/ /g;
- if ( $header =~ /^Subject: (.*?)$/m ) {
- $subject = $1;
- }
- else {
- $subject = $self->TicketObj->Subject();
- }
-
- }
- else {
- $subject = $self->TicketObj->Subject();
- }
+ $subject = '' unless defined $subject;
+ chomp $subject;
- $subject =~ s/(\r\n|\n|\s)/ /gi;
+ $subject =~ s/(\r\n|\n|\s)/ /g;
- chomp $subject;
$self->SetHeader( 'Subject', $subject );
}
-# }}}
-
-# {{{ sub SetSubjectToken
-
=head2 SetSubjectToken
This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
@@ -806,22 +951,16 @@ This routine fixes the RT tag in the subject. It's unlikely that you want to ove
sub SetSubjectToken {
my $self = shift;
- my $sub = $self->TemplateObj->MIMEObj->head->get('Subject');
- my $id = $self->TicketObj->id;
-
- my $token_re = $RT::EmailSubjectTagRegex;
- $token_re = qr/\Q$RT::rtname\E/o unless $token_re;
- return if $sub =~ /\[$token_re\s+#$id\]/;
- $sub =~ s/(\r\n|\n|\s)/ /gi;
- chomp $sub;
- $self->TemplateObj->MIMEObj->head->replace(
- Subject => "[$RT::rtname #$id] $sub",
+ my $head = $self->TemplateObj->MIMEObj->head;
+ $head->replace(
+ Subject => RT::Interface::Email::AddSubjectTag(
+ Encode::decode_utf8( $head->get('Subject') ),
+ $self->TicketObj,
+ ),
);
}
-# }}}
-
=head2 SetReferencesHeaders
Set References and In-Reply-To headers for this message.
@@ -829,18 +968,14 @@ Set References and In-Reply-To headers for this message.
=cut
sub SetReferencesHeaders {
-
my $self = shift;
my ( @in_reply_to, @references, @msgid );
- my $attachments = $self->TransactionObj->Message;
-
- if ( my $top = $attachments->First() ) {
- @in_reply_to = split(/\s+/m, $top->GetHeader('In-Reply-To') || '');
- @references = split(/\s+/m, $top->GetHeader('References') || '' );
- @msgid = split(/\s+/m, $top->GetHeader('Message-ID') || '');
- }
- else {
+ if ( my $top = $self->TransactionObj->Message->First ) {
+ @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' );
+ @references = split( /\s+/m, $top->GetHeader('References') || '' );
+ @msgid = split( /\s+/m, $top->GetHeader('Message-ID') || '' );
+ } else {
return (undef);
}
@@ -848,53 +983,54 @@ sub SetReferencesHeaders {
# the RT Web UI, and hence we want to *not* append its Message-ID
# to the References and In-Reply-To. OR it came from an outside
# source, and we should treat it as per the RFC
- if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>/) {
+ my $org = RT->Config->Get('Organization');
+ if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) {
+
+ # Make all references which are internal be to version which we
+ # have sent out
- # Make all references which are internal be to version which we
- # have sent out
- for (@references, @in_reply_to) {
- s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>$/
+ for ( @references, @in_reply_to ) {
+ s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/
"<$1." . $self->TicketObj->id .
"-" . $self->ScripObj->id .
"-" . $self->ScripActionObj->{_Message_ID} .
- "@" . $RT::Organization . ">"/eg
- }
+ "@" . $org . ">"/eg
+ }
- # In reply to whatever the internal message was in reply to
- $self->SetHeader( 'In-Reply-To', join( " ", ( @in_reply_to )));
+ # In reply to whatever the internal message was in reply to
+ $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) );
- # Default the references to whatever we're in reply to
- @references = @in_reply_to unless @references;
+ # Default the references to whatever we're in reply to
+ @references = @in_reply_to unless @references;
- # References are unchanged from internal
+ # References are unchanged from internal
} else {
- # In reply to that message
- $self->SetHeader( 'In-Reply-To', join( " ", ( @msgid )));
- # Default the references to whatever we're in reply to
- @references = @in_reply_to unless @references;
+ # In reply to that message
+ $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) );
- # Push that message onto the end of the references
- push @references, @msgid;
+ # Default the references to whatever we're in reply to
+ @references = @in_reply_to unless @references;
+
+ # Push that message onto the end of the references
+ push @references, @msgid;
}
# Push pseudo-ref to the front
my $pseudo_ref = $self->PseudoReference;
- @references = ($pseudo_ref, grep { $_ ne $pseudo_ref } @references);
+ @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references );
# If there are more than 10 references headers, remove all but the
# first four and the last six (Gotta keep this from growing
# forever)
- splice(@references, 4, -6) if ($#references >= 10);
+ splice( @references, 4, -6 ) if ( $#references >= 10 );
# Add on the references
- $self->SetHeader( 'References', join( " ", @references) );
+ $self->SetHeader( 'References', join( " ", @references ) );
$self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
}
-# }}}
-
=head2 PseudoReference
Returns a fake Message-ID: header for the ticket to allow a base level of threading
@@ -904,13 +1040,13 @@ Returns a fake Message-ID: header for the ticket to allow a base level of thread
sub PseudoReference {
my $self = shift;
- my $pseudo_ref = '<RT-Ticket-'.$self->TicketObj->id .'@'.$RT::Organization .'>';
+ my $pseudo_ref
+ = '<RT-Ticket-'
+ . $self->TicketObj->id . '@'
+ . RT->Config->Get('Organization') . '>';
return $pseudo_ref;
}
-
-# {{{ SetHeadingAsEncoding
-
=head2 SetHeaderAsEncoding($field_name, $charset_encoding)
This routine converts the field into specified charset encoding.
@@ -921,86 +1057,37 @@ sub SetHeaderAsEncoding {
my $self = shift;
my ( $field, $enc ) = ( shift, shift );
- if ($field eq 'From' and $RT::SMTPFrom) {
- $self->TemplateObj->MIMEObj->head->replace( $field, $RT::SMTPFrom );
- return;
- }
-
- my $value = $self->TemplateObj->MIMEObj->head->get($field);
-
- $value = $self->MIMEEncodeString($value, $enc);
+ my $head = $self->TemplateObj->MIMEObj->head;
- $self->TemplateObj->MIMEObj->head->replace( $field, $value );
+ if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
+ $head->replace( $field, RT->Config->Get('SMTPFrom') );
+ return;
+ }
+ my $value = $head->get( $field );
+ $value = $self->MIMEEncodeString( $value, $enc );
+ $head->replace( $field, $value );
-}
-# }}}
+}
-# {{{ MIMEEncodeString
+=head2 MIMEEncodeString
-=head2 MIMEEncodeString STRING ENCODING
+Takes a perl string and optional encoding pass it over
+L<RT::Interface::Email/EncodeToMIME>.
-Takes a string and a possible encoding and returns the string wrapped in MIME goo.
+Basicly encode a string using B encoding according to RFC2047.
=cut
sub MIMEEncodeString {
- my $self = shift;
- my $value = shift;
- # using RFC2047 notation, sec 2.
- # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
- my $charset = shift;
- my $encoding = 'B';
- # An 'encoded-word' may not be more than 75 characters long
- #
- # MIME encoding increases 4/3*(number of bytes), and always in multiples
- # of 4. Thus we have to find the best available value of bytes available
- # for each chunk.
- #
- # First we get the integer max which max*4/3 would fit on space.
- # Then we find the greater multiple of 3 lower or equal than $max.
- my $max = int(((75-length('=?'.$charset.'?'.$encoding.'?'.'?='))*3)/4);
- $max = int($max/3)*3;
-
- chomp $value;
-
- if ( $max <= 0 ) {
- # gives an error...
- $RT::Logger->crit("Can't encode! Charset or encoding too big.\n");
- return ($value);
- }
-
- return ($value) unless $value =~ /[^\x20-\x7e]/;
-
- $value =~ s/\s*$//;
-
- # we need perl string to split thing char by char
- Encode::_utf8_on($value) unless Encode::is_utf8( $value );
-
- my ($tmp, @chunks) = ('', ());
- while ( length $value ) {
- my $char = substr($value, 0, 1, '');
- my $octets = Encode::encode( $charset, $char );
- if ( length($tmp) + length($octets) > $max ) {
- push @chunks, $tmp;
- $tmp = '';
- }
- $tmp .= $octets;
- }
- push @chunks, $tmp if length $tmp;
-
- # encode an join chuncks
- $value = join "\n ",
- map encode_mimeword( $_, $encoding, $charset ), @chunks ;
- return($value);
+ my $self = shift;
+ return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] );
}
-# }}}
-
eval "require RT::Action::SendEmail_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm} );
eval "require RT::Action::SendEmail_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm} );
1;
diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm
index b4c8ee199..9b0838926 100644
--- a/rt/lib/RT/Action/SetPriority.pm
+++ b/rt/lib/RT/Action/SetPriority.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,12 +45,11 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
+
package RT::Action::SetPriority;
-require RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
#Do what we need to do and send it out.
diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm
index 7bf6eee51..80ef49224 100644
--- a/rt/lib/RT/Action/UserDefined.pm
+++ b/rt/lib/RT/Action/UserDefined.pm
@@ -1,8 +1,8 @@
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,11 @@
# those contributions and any derivatives thereof.
#
# END BPS TAGGED BLOCK }}}
-
package RT::Action::UserDefined;
-use RT::Action::Generic;
+use base 'RT::Action';
use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
=head2 Prepare