diff options
Diffstat (limited to 'rt/lib/RT/Action')
-rw-r--r-- | rt/lib/RT/Action/AutoOpen.pm | 86 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Autoreply.pm | 40 | ||||
-rw-r--r-- | rt/lib/RT/Action/CreateTickets.pm | 564 | ||||
-rw-r--r-- | rt/lib/RT/Action/EscalatePriority.pm | 142 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Generic.pm | 56 | ||||
-rwxr-xr-x | rt/lib/RT/Action/Notify.pm | 83 | ||||
-rwxr-xr-x | rt/lib/RT/Action/NotifyAsComment.pm | 34 | ||||
-rw-r--r-- | rt/lib/RT/Action/ResolveMembers.pm | 35 | ||||
-rwxr-xr-x | rt/lib/RT/Action/SendEmail.pm | 699 | ||||
-rw-r--r-- | rt/lib/RT/Action/SetPriority.pm | 61 | ||||
-rw-r--r-- | rt/lib/RT/Action/UserDefined.pm | 71 |
11 files changed, 1589 insertions, 282 deletions
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm new file mode 100644 index 000000000..ea6da1952 --- /dev/null +++ b/rt/lib/RT/Action/AutoOpen.pm @@ -0,0 +1,86 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +# 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); + +#Do what we need to do and send it out. + +#What does this type of Action does + +# {{{ sub Describe +sub Describe { + my $self = shift; + return (ref $self ); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + my $self = shift; + + # if the ticket is already open or the ticket is new and the message is more mail from the + # requestor, don't reopen it. + + if ( ( $self->TicketObj->Status eq 'open' ) + || ( ( $self->TicketObj->Status eq 'new' ) + && $self->TransactionObj->IsInbound ) + ) { + + return undef; + } + else { + return (1); + } +} +# }}} + +sub Commit { + my $self = shift; + my $oldstatus = $self->TicketObj->Status(); + $self->TicketObj->__Set( Field => 'Status', Value => 'open' ); + $self->TicketObj->_NewTransaction( + Type => 'Set', + Field => 'Status', + OldValue => $oldstatus, + NewValue => 'open', + Data => 'Ticket auto-opened on incoming correspondence' + ); + + + return(1); +} + +eval "require RT::Action::AutoOpen_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/AutoOpen_Vendor.pm}); +eval "require RT::Action::AutoOpen_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/AutoOpen_Local.pm}); + +1; diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index 624888e94..81f7bddfa 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -1,7 +1,31 @@ -#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Autoreply.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ - +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK package RT::Action::Autoreply; require RT::Action::SendEmail; + +use strict; +use vars qw/@ISA/; @ISA = qw(RT::Action::SendEmail); @@ -17,7 +41,7 @@ Sets the recipients of this message to this ticket's Requestor. sub SetRecipients { my $self=shift; - push(@{$self->{'To'}}, @{$self->TicketObj->Requestors->Emails}); + push(@{$self->{'To'}}, $self->TicketObj->Requestors->MemberEmailAddresses); return(1); } @@ -39,6 +63,7 @@ sub SetReturnAddress { @_ ); + my $replyto; if ($args{'is_comment'}) { $replyto = $self->TicketObj->QueueObj->CommentAddress || $RT::CommentAddress; @@ -49,7 +74,9 @@ sub SetReturnAddress { } unless ($self->TemplateObj->MIMEObj->head->get('From')) { - my $friendly_name=$self->TicketObj->QueueObj->Name; + my $friendly_name = $self->TicketObj->QueueObj->Description || + $self->TicketObj->QueueObj->Name; + $friendly_name =~ s/"/\\"/g; $self->SetHeader('From', "\"$friendly_name\" <$replyto>"); } @@ -61,4 +88,9 @@ sub SetReturnAddress { # }}} +eval "require RT::Action::Autoreply_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Vendor.pm}); +eval "require RT::Action::Autoreply_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Local.pm}); + 1; diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm new file mode 100644 index 000000000..0ab206771 --- /dev/null +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -0,0 +1,564 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +package RT::Action::CreateTickets; +require RT::Action::Generic; + +use strict; +use vars qw/@ISA/; +@ISA = qw(RT::Action::Generic); + +use MIME::Entity; + +=head1 NAME + + RT::Action::CreateTickets + +Create one or more tickets according to an externally supplied template. + + +=head1 SYNOPSIS + + ===Create-Ticket: codereview + Subject: Code review for {$Tickets{'TOP'}->Subject} + Depended-On-By: {$Tickets{'TOP'}->Id} + Content: Someone has created a ticket. you should review and approve it, + so they can finish their work + ENDOFCONTENT + +=head1 DESCRIPTION + + +Using the "CreateTickets" ScripAction and mandatory dependencies, RT now has +the ability to model complex workflow. When a ticket is created in a queue +that has a "CreateTickets" scripaction, that ScripAction parses its "Template" + + + +=head2 FORMAT + +CreateTickets uses the template as a template for an ordered set of tickets +to create. The basic format is as follows: + + + ===Create-Ticket: identifier + Param: Value + Param2: Value + Param3: Value + Content: Blah + blah + blah + ENDOFCONTENT + ===Create-Ticket: id2 + Param: Value + Content: Blah + ENDOFCONTENT + + +Each ===Create-Ticket: section is evaluated as its own +Text::Template object, which means that you can embed snippets +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. + +A simple example: + + ===Create-Ticket: codereview + Subject: Code review for {$Tickets{'TOP'}->Subject} + Depended-On-By: {$Tickets{'TOP'}->Id} + Content: Someone has created a ticket. you should review and approve it, + so they can finish their work + ENDOFCONTENT + + + +A convoluted example + + ===Create-Ticket: approval + { # Find out who the administrators of the group called "HR" + # of which the creator of this ticket is a member + my $name = "HR"; + + my $groups = RT::Groups->new($RT::SystemUser); + $groups->LimitToUserDefinedGroups(); + $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name"); + $groups->WithMember($TransactionObj->CreatorObj->Id); + + my $groupid = $groups->First->Id; + + my $adminccs = RT::Users->new($RT::SystemUser); + $adminccs->WhoHaveRight( + Right => "AdminGroup", + Object =>$groups->First, + IncludeSystemRights => undef, + IncludeSuperusers => 0, + IncludeSubgroupMembers => 0, + ); + + my @admins; + while (my $admin = $adminccs->Next) { + push (@admins, $admin->EmailAddress); + } + } + Queue: Approvals + Type: Approval + AdminCc: {join ("\nAdminCc: ",@admins) } + Depended-On-By: {$Tickets{"TOP"}->Id} + Refers-To: {$Tickets{"TOP"}->Id} + 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: {$Tickets{"TOP"}->Id} + Refers-On: {$Tickets{"approval"}->Id} + Queue: Approvals + Content-Type: text/plain + Content: + Your approval is requred for this ticket, too. + ENDOFCONTENT + +=head2 Acceptable fields + +A complete list of acceptable fields for this beastie: + + + * Queue => Name or id# of a queue + Subject => A text string + ! Status => A valid status. defaults to 'new' + Due => Dates can be specified in seconds since the epoch + to be handled literally or in a semi-free textual + format which RT will attempt to parse. + + + + Starts => + Started => + Resolved => + Owner => Username or id of an RT user who can and should own + this ticket + + Requestor => Email address + + Cc => Email address + + AdminCc => Email address + TimeWorked => + TimeEstimated => + TimeLeft => + InitialPriority => + FinalPriority => + Type => + +! DependsOn => + +! DependedOnBy => + +! RefersTo => + +! ReferredToBy => + +! Members => + +! MemberOf => + Content => content. Can extend to multiple lines. Everything + within a template after a Content: header is treated + as content until we hit a line containing only + ENDOFCONTENT + ContentType => the content-type of the Content field + CustomField-<id#> => custom field value + +Fields marked with an * are required. + +Fields marked with a + man have multiple values, simply +by repeating the fieldname on a new line with an additional value. + +Fields marked with a ! are postponed to be processed after all +tickets in the same actions are created. Except for 'Status', those +field can also take a ticket name within the same action (i.e. +the identifiers after ==Create-Ticket), instead of raw Ticket ID +numbers. + +When parsed, field names are converted to lowercase and have -s stripped. +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 +{ my $name = "HR"; + my $groups = RT::Groups->new($RT::SystemUser); + $groups->LimitToUserDefinedGroups(); + $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name"); + $groups->WithMember($Transaction->CreatorObj->Id); + + my $groupid = $groups->First->Id; + + my $adminccs = RT::Users->new($RT::SystemUser); + $adminccs->WhoHaveRight(Right => "AdminGroup", IncludeSystemRights => undef, IncludeSuperusers => 0, IncludeSubgroupMembers => 0, Object => $groups->First); + + my @admins; + while (my $admin = $adminccs->Next) { + push (@admins, $admin->EmailAddress); + } +} +Queue: Approvals +Type: Approval +AdminCc: {join ("\nAdminCc: ",@admins) } +Depended-On-By: {$Tickets{"TOP"}->Id} +Refers-To: {$Tickets{"TOP"}->Id} +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. +Depends-On: {$Tickets{"approval"}->Id} +Queue: Approvals +Content-Type: text/plain +Content: +Your minion approved this ticket. 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); +$t->Create(Subject => "Sample workflow test", + Owner => "root", + Queue => $q->Id); + + +=end testing + + +=head1 AUTHOR + +Jesse Vincent <jesse@bestpractical.com> + +=head1 SEE ALSO + +perl(1). + +=cut + +my %LINKTYPEMAP = ( + MemberOf => { Type => 'MemberOf', + Mode => 'Target', }, + Members => { Type => 'MemberOf', + Mode => 'Base', }, + HasMember => { Type => 'MemberOf', + Mode => 'Base', }, + RefersTo => { Type => 'RefersTo', + Mode => 'Target', }, + ReferredToBy => { Type => 'RefersTo', + Mode => 'Base', }, + DependsOn => { Type => 'DependsOn', + Mode => 'Target', }, + DependedOnBy => { Type => 'DependsOn', + Mode => 'Base', }, + +); + +# {{{ Scrip methods (Commit, Prepare) + +# {{{ sub Commit +#Do what we need to do and send it out. +sub Commit { + my $self = shift; + my (@links, @postponed); + + # XXX: cargo cult programming that works. i'll be back. + use bytes; + + # Create all the tickets we care about + return(1) unless $self->TicketObj->Type eq 'ticket'; + + %T::Tickets = (); + + foreach my $template_id ( @{ $self->{'template_order'} } ) { + $T::Tickets{'TOP'} = $T::TOP = $self->TicketObj; + $RT::Logger->debug("Workflow: processing $template_id of $T::TOP"); + + $T::ID = $template_id; + @T::AllID = @{ $self->{'template_order'} }; + + my $template = Text::Template->new( + TYPE => 'STRING', + SOURCE => $self->{'templates'}->{$template_id} + ); + + $RT::Logger->debug("Workflow: evaluating\n$self->{templates}{$template_id}"); + + my $err; + my $filled_in = $template->fill_in( PACKAGE => 'T', BROKEN => sub { + $err = { @_ }->{error}; + } ); + + $RT::Logger->debug("Workflow: yielding\n$filled_in"); + + if ($err) { + $RT::Logger->error("Ticket creation failed for ".$self->TicketObj->Id." ".$err); + while (my ($k, $v) = each %T::X) { + $RT::Logger->debug("Eliminating $template_id from ${k}'s parents."); + delete $v->{$template_id}; + } + next; + } + + my %args; + my @lines = ( split ( /\n/, $filled_in ) ); + while ( defined(my $line = shift @lines) ) { + if ( $line =~ /^(.*?):(?:\s+(.*))?$/ ) { + my $value = $2; + my $tag = lc ($1); + $tag =~ s/-//g; + + if (ref($args{$tag})) { #If it's an array, we want to push the value + push @{$args{$tag}}, $value; + } + elsif (defined ($args{$tag})) { #if we're about to get a second value, make it an array + $args{$tag} = [$args{$tag}, $value]; + } + else { #if there's nothing there, just set the value + $args{ $tag } = $value; + } + + if ( $tag eq 'content' ) { #just build up the content + # convert it to an array + $args{$tag} = defined($value) ? [ $value."\n" ] : []; + while ( defined(my $l = shift @lines) ) { + last if ($l =~ /^ENDOFCONTENT\s*$/) ; + push @{$args{'content'}}, $l."\n"; + } + } + } + } + + foreach my $date qw(due starts started resolved) { + my $dateobj = RT::Date->new($RT::SystemUser); + next unless $args{$date}; + if ($args{$date} =~ /^\d+$/) { + $dateobj->Set(Format => 'unix', Value => $args{$date}); + } else { + $dateobj->Set(Format => 'unknown', Value => $args{$date}); + } + $args{$date} = $dateobj->ISO; + } + my $mimeobj = MIME::Entity->new(); + $mimeobj->build(Type => $args{'contenttype'}, + Data => $args{'content'}); + # Now we have a %args to work with. + # Make sure we have at least the minimum set of + # reasonable data and do our thang + $T::Tickets{$template_id} ||= RT::Ticket->new($RT::SystemUser); + + # Deferred processing + push @links, ( + $T::Tickets{$template_id}, { + DependsOn => $args{'dependson'}, + DependedOnBy => $args{'dependedonby'}, + RefersTo => $args{'refersto'}, + ReferredToBy => $args{'referredtoby'}, + Members => $args{'members'}, + MemberOf => $args{'memberof'}, + } + ); + + push @postponed, ( + # Status is postponed so we don't violate dependencies + $T::Tickets{$template_id}, { + Status => $args{'status'}, + } + ); + + $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses; + + my %ticketargs = ( Queue => $args{'queue'}, + Subject=> $args{'subject'}, + Status => 'new', + Due => $args{'due'}, + Starts => $args{'starts'}, + Started => $args{'started'}, + Resolved => $args{'resolved'}, + Owner => $args{'owner'}, + Requestor => $args{'requestor'}, + Cc => $args{'cc'}, + AdminCc=> $args{'admincc'}, + TimeWorked =>$args{'timeworked'}, + TimeEstimated =>$args{'timeestimated'}, + TimeLeft =>$args{'timeleft'}, + InitialPriority => $args{'initialpriority'}, + FinalPriority => $args{'finalpriority'}, + Type => $args{'type'}, + MIMEObj => $mimeobj); + + + foreach my $key (keys(%args)) { + $key =~ /^customfield-(\d+)$/ or next; + $ticketargs{ "CustomField-" . $1 } = $args{$key}; + } + + my ($id, $transid, $msg) = $T::Tickets{$template_id}->Create(%ticketargs); + if (!$id) { + $RT::Logger->error( + "Couldn't create related ticket $template_id for ". + $self->TicketObj->Id." ".$msg + ); + next; + } + + $RT::Logger->debug("Assigned $template_id with $id"); + $T::Tickets{$template_id}->SetOriginObj($self->TicketObj) + if $T::Tickets{$template_id}->can('SetOriginObj'); + } + + # postprocessing: add links + + while (my $ticket = shift(@links)) { + $RT::Logger->debug("Handling links for " . $ticket->Id); + my %args = %{shift(@links)}; + + foreach my $type ( keys %LINKTYPEMAP ) { + next unless (defined $args{$type}); + foreach my $link ( + ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) + { + if (!exists $T::Tickets{$link}) { + $RT::Logger->debug("Skipping $type link for $link (non-existent)"); + next; + } + $RT::Logger->debug("Building $type link for $link: " . $T::Tickets{$link}->Id); + $link = $T::Tickets{$link}->Id; + + my ( $wval, $wmsg ) = $ticket->AddLink( + Type => $LINKTYPEMAP{$type}->{'Type'}, + $LINKTYPEMAP{$type}->{'Mode'} => $link, + Silent => 1 + ); + + $RT::Logger->warning("AddLink thru $link failed: $wmsg") unless $wval; + # push @non_fatal_errors, $wmsg unless ($wval); + } + + } + } + + # postponed actions -- Status only, currently + while (my $ticket = shift(@postponed)) { + $RT::Logger->debug("Handling postponed actions for $ticket"); + my %args = %{shift(@postponed)}; + + $ticket->SetStatus($args{Status}) if defined $args{Status}; + } + + return(1); +} +# }}} + +# {{{ sub Prepare + +sub Prepare { + my $self = shift; + + unless ($self->TemplateObj) { + $RT::Logger->warning("No template object handed to $self\n"); + } + + unless ($self->TransactionObj) { + $RT::Logger->warning("No transaction object handed to $self\n"); + + } + + unless ($self->TicketObj) { + $RT::Logger->warning("No ticket object handed to $self\n"); + + } + + + + +my $template_id; +foreach my $line (split(/\n/,$self->TemplateObj->Content)) { + if ($line =~ /^===Create-Ticket: (.*)$/) { + $template_id = $1; + push @{$self->{'template_order'}},$template_id; + } else { + $self->{'templates'}->{$template_id} .= $line."\n"; + } + + +} + + return 1; + +} + +# }}} + +# }}} + +eval "require RT::Action::CreateTickets_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Vendor.pm}); +eval "require RT::Action::CreateTickets_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Local.pm}); + +1; + diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm new file mode 100644 index 000000000..7ed63ae01 --- /dev/null +++ b/rt/lib/RT/Action/EscalatePriority.pm @@ -0,0 +1,142 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +=head1 NAME + + RT::Action::EscalatePriority + +=head1 DESCRIPTION + +EscalatePriority is a ScripAction which is NOT intended to be called per +transaction. It's intended to be called by an RT escalation daemon. +(The daemon is called escalator). + +EsclatePriority uses the following formula to change a ticket's priority: + + Priority = Priority + (( FinalPriority - Priority ) / ( DueDate-Today)) + +Unless the duedate is past, in which case priority gets bumped straight +to final priority. + +In this way, priority is either increased or decreased toward the final priority +as the ticket heads toward its due date. + + +=cut + + +package RT::Action::EscalatePriority; +require RT::Action::Generic; + +use strict; +use vars qw/@ISA/; +@ISA=qw(RT::Action::Generic); + +#Do what we need to do and send it out. + +#What does this type of Action does + +# {{{ sub Describe +sub Describe { + my $self = shift; + return (ref $self . " will move a ticket's priority toward its final priority."); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + my $self = shift; + + if ($self->TicketObj->Priority() == $self->TicketObj->FinalPriority()) { + # no update necessary. + return 0; + } + + #compute the number of days until the ticket is due + my $due = $self->TicketObj->DueObj(); + + + # If we don't have a due date, adjust the priority by one + # until we hit the final priority + if ($due->Unix() < 1) { + if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){ + $self->{'prio'} = ($self->TicketObj->Priority - 1); + return 1; + } + elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){ + $self->{'prio'} = ($self->TicketObj->Priority + 1); + return 1; + } + # otherwise the priority is at the final priority. we don't need to + # Continue + else { + return 0; + } + } + + # we've got a due date. now there are other things we should do + else { + my $diff_in_seconds = $due->Diff(time()); + my $diff_in_days = int( $diff_in_seconds / 86400); + + #if we haven't hit the due date yet + if ($diff_in_days > 0 ) { + + # compute the difference between the current priority and the + # final priority + + my $prio_delta = + $self->TicketObj->FinalPriority() - $self->TicketObj->Priority; + + my $inc_priority_by = int( $prio_delta / $diff_in_days ); + + #set the ticket's priority to that amount + $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by; + + } + #if $days is less than 1, set priority to final_priority + else { + $self->{'prio'} = $self->TicketObj->FinalPriority(); + } + + } + return 1; +} +# }}} + +sub Commit { + my $self = shift; + my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'}); + + unless ($val) { + $RT::Logger->debug($self . " $msg\n"); + } +} + +eval "require RT::Action::EscalatePriority_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/EscalatePriority_Vendor.pm}); +eval "require RT::Action::EscalatePriority_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/EscalatePriority_Local.pm}); + +1; diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm index ecfd4ab1a..007d299c7 100755 --- a/rt/lib/RT/Action/Generic.pm +++ b/rt/lib/RT/Action/Generic.pm @@ -1,7 +1,26 @@ -# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Generic.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# (c) 1996-2000 Jesse Vincent <jesse@fsck.com> -# This software is redistributable under the terms of the GNU GPL - +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK =head1 NAME RT::Action::Generic - a generic baseclass for RT Actions @@ -16,7 +35,6 @@ =begin testing -ok (require RT::TestHarness); ok (require RT::Action::Generic); =end testing @@ -25,6 +43,8 @@ ok (require RT::Action::Generic); package RT::Action::Generic; +use strict; + # {{{ sub new sub new { my $proto = shift; @@ -36,6 +56,13 @@ sub new { } # }}} +# {{{ sub new +sub loc { + my $self = shift; + return $self->{'ScripObj'}->loc(@_); +} +# }}} + # {{{ sub _Init sub _Init { my $self = shift; @@ -87,6 +114,13 @@ sub TemplateObj { } # }}} +# {{{ sub ScripObj +sub ScripObj { + my $self = shift; + return($self->{'ScripObj'}); +} +# }}} + # {{{ sub Type sub Type { my $self = shift; @@ -102,7 +136,7 @@ sub Type { # {{{ sub Commit sub Commit { my $self = shift; - return(0,"Commit Stubbed"); + return(0, $self->loc("Commit Stubbed")); } # }}} @@ -112,7 +146,7 @@ sub Commit { # {{{ sub Describe sub Describe { my $self = shift; - return ("No description for " . ref $self); + return $self->loc("No description for [_1]", ref $self); } # }}} @@ -122,7 +156,7 @@ sub Describe { # {{{ sub Prepare sub Prepare { my $self = shift; - return (0,"Prepare Stubbed"); + return (0, $self->loc("Prepare Stubbed")); } # }}} @@ -152,4 +186,10 @@ sub DESTROY { } # }}} + +eval "require RT::Action::Generic_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Vendor.pm}); +eval "require RT::Action::Generic_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Local.pm}); + 1; diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm index 6dca4fd41..1e4e4c073 100755 --- a/rt/lib/RT/Action/Notify.pm +++ b/rt/lib/RT/Action/Notify.pm @@ -1,7 +1,31 @@ -#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Notify.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ - +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK package RT::Action::Notify; require RT::Action::SendEmail; + +use strict; +use vars qw/@ISA/; @ISA = qw(RT::Action::SendEmail); # {{{ sub SetRecipients @@ -9,14 +33,14 @@ require RT::Action::SendEmail; =head2 SetRecipients Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All. -Explicitly B<does not> notify the creator of the transaction. +Explicitly B<does not> notify the creator of the transaction by default =cut sub SetRecipients { my $self = shift; - $arg = $self->Argument; + my $arg = $self->Argument; $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/; @@ -24,14 +48,14 @@ sub SetRecipients { if ($arg =~ /\bOtherRecipients\b/) { - if ($self->TransactionObj->Message->First) { - push (@Cc, $self->TransactionObj->Message->First->GetHeader('RT-Send-Cc')); - push (@Bcc, $self->TransactionObj->Message->First->GetHeader('RT-Send-Bcc')); + if ($self->TransactionObj->Attachments->First) { + push (@Cc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc')); + push (@Bcc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc')); } } if ( $arg =~ /\bRequestor\b/ ) { - push ( @To, @{ $self->TicketObj->Requestors->Emails } ); + push ( @To, $self->TicketObj->Requestors->MemberEmailAddresses ); } @@ -40,12 +64,12 @@ sub SetRecipients { #If we have a To, make the Ccs, Ccs, otherwise, promote them to To if (@To) { - push ( @Cc, @{ $self->TicketObj->Cc->Emails } ); - push ( @Cc, @{ $self->TicketObj->QueueObj->Cc->Emails } ); + push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses ); + push ( @Cc, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses ); } else { - push ( @Cc, @{ $self->TicketObj->Cc->Emails } ); - push ( @To, @{ $self->TicketObj->QueueObj->Cc->Emails } ); + push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses ); + push ( @To, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses ); } } @@ -65,15 +89,16 @@ sub SetRecipients { } if ( $arg =~ /\bAdminCc\b/ ) { - push ( @Bcc, @{ $self->TicketObj->AdminCc->Emails } ); - push ( @Bcc, @{ $self->TicketObj->QueueObj->AdminCc->Emails } ); + push ( @Bcc, $self->TicketObj->AdminCc->MemberEmailAddresses ); + push ( @Bcc, $self->TicketObj->QueueObj->AdminCc->MemberEmailAddresses ); } if ($RT::UseFriendlyToLine) { unless (@To) { - push ( @PseudoTo, - "\"$arg of $RT::rtname Ticket #" - . $self->TicketObj->id . "\":;" ); + push ( + @PseudoTo, + sprintf($RT::FriendlyToLineFormat, $arg, $self->TicketObj->id), + ); } } @@ -81,14 +106,17 @@ 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. - - $RT::Logger->debug("$self: To is ".join(",",@To)); - $RT::Logger->debug("$self: Cc is ".join(",",@Cc)); - $RT::Logger->debug("$self: Bcc is ".join(",",@Bcc)); - - @{ $self->{'To'} } = grep ( !/^$creator$/, @To ); - @{ $self->{'Cc'} } = grep ( !/^$creator$/, @Cc ); - @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc ); + # unless a flag is set + if ($RT::NotifyActor) { + @{ $self->{'To'} } = @To; + @{ $self->{'Cc'} } = @Cc; + @{ $self->{'Bcc'} } = @Bcc; + } + else { + @{ $self->{'To'} } = grep ( !/^$creator$/, @To ); + @{ $self->{'Cc'} } = grep ( !/^$creator$/, @Cc ); + @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc ); + } @{ $self->{'PseudoTo'} } = @PseudoTo; return (1); @@ -96,4 +124,9 @@ 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"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Local.pm}); + 1; diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm index c72bfff13..210e4ab15 100755 --- a/rt/lib/RT/Action/NotifyAsComment.pm +++ b/rt/lib/RT/Action/NotifyAsComment.pm @@ -1,7 +1,31 @@ -#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/NotifyAsComment.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ - +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK package RT::Action::NotifyAsComment; require RT::Action::Notify; + +use strict; +use vars qw/@ISA/; @ISA = qw(RT::Action::Notify); @@ -21,5 +45,11 @@ sub SetReturnAddress { return($self->SUPER::SetReturnAddress(is_comment => 1)); } + +eval "require RT::Action::NotifyAsComment_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyAsComment_Vendor.pm}); +eval "require RT::Action::NotifyAsComment_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyAsComment_Local.pm}); + 1; diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm index 00547ebe8..02ff3a58c 100644 --- a/rt/lib/RT/Action/ResolveMembers.pm +++ b/rt/lib/RT/Action/ResolveMembers.pm @@ -1,8 +1,34 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK # This Action will resolve all members of a resolved group ticket package RT::Action::ResolveMembers; require RT::Action::Generic; require RT::Links; + +use strict; +use vars qw/@ISA/; @ISA=qw(RT::Action::Generic); #Do what we need to do and send it out. @@ -12,7 +38,7 @@ require RT::Links; # {{{ sub Describe sub Describe { my $self = shift; - return (ref $self . " will resolve all members of a resolved group ticket."); + return $self->loc("[_1] will resolve all members of a resolved group ticket.", ref $self); } # }}} @@ -33,7 +59,7 @@ sub Commit { while (my $Link=$Links->Next()) { # Todo: Try to deal with remote URIs as well - next unless $Link->BaseIsLocal; + next unless $Link->BaseURI->IsLocal; my $base=RT::Ticket->new($self->TicketObj->CurrentUser); # Todo: Only work if Base is a plain ticket num: $base->Load($Link->Base); @@ -53,5 +79,10 @@ sub IsApplicable { } # }}} +eval "require RT::Action::ResolveMembers_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/ResolveMembers_Vendor.pm}); +eval "require RT::Action::ResolveMembers_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/ResolveMembers_Local.pm}); + 1; diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index e3abb1154..dac8fc8e7 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -1,20 +1,44 @@ -# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/SendEmail.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# Copyright 1996-2002 Jesse Vincent <jesse@bestpractical.com> +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK # Portions Copyright 2000 Tobias Brox <tobix@cpan.org> -# Released under the terms of version 2 of the GNU Public License package RT::Action::SendEmail; require RT::Action::Generic; +use strict; +use vars qw/@ISA/; @ISA = qw(RT::Action::Generic); +use MIME::Words qw(encode_mimeword); -=head1 NAME +use RT::EmailParser; - RT::Action::SendEmail - An Action which users can use to send mail - or can subclassed for more specialized mail sending behavior. - RT::Action::AutoReply is a good example subclass. +=head1 NAME +RT::Action::SendEmail - An Action which users can use to send mail +or can subclassed for more specialized mail sending behavior. +RT::Action::AutoReply is a good example subclass. =head1 SYNOPSIS @@ -36,7 +60,6 @@ the comments for the SetRecipients sub). =begin testing -ok (require RT::TestHarness); ok (require RT::Action::SendEmail); =end testing @@ -54,158 +77,266 @@ perl(1). # {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable) -# {{{ sub _Init +# {{{ sub _Init # We use _Init from RT::Action # }}} -# {{{ sub Commit +# {{{ sub Commit #Do what we need to do and send it out. -sub Commit { +sub Commit { my $self = shift; + + my $MIMEObj = $self->TemplateObj->MIMEObj; + my $msgid = $MIMEObj->head->get('Message-Id'); + chomp $msgid; + $RT::Logger->info($msgid." #".$self->TicketObj->id."/".$self->TransactionObj->id." - Scrip ". $self->ScripObj->id ." ".$self->ScripObj->Description); #send the email - + + # Weed out any RT addresses. We really don't want to talk to ourselves! + @{$self->{'To'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'To'}}); + @{$self->{'Cc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Cc'}}); + @{$self->{'Bcc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Bcc'}}); # 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 (defined $self->TransactionObj->Message->First()) { - my $headers = $self->TransactionObj->Message->First->Headers(); - - if ($headers =~ /^RT-Squelch-Replies-To: (.*?)$/si) { - my @blacklist = split(/,/,$1); - - # 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; - @{$self->{'To'}} = grep (!/^$person_to_yank$/, @{$self->{'To'}}); - @{$self->{'Cc'}} = grep (!/^$person_to_yank$/, @{$self->{'Cc'}}); - @{$self->{'Bcc'}} = grep (!/^$person_to_yank$/, @{$self->{'Bcc'}}); - } - } + + if ( defined $self->TransactionObj->Attachments->First() ) { + + my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To'); + + if ($squelch) { + my @blacklist = split ( /,/, $squelch ); + + # 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; + @{ $self->{'To'} } = + grep ( !/^$person_to_yank$/, @{ $self->{'To'} } ); + @{ $self->{'Cc'} } = + grep ( !/^$person_to_yank$/, @{ $self->{'Cc'} } ); + @{ $self->{'Bcc'} } = + grep ( !/^$person_to_yank$/, @{ $self->{'Bcc'} } ); + } + } } - - # Go add all the Tos, Ccs and Bccs that we need to to the message to + + # 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. - - $self->SetHeader('To', join(',', @{$self->{'To'}})) - if (@{$self->{'To'}}); - $self->SetHeader('Cc', join(',' , @{$self->{'Cc'}})) - if (@{$self->{'Cc'}}); - $self->SetHeader('Bcc', join(',', @{$self->{'Bcc'}})) - if (@{$self->{'Bcc'}});; - - my $MIMEObj = $self->TemplateObj->MIMEObj; - - $MIMEObj->make_singlepart; - - + $self->SetHeader( 'To', join ( ',', @{ $self->{'To'} } ) ) + if ( $self->{'To'} && @{ $self->{'To'} } ); + $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) ) + if ( $self->{'Cc'} && @{ $self->{'Cc'} } ); + $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) ) + if ( $self->{'Cc'} && @{ $self->{'Bcc'} } ); + + + $self->SetHeader('MIME-Version', '1.0'); + + # try to convert message body from utf-8 to $RT::EmailOutputEncoding + $self->SetHeader( 'Content-Type', 'text/plain; charset="utf-8"' ); + + RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' ); + $self->SetHeader( 'Content-Type', 'text/plain; charset="' . $RT::EmailOutputEncoding . '"' ); + + + # Build up a MIME::Entity that looks like the original message. + + my $do_attach = $self->TemplateObj->MIMEObj->head->get('RT-Attach-Message'); + + if ($do_attach) { + $self->TemplateObj->MIMEObj->head->delete('RT-Attach-Message'); + + my $attachments = RT::Attachments->new($RT::SystemUser); + $attachments->Limit( FIELD => 'TransactionId', + VALUE => $self->TransactionObj->Id ); + $attachments->OrderBy('id'); + + my $transaction_content_obj = $self->TransactionObj->ContentObj; + + # attach any of this transaction's attachments + while ( my $attach = $attachments->Next ) { + + # Don't attach anything blank + next unless ( $attach->ContentLength ); + + # We want to make sure that we don't include the attachment that's being sued 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 ), + Encoding => '-SUGGEST'); + } + + } + + + my $retval = $self->SendMessage($MIMEObj); + + + return ($retval); +} + +# }}} + +# {{{ sub Prepare + +sub Prepare { + my $self = shift; + + # This actually populates the MIME::Entity fields in the Template Object + + unless ( $self->TemplateObj ) { + $RT::Logger->warning("No template object handed to $self\n"); + } + + unless ( $self->TransactionObj ) { + $RT::Logger->warning("No transaction object handed to $self\n"); + + } + + unless ( $self->TicketObj ) { + $RT::Logger->warning("No ticket object handed to $self\n"); + + } + + my ( $result, $message ) = $self->TemplateObj->Parse( + Argument => $self->Argument, + TicketObj => $self->TicketObj, + TransactionObj => $self->TransactionObj + ); + if ($result) { + + # Header + $self->SetSubject(); + $self->SetSubjectToken(); + $self->SetRecipients(); + $self->SetReturnAddress(); + $self->SetRTSpecialHeaders(); + if ($RT::EmailOutputEncoding) { + + # l10n related header + $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding ); + } + } + + return $result; + +} + +# }}} + +# }}} + +# {{{ SendMessage +=head2 SendMessage MIMEObj + +sends the message using RT's preferred API. +TODO: Break this out to a seperate module + +=cut + +sub SendMessage { + my $self = shift; + my $MIMEObj = shift; + + my $msgid = $MIMEObj->head->get('Message-Id'); + + #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->debug("$self: No recipients found. Not sending.\n"); - return(1); + 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); } # PseudoTo (fake to headers) shouldn't get matched for message recipients. # If we don't have any 'To' header, drop in the pseudo-to header. - $self->SetHeader('To', join(',', @{$self->{'PseudoTo'}})) - if ( (@{$self->{'PseudoTo'}}) and (! $MIMEObj->head->get('To'))); - - if ($RT::MailCommand eq 'sendmailpipe') { - open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0); - print MAIL $MIMEObj->as_string; - close(MAIL); + $self->SetHeader( 'To', join ( ',', @{ $self->{'PseudoTo'} } ) ) + if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } ) + and ( !$MIMEObj->head->get('To') ) ); + if ( $RT::MailCommand eq 'sendmailpipe' ) { + eval { + open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ); + print MAIL $MIMEObj->as_string; + close(MAIL); + }; + if ($@) { + $RT::Logger->crit($msgid. "Could not send mail. -".$@ ); + } } else { - unless ($MIMEObj->send($RT::MailCommand, $RT::MailParams)) { - $RT::Logger->crit("$self: Could not send mail for ". - $self->TransactionObj . "\n"); - return(0); + my @mailer_args = ($RT::MailCommand); + local $ENV{MAILADDRESS}; + + if ( $RT::MailCommand eq 'sendmail' ) { + push @mailer_args, $RT::SendmailArguments; + } + elsif ( $RT::MailCommand eq 'smtp' ) { + $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From'); + push @mailer_args, (Server => $RT::SMTPServer); + push @mailer_args, (Debug => $RT::SMTPDebug); + } + else { + push @mailer_args, $RT::MailParams; } + + unless ( $MIMEObj->send( @mailer_args ) ) { + $RT::Logger->crit($msgid. "Could not send mail." ); + return (0); + } } - - return (1); - -} -# }}} -# {{{ sub Prepare -sub Prepare { - my $self = shift; - - # This actually populates the MIME::Entity fields in the Template Object - - unless ($self->TemplateObj) { - $RT::Logger->warning("No template object handed to $self\n"); - } - - unless ($self->TransactionObj) { - $RT::Logger->warning("No transaction object handed to $self\n"); - - } - - unless ($self->TicketObj) { - $RT::Logger->warning("No ticket object handed to $self\n"); - - } - - - $self->TemplateObj->Parse(Argument => $self->Argument, - TicketObj => $self->TicketObj, - TransactionObj => $self->TransactionObj); - - # Header - - $self->SetSubject(); - - $self->SetSubjectToken(); - - $self->SetRecipients(); - - $self->SetReturnAddress(); + my $success = ($msgid. " sent To: ".$MIMEObj->head->get('To') . " Cc: ".$MIMEObj->head->get('Cc') . " Bcc: ".$MIMEObj->head->get('Bcc')); + $success =~ s/\n//gi; + $RT::Logger->info($success); - $self->SetRTSpecialHeaders(); - - return 1; - + return (1); } # }}} -# }}} - # {{{ Deal with message headers (Set* subs, designed for easy overriding) # {{{ sub SetRTSpecialHeaders -# This routine adds all the random headers that RT wants in a mail message -# that don't matter much to anybody else. +=head2 SetRTSpecialHeaders + +This routine adds all the random headers that RT wants in a mail message +that don't matter much to anybody else. + +=cut sub SetRTSpecialHeaders { my $self = shift; - + $self->SetReferences(); $self->SetMessageID(); - + $self->SetPrecedence(); - $self->SetHeader('X-RT-Loop-Prevention', $RT::rtname); - $self->SetHeader('RT-Ticket', $RT::rtname. " #".$self->TicketObj->id()); - $self->SetHeader - ('Managed-by',"RT $RT::VERSION (http://bestpractical.com/rt/)"); - - $self->SetHeader('RT-Originator', $self->TransactionObj->CreatorObj->EmailAddress); - return(); - -} + $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname ); + $self->SetHeader( 'RT-Ticket', + $RT::rtname . " #" . $self->TicketObj->id() ); + $self->SetHeader( 'Managed-by', + "RT $RT::VERSION (http://www.bestpractical.com/rt/)" ); + $self->SetHeader( 'RT-Originator', + $self->TransactionObj->CreatorObj->EmailAddress ); + return (); +} # {{{ sub SetReferences @@ -218,105 +349,126 @@ sub SetRTSpecialHeaders { =cut sub SetReferences { - my $self = shift; - - # TODO: this one is broken. What is this email really a reply to? - # If it's a reply to an incoming message, we'll need to use the - # actual message-id from the appropriate Attachment object. For - # incoming mails, we would like to preserve the In-Reply-To and/or - # References. + my $self = shift; - $self->SetHeader - ('In-Reply-To', "<rt-".$self->TicketObj->id(). - "\@".$RT::rtname.">"); + # TODO: this one is broken. What is this email really a reply to? + # If it's a reply to an incoming message, we'll need to use the + # actual message-id from the appropriate Attachment object. For + # incoming mails, we would like to preserve the In-Reply-To and/or + # References. + $self->SetHeader( 'In-Reply-To', + "<rt-" . $self->TicketObj->id() . "\@" . $RT::rtname . ">" ); - # TODO We should always add References headers for all message-ids - # of previous messages related to this ticket. + # TODO We should always add References headers for all message-ids + # of previous messages related to this ticket. } # }}} # {{{ sub SetMessageID -# Without this one, threading won't work very nice in email agents. -# Anyway, I'm not really sure it's that healthy if we need to send -# several separate/different emails about the same transaction. +=head2 SetMessageID -sub SetMessageID { - my $self = shift; +Without this one, threading won't work very nice in email agents. +Anyway, I'm not really sure it's that healthy if we need to send +several separate/different emails about the same transaction. - # TODO this one might be sort of broken. If we have several scrips +++ - # sending several emails to several different persons, we need to - # pull out different message-ids. I'd suggest message ids like - # "rt-ticket#-transaction#-scrip#-receipient#" +=cut + +sub SetMessageID { + my $self = shift; - $self->SetHeader - ('Message-ID', "<rt-".$self->TicketObj->id(). - "-". - $self->TransactionObj->id()."." .rand(20) . "\@".$RT::Organization.">") + # TODO this one might be sort of broken. If we have several scrips +++ + # sending several emails to several different persons, we need to + # pull out different message-ids. I'd suggest message ids like + # "rt-ticket#-transaction#-scrip#-receipient#" + + $self->SetHeader( 'Message-ID', + "<rt-" + . $RT::VERSION ."-" + . $self->TicketObj->id() . "-" + . $self->TransactionObj->id() . "." + . rand(20) . "\@" + . $RT::Organization . ">" ) unless $self->TemplateObj->MIMEObj->head->get('Message-ID'); } - # }}} # }}} -# {{{ sub SetReturnAddress +# {{{ sub SetReturnAddress + +=head2 SetReturnAddress is_comment => BOOLEAN + +Calculate and set From and Reply-To headers based on the is_comment flag. + +=cut sub SetReturnAddress { - my $self = shift; - my %args = ( is_comment => 0, - @_ ); - - # From and Reply-To - # $args{is_comment} should be set if the comment address is to be used. - 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')) { - my $friendly_name=$self->TransactionObj->CreatorObj->RealName; - - if ($friendly_name =~ /^\S+\@\S+$/) { # A "bare" mail address - $friendly_name =~ s/"/\\"/g; - $friendly_name = qq|"$friendly_name"|; - } - - - # TODO: this "via RT" should really be site-configurable. - $self->SetHeader('From', "\"$friendly_name via RT\" <$replyto>"); - } - - unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) { - $self->SetHeader('Reply-To', "$replyto"); - } - + my $self = shift; + my %args = ( is_comment => 0, + @_ ); + + # From and Reply-To + # $args{is_comment} should be set if the comment address is to be used. + 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->TransactionObj->CreatorObj->RealName; + 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 ), $replyto), + ); + } + else { + $self->SetHeader( 'From', $replyto ); + } + } + + unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) { + $self->SetHeader( 'Reply-To', "$replyto" ); + } + } # }}} # {{{ sub SetHeader +=head2 SetHeader FIELD, VALUE + +Set the FIELD of the current MIME object into VALUE. + +=cut + sub SetHeader { - my $self = shift; - my $field = shift; - my $val = shift; - - chomp $val; - chomp $field; - $self->TemplateObj->MIMEObj->head->fold_length($field,10000); - $self->TemplateObj->MIMEObj->head->add($field, $val); - return $self->TemplateObj->MIMEObj->head->get($field); + my $self = shift; + my $field = shift; + my $val = shift; + + 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); } # }}} @@ -331,21 +483,29 @@ Dummy method to be overriden by subclasses which want to set the recipients. sub SetRecipients { my $self = shift; - return(); + return (); } # }}} # {{{ sub SetTo +=head2 SetTo + +Takes a string that is the addresses you want to send mail to + +=cut + sub SetTo { - my $self=shift; + my $self = shift; my $addresses = shift; - return $self->SetHeader('To',$addresses); + return $self->SetHeader( 'To', $addresses ); } + # }}} # {{{ sub SetCc + =head2 SetCc Takes a string that is the addresses you want to Cc @@ -353,11 +513,12 @@ Takes a string that is the addresses you want to Cc =cut sub SetCc { - my $self=shift; + my $self = shift; my $addresses = shift; - return $self->SetHeader('Cc', $addresses); + return $self->SetHeader( 'Cc', $addresses ); } + # }}} # {{{ sub SetBcc @@ -367,23 +528,24 @@ sub SetCc { Takes a string that is the addresses you want to Bcc =cut + sub SetBcc { - my $self=shift; + my $self = shift; my $addresses = shift; - return $self->SetHeader('Bcc', $addresses); + return $self->SetHeader( 'Bcc', $addresses ); } # }}} -# {{{ sub SetPrecedence +# {{{ sub SetPrecedence sub SetPrecedence { - my $self = shift; + my $self = shift; - unless ($self->TemplateObj->MIMEObj->head->get("Precedence")) { - $self->SetHeader('Precedence', "bulk"); - } + unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ) { + $self->SetHeader( 'Precedence', "bulk" ); + } } # }}} @@ -399,70 +561,125 @@ the transaction's subject. =cut sub SetSubject { - my $self = shift; - unless ($self->TemplateObj->MIMEObj->head->get('Subject')) { - my $message=$self->TransactionObj->Message; - my $ticket=$self->TicketObj->Id; - + my $self = shift; my $subject; - - if ($self->{'Subject'}) { - $subject = $self->{'Subject'}; - } - elsif (($message->First()) && - ($message->First->Headers)) { - $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 =~ s/(\r\n|\n|\s)/ /gi; - chomp $subject; - $self->SetHeader('Subject',$subject); - + unless ( $self->TemplateObj->MIMEObj->head->get('Subject') ) { + my $message = $self->TransactionObj->Attachments; + my $ticket = $self->TicketObj->Id; + + if ( $self->{'Subject'} ) { + $subject = $self->{'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 =~ s/(\r\n|\n|\s)/ /gi; + + chomp $subject; + $self->SetHeader( 'Subject', $subject ); + } - return($subject); + return ($subject); } + # }}} # {{{ sub SetSubjectToken =head2 SetSubjectToken - This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this. +This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this. =cut sub SetSubjectToken { - my $self=shift; - my $tag = "[$RT::rtname #".$self->TicketObj->id."]"; - my $sub = $self->TemplateObj->MIMEObj->head->get('Subject'); - unless ($sub =~ /\Q$tag\E/) { - $sub =~ s/(\r\n|\n|\s)/ /gi; - chomp $sub; - $self->TemplateObj->MIMEObj->head->replace('Subject', "$tag $sub"); - } + my $self = shift; + my $tag = "[$RT::rtname #" . $self->TicketObj->id . "]"; + my $sub = $self->TemplateObj->MIMEObj->head->get('Subject'); + unless ( $sub =~ /\Q$tag\E/ ) { + $sub =~ s/(\r\n|\n|\s)/ /gi; + chomp $sub; + $self->TemplateObj->MIMEObj->head->replace( 'Subject', "$tag $sub" ); + } } # }}} # }}} -__END__ +# {{{ + +=head2 SetHeaderAsEncoding($field_name, $charset_encoding) + +This routine converts the field into specified charset encoding. + +=cut + +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); + + # don't bother if it's us-ascii + + # See RT::I18N, 'NOTES: Why Encode::_utf8_off before Encode::from_to' + + $value = $self->MIMEEncodeString($value, $enc); + + $self->TemplateObj->MIMEObj->head->replace( $field, $value ); + + +} +# }}} + +# {{{ MIMENcodeString + +=head2 MIMEEncodeString STRING ENCODING + +Takes a string and a possible encoding and returns the string wrapped in MIME goo. + +=cut + +sub MIMEEncodeString { + my $self = shift; + my $value = shift; + my $enc = shift; -# {{{ POD + chomp $value; + return ($value) unless $value =~ /[^\x20-\x7e]/; + + $value =~ s/\s*$//; + Encode::_utf8_off($value); + my $res = Encode::from_to( $value, "utf-8", $enc ); + $value = encode_mimeword( $value, 'B', $enc ); +} # }}} +eval "require RT::Action::SendEmail_Vendor"; +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}); + 1; diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm new file mode 100644 index 000000000..515eeb58c --- /dev/null +++ b/rt/lib/RT/Action/SetPriority.pm @@ -0,0 +1,61 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +package RT::Action::SetPriority; +require RT::Action::Generic; + +use strict; +use vars qw/@ISA/; +@ISA=qw(RT::Action::Generic); + +#Do what we need to do and send it out. + +#What does this type of Action does + +# {{{ sub Describe +sub Describe { + my $self = shift; + return (ref $self . " will set a ticket's priority to the argument provided."); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + # nothing to prepare + return 1; +} +# }}} + +sub Commit { + my $self = shift; + $self->TicketObj->SetPriority($self->Argument); + +} + +eval "require RT::Action::SetPriority_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SetPriority_Vendor.pm}); +eval "require RT::Action::SetPriority_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SetPriority_Local.pm}); + +1; diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm new file mode 100644 index 000000000..e2e3d72ce --- /dev/null +++ b/rt/lib/RT/Action/UserDefined.pm @@ -0,0 +1,71 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> +# +# (Except where explictly superceded by other copyright notices) +# +# 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. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK + + +package RT::Action::UserDefined; +use RT::Action::Generic; + +use strict; +use vars qw/@ISA/; +@ISA = qw(RT::Action::Generic); + +=head2 Prepare + +This happens on every transaction. it's always applicable + +=cut + +sub Prepare { + my $self = shift; + my $retval = eval $self->ScripObj->CustomPrepareCode; + if ($@) { + $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Prepare failed: ".$@); + return (undef); + } + return ($retval); +} + +=head2 Commit + +This happens on every transaction. it's always applicable + +=cut + +sub Commit { + my $self = shift; + my $retval = eval $self->ScripObj->CustomCommitCode; + if ($@) { + $RT::Logger->error("Scrip ".$self->ScripObj->Id. " Commit failed: ".$@); + return (undef); + } + return ($retval); +} + +eval "require RT::Action::UserDefined_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/UserDefined_Vendor.pm}); +eval "require RT::Action::UserDefined_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/UserDefined_Local.pm}); + +1; + |