From 3ef62a0570055da710328937e7f65dbb2c027c62 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 12 Aug 2002 06:17:09 +0000 Subject: import rt 2.0.14 --- rt/lib/RT/Action/Autoreply.pm | 64 +++++ rt/lib/RT/Action/Generic.pm | 155 +++++++++++ rt/lib/RT/Action/Notify.pm | 99 +++++++ rt/lib/RT/Action/NotifyAsComment.pm | 25 ++ rt/lib/RT/Action/OpenDependent.pm | 55 ++++ rt/lib/RT/Action/ResolveMembers.pm | 57 +++++ rt/lib/RT/Action/SendEmail.pm | 468 ++++++++++++++++++++++++++++++++++ rt/lib/RT/Action/SendPasswordEmail.pm | 170 ++++++++++++ rt/lib/RT/Action/StallDependent.pm | 68 +++++ 9 files changed, 1161 insertions(+) create mode 100755 rt/lib/RT/Action/Autoreply.pm create mode 100755 rt/lib/RT/Action/Generic.pm create mode 100755 rt/lib/RT/Action/Notify.pm create mode 100755 rt/lib/RT/Action/NotifyAsComment.pm create mode 100644 rt/lib/RT/Action/OpenDependent.pm create mode 100644 rt/lib/RT/Action/ResolveMembers.pm create mode 100755 rt/lib/RT/Action/SendEmail.pm create mode 100755 rt/lib/RT/Action/SendPasswordEmail.pm create mode 100644 rt/lib/RT/Action/StallDependent.pm (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm new file mode 100755 index 000000000..624888e94 --- /dev/null +++ b/rt/lib/RT/Action/Autoreply.pm @@ -0,0 +1,64 @@ +#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Autoreply.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ + +package RT::Action::Autoreply; +require RT::Action::SendEmail; +@ISA = qw(RT::Action::SendEmail); + + +# {{{ sub SetRecipients + +=head2 SetRecipients + +Sets the recipients of this message to this ticket's Requestor. + +=cut + + +sub SetRecipients { + my $self=shift; + + push(@{$self->{'To'}}, @{$self->TicketObj->Requestors->Emails}); + + return(1); +} + +# }}} + + +# {{{ sub SetReturnAddress + +=head2 SetReturnAddress + +Set this message\'s return address to the apropriate queue address + +=cut + +sub SetReturnAddress { + my $self = shift; + my %args = ( is_comment => 0, + @_ + ); + + 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->TicketObj->QueueObj->Name; + $self->SetHeader('From', "\"$friendly_name\" <$replyto>"); + } + + unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) { + $self->SetHeader('Reply-To', "$replyto"); + } + +} + +# }}} + +1; diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm new file mode 100755 index 000000000..ecfd4ab1a --- /dev/null +++ b/rt/lib/RT/Action/Generic.pm @@ -0,0 +1,155 @@ +# $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 +# This software is redistributable under the terms of the GNU GPL + +=head1 NAME + + RT::Action::Generic - a generic baseclass for RT Actions + +=head1 SYNOPSIS + + use RT::Action::Generic; + +=head1 DESCRIPTION + +=head1 METHODS + +=begin testing + +ok (require RT::TestHarness); +ok (require RT::Action::Generic); + +=end testing + +=cut + +package RT::Action::Generic; + +# {{{ 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 = ( TransactionObj => undef, + TicketObj => undef, + ScripObj => undef, + TemplateObj => undef, + Argument => undef, + Type => undef, + @_ ); + + + $self->{'Argument'} = $args{'Argument'}; + $self->{'ScripObj'} = $args{'ScripObj'}; + $self->{'TicketObj'} = $args{'TicketObj'}; + $self->{'TransactionObj'} = $args{'TransactionObj'}; + $self->{'TemplateObj'} = $args{'TemplateObj'}; + $self->{'Type'} = $args{'Type'}; +} +# }}} + +# 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 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,"Commit Stubbed"); +} +# }}} + + +#What does this type of Action does + +# {{{ sub Describe +sub Describe { + my $self = shift; + return ("No description for " . ref $self); +} +# }}} + + +#Parse the templates, get things ready to go. + +# {{{ sub Prepare +sub Prepare { + my $self = shift; + return (0,"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->{'TemplateObj'} =undef + $self->{'TicketObj'} = undef; + $self->{'TransactionObj'} = undef; + $self->{'ScripObj'} = undef; + + + +} + +# }}} +1; diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm new file mode 100755 index 000000000..6dca4fd41 --- /dev/null +++ b/rt/lib/RT/Action/Notify.pm @@ -0,0 +1,99 @@ +#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Notify.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ + +package RT::Action::Notify; +require RT::Action::SendEmail; +@ISA = qw(RT::Action::SendEmail); + +# {{{ sub SetRecipients + +=head2 SetRecipients + +Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All. +Explicitly B notify the creator of the transaction. + +=cut + +sub SetRecipients { + my $self = shift; + + $arg = $self->Argument; + + $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/; + + my ( @To, @PseudoTo, @Cc, @Bcc ); + + + 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 ( $arg =~ /\bRequestor\b/ ) { + push ( @To, @{ $self->TicketObj->Requestors->Emails } ); + } + + + + 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->Emails } ); + push ( @Cc, @{ $self->TicketObj->QueueObj->Cc->Emails } ); + } + else { + push ( @Cc, @{ $self->TicketObj->Cc->Emails } ); + push ( @To, @{ $self->TicketObj->QueueObj->Cc->Emails } ); + } + } + + if ( ( $arg =~ /\bOwner\b/ ) + && ( $self->TicketObj->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 ); + } + else { + push ( @To, $self->TicketObj->OwnerObj->EmailAddress ); + } + + } + + if ( $arg =~ /\bAdminCc\b/ ) { + push ( @Bcc, @{ $self->TicketObj->AdminCc->Emails } ); + push ( @Bcc, @{ $self->TicketObj->QueueObj->AdminCc->Emails } ); + } + + if ($RT::UseFriendlyToLine) { + unless (@To) { + push ( @PseudoTo, + "\"$arg of $RT::rtname Ticket #" + . $self->TicketObj->id . "\":;" ); + } + } + + my $creator = $self->TransactionObj->CreatorObj->EmailAddress(); + + #Strip the sender out of the To, Cc and AdminCc and set the + # recipients fields used to build the message by the superclass. + + $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 ); + @{ $self->{'PseudoTo'} } = @PseudoTo; + return (1); + +} + +# }}} + +1; diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm new file mode 100755 index 000000000..c72bfff13 --- /dev/null +++ b/rt/lib/RT/Action/NotifyAsComment.pm @@ -0,0 +1,25 @@ +#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/NotifyAsComment.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ + +package RT::Action::NotifyAsComment; +require RT::Action::Notify; +@ISA = qw(RT::Action::Notify); + + +=head2 SetReturnAddress + +Tell SendEmail that this message should come out as a comment. +Calls SUPER::SetReturnAddress. + +=cut + +sub SetReturnAddress { + my $self = shift; + + # Tell RT::Action::SendEmail that this should come + # from the relevant comment email address. + $self->{'comment'} = 1; + + return($self->SUPER::SetReturnAddress(is_comment => 1)); +} +1; + diff --git a/rt/lib/RT/Action/OpenDependent.pm b/rt/lib/RT/Action/OpenDependent.pm new file mode 100644 index 000000000..b271e4709 --- /dev/null +++ b/rt/lib/RT/Action/OpenDependent.pm @@ -0,0 +1,55 @@ +# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Attic/OpenDependent.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ +# This Action will open the BASE if a dependent is resolved. + +package RT::Action::OpenDependent; +require RT::Action::Generic; +require RT::Links; +@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 stall a [local] BASE if it's open and a dependency link is created."); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + # nothing to prepare + return 1; +} +# }}} + +sub Commit { + my $self = shift; + + my $Links=RT::Links->new($RT::SystemUser); + $Links->Limit(FIELD => 'Type', VALUE => 'DependsOn'); + $Links->Limit(FIELD => 'Target', VALUE => $self->TicketObj->id); + + while (my $Link=$Links->Next()) { + next unless $Link->BaseIsLocal; + my $base=RT::Ticket->new($self->TicketObj->CurrentUser); + # Todo: Only work if Base is a plain ticket num: + $base->Load($Link->Base); + $base->Open if $base->Status eq 'stalled'; + } +} + + +# Applicability checked in Commit. + +# {{{ sub IsApplicable +sub IsApplicable { + my $self = shift; + 1; + return 1; +} +# }}} + +1; diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm new file mode 100644 index 000000000..00547ebe8 --- /dev/null +++ b/rt/lib/RT/Action/ResolveMembers.pm @@ -0,0 +1,57 @@ +# This Action will resolve all members of a resolved group ticket + +package RT::Action::ResolveMembers; +require RT::Action::Generic; +require RT::Links; +@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 resolve all members of a resolved group ticket."); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + # nothing to prepare + return 1; +} +# }}} + +sub Commit { + my $self = shift; + + my $Links=RT::Links->new($RT::SystemUser); + $Links->Limit(FIELD => 'Type', VALUE => 'MemberOf'); + $Links->Limit(FIELD => 'Target', VALUE => $self->TicketObj->id); + + while (my $Link=$Links->Next()) { + # Todo: Try to deal with remote URIs as well + next unless $Link->BaseIsLocal; + my $base=RT::Ticket->new($self->TicketObj->CurrentUser); + # Todo: Only work if Base is a plain ticket num: + $base->Load($Link->Base); + # I'm afraid this might be a major bottleneck if ResolveGroupTicket is on. + $base->Resolve; + } +} + + +# Applicability checked in Commit. + +# {{{ sub IsApplicable +sub IsApplicable { + my $self = shift; + 1; + return 1; +} +# }}} + +1; + diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm new file mode 100755 index 000000000..e3abb1154 --- /dev/null +++ b/rt/lib/RT/Action/SendEmail.pm @@ -0,0 +1,468 @@ +# $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 +# Portions Copyright 2000 Tobias Brox +# Released under the terms of version 2 of the GNU Public License + +package RT::Action::SendEmail; +require RT::Action::Generic; + +@ISA = qw(RT::Action::Generic); + + +=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 + + require RT::Action::SendEmail; + @ISA = qw(RT::Action::SendEmail); + + +=head1 DESCRIPTION + +Basically, you create another module RT::Action::YourAction which ISA +RT::Action::SendEmail. + +If you want to set the recipients of the mail to something other than +the addresses mentioned in the To, Cc, Bcc and headers in +the template, you should subclass RT::Action::SendEmail and override +either the SetRecipients method or the SetTo, SetCc, etc methods (see +the comments for the SetRecipients sub). + + +=begin testing + +ok (require RT::TestHarness); +ok (require RT::Action::SendEmail); + +=end testing + + +=head1 AUTHOR + +Jesse Vincent and Tobias Brox + +=head1 SEE ALSO + +perl(1). + +=cut + +# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable) + +# {{{ sub _Init +# We use _Init from RT::Action +# }}} + +# {{{ sub Commit +#Do what we need to do and send it out. +sub Commit { + my $self = shift; + #send the email + + # 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'}}); + } + } + } + + # 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; + + + #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); + } + + # 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); + } + else { + unless ($MIMEObj->send($RT::MailCommand, $RT::MailParams)) { + $RT::Logger->crit("$self: Could not send mail for ". + $self->TransactionObj . "\n"); + 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(); + + $self->SetRTSpecialHeaders(); + + 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. + +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(); + +} + + + +# {{{ sub SetReferences + +=head2 SetReferences + + # This routine will set the References: and In-Reply-To headers, +# autopopulating it with all the correspondence on this ticket so +# far. This should make RT responses threadable. + +=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. + + $self->SetHeader + ('In-Reply-To', "TicketObj->id(). + "\@".$RT::rtname.">"); + + + # 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. + +sub SetMessageID { + my $self = shift; + + # 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', "TicketObj->id(). + "-". + $self->TransactionObj->id()."." .rand(20) . "\@".$RT::Organization.">") + unless $self->TemplateObj->MIMEObj->head->get('Message-ID'); +} + + +# }}} + +# }}} + +# {{{ sub SetReturnAddress + +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"); + } + +} + +# }}} + +# {{{ sub SetHeader + +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); +} + +# }}} + +# {{{ sub SetRecipients + +=head2 SetRecipients + +Dummy method to be overriden by subclasses which want to set the recipients. + +=cut + +sub SetRecipients { + my $self = shift; + return(); +} + +# }}} + +# {{{ sub SetTo + +sub SetTo { + my $self=shift; + my $addresses = shift; + return $self->SetHeader('To',$addresses); +} +# }}} + +# {{{ sub SetCc +=head2 SetCc + +Takes a string that is the addresses you want to Cc + +=cut + +sub SetCc { + my $self=shift; + my $addresses = shift; + + return $self->SetHeader('Cc', $addresses); +} +# }}} + +# {{{ sub SetBcc + +=head2 SetBcc + +Takes a string that is the addresses you want to Bcc + +=cut +sub SetBcc { + my $self=shift; + my $addresses = shift; + + return $self->SetHeader('Bcc', $addresses); +} + +# }}} + +# {{{ sub SetPrecedence + +sub SetPrecedence { + my $self = shift; + + unless ($self->TemplateObj->MIMEObj->head->get("Precedence")) { + $self->SetHeader('Precedence', "bulk"); + } +} + +# }}} + +# {{{ 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 +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 $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); + + } + return($subject); +} +# }}} + +# {{{ sub SetSubjectToken + +=head2 SetSubjectToken + + 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"); + } +} + +# }}} + +# }}} + +__END__ + +# {{{ POD + +# }}} + +1; + diff --git a/rt/lib/RT/Action/SendPasswordEmail.pm b/rt/lib/RT/Action/SendPasswordEmail.pm new file mode 100755 index 000000000..91bb3c1cb --- /dev/null +++ b/rt/lib/RT/Action/SendPasswordEmail.pm @@ -0,0 +1,170 @@ +# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Attic/SendPasswordEmail.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ +# Copyright 2001 Jesse Vincent +# Released under the terms of the GNU Public License + +package RT::Action::SendPasswordEmail; +require RT::Action::Generic; + +@ISA = qw(RT::Action::Generic); + + +=head1 NAME + + RT::Action::SendGenericEmail - An Action which users can use to send mail + or can subclassed for more specialized mail sending behavior. + + + +=head1 SYNOPSIS + + require RT::Action::SendPasswordEmail; + + +=head1 DESCRIPTION + +Basically, you create another module RT::Action::YourAction which ISA +RT::Action::SendEmail. + +If you want to set the recipients of the mail to something other than +the addresses mentioned in the To, Cc, Bcc and headers in +the template, you should subclass RT::Action::SendEmail and override +either the SetRecipients method or the SetTo, SetCc, etc methods (see +the comments for the SetRecipients sub). + + +=begin testing + +ok (require RT::TestHarness); +ok (require RT::Action::SendPasswordEmail); + +=end testing + + +=head1 AUTHOR + +Jesse Vincent + +=head1 SEE ALSO + +perl(1). + +=cut + +# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable) + +# {{{ sub Commit + +#Do what we need to do and send it out. + +sub Commit { + my $self = shift; + #send the email + + + + + + my $MIMEObj = $self->TemplateObj->MIMEObj; + + + $MIMEObj->make_singlepart; + + #If we don\'t have any recipients to send to, don\'t send a message; + unless ($MIMEObj->head->get('To')) { + $RT::Logger->debug("$self: No recipients found. Not sending.\n"); + return(1); + } + + if ($RT::MailCommand eq 'sendmailpipe') { + open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0); + print MAIL $MIMEObj->as_string; + close(MAIL); + } + else { + unless ($MIMEObj->send($RT::MailCommand, $RT::MailParams)) { + $RT::Logger->crit("$self: Could not send mail for ". + $self->TransactionObj . "\n"); + 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->TemplateObj->MIMEObj->head->get('Reply-To')) { + $self->SetHeader('Reply-To',$RT::CorrespondAddress ); + } + + + $self->SetHeader('Precedence', "bulk"); + $self->SetHeader('X-RT-Loop-Prevention', $RT::rtname); + $self->SetHeader + ('Managed-by',"Request Tracker $RT::VERSION (http://www.fsck.com/projects/rt/)"); + + $self->TemplateObj->Parse(Argument => $self->Argument); + + + return 1; +} + +# }}} + +# }}} + + +# {{{ sub SetTo + +=head2 SetTo EMAIL + +Sets this message's "To" field to EMAIL + +=cut + +sub SetTo { + my $self = shift; + my $to = shift; + $self->SetHeader('To',$to); +} + +# }}} + +# {{{ sub SetHeader + +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); +} + +# }}} + + + +__END__ + +# {{{ POD + +# }}} + +1; + diff --git a/rt/lib/RT/Action/StallDependent.pm b/rt/lib/RT/Action/StallDependent.pm new file mode 100644 index 000000000..09d3448a8 --- /dev/null +++ b/rt/lib/RT/Action/StallDependent.pm @@ -0,0 +1,68 @@ +# This Action will stall the BASE if a dependency or membership link +# (according to argument) is created and if BASE is open. + +# TODO: Rename this .pm + +package RT::Action::StallDependent; +require RT::Action::Generic; +@ISA=qw|RT::Action::Generic|; + +# {{{ sub Describe +sub Describe { + my $self = shift; + return (ref $self . " will stall a [local] BASE if it's dependent [or member] of a linked up request."); +} +# }}} + + +# {{{ sub Prepare +sub Prepare { + # nothing to prepare + return 1; +} +# }}} + +sub Commit { + my $self = shift; + # Find all Dependent + my $arg=$self->Argument || "DependsOn"; + unless ($self->TransactionObj->Data =~ /^([^ ]+) $arg /) { + warn; return 0; + } + my $base_id=$1; + my $base; + if ($1 eq "THIS") { + $base=$self->TicketObj; + } else { + $base_id=&RT::Link::_IsLocal(undef, $base_id) || return 0; + $base=RT::Ticket->new($self->TicketObj->CurrentUser); + $base->Load($base_id); + } + $base->Stall if $base->Status eq 'open'; + return 0; +} + + +# {{{ sub IsApplicable + +# Only applicable if: +# 1. the link action is a dependency +# 2. BASE is a local ticket + +sub IsApplicable { + my $self = shift; + + my $arg=$self->Argument || "DependsOn"; + + # 1: + $self->TransactionObj->Data =~ /^([^ ]*) $arg / || return 0; + + # 2: + # (dirty!) + &RT::Link::_IsLocal(undef,$1) || return 0; + + return 1; +} +# }}} + +1; -- cgit v1.2.1 From 160be29a0dc62e79a4fb95d2ab8c0c7e5996760e Mon Sep 17 00:00:00 2001 From: cvs2git Date: Mon, 12 Aug 2002 06:17:10 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'BESTPRACTICAL'. --- rt/lib/RT/Action/OpenDependent.pm | 55 ----------- rt/lib/RT/Action/SendPasswordEmail.pm | 170 ---------------------------------- rt/lib/RT/Action/StallDependent.pm | 68 -------------- 3 files changed, 293 deletions(-) delete mode 100644 rt/lib/RT/Action/OpenDependent.pm delete mode 100755 rt/lib/RT/Action/SendPasswordEmail.pm delete mode 100644 rt/lib/RT/Action/StallDependent.pm (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/OpenDependent.pm b/rt/lib/RT/Action/OpenDependent.pm deleted file mode 100644 index b271e4709..000000000 --- a/rt/lib/RT/Action/OpenDependent.pm +++ /dev/null @@ -1,55 +0,0 @@ -# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Attic/OpenDependent.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# This Action will open the BASE if a dependent is resolved. - -package RT::Action::OpenDependent; -require RT::Action::Generic; -require RT::Links; -@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 stall a [local] BASE if it's open and a dependency link is created."); -} -# }}} - - -# {{{ sub Prepare -sub Prepare { - # nothing to prepare - return 1; -} -# }}} - -sub Commit { - my $self = shift; - - my $Links=RT::Links->new($RT::SystemUser); - $Links->Limit(FIELD => 'Type', VALUE => 'DependsOn'); - $Links->Limit(FIELD => 'Target', VALUE => $self->TicketObj->id); - - while (my $Link=$Links->Next()) { - next unless $Link->BaseIsLocal; - my $base=RT::Ticket->new($self->TicketObj->CurrentUser); - # Todo: Only work if Base is a plain ticket num: - $base->Load($Link->Base); - $base->Open if $base->Status eq 'stalled'; - } -} - - -# Applicability checked in Commit. - -# {{{ sub IsApplicable -sub IsApplicable { - my $self = shift; - 1; - return 1; -} -# }}} - -1; diff --git a/rt/lib/RT/Action/SendPasswordEmail.pm b/rt/lib/RT/Action/SendPasswordEmail.pm deleted file mode 100755 index 91bb3c1cb..000000000 --- a/rt/lib/RT/Action/SendPasswordEmail.pm +++ /dev/null @@ -1,170 +0,0 @@ -# $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Action/Attic/SendPasswordEmail.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ -# Copyright 2001 Jesse Vincent -# Released under the terms of the GNU Public License - -package RT::Action::SendPasswordEmail; -require RT::Action::Generic; - -@ISA = qw(RT::Action::Generic); - - -=head1 NAME - - RT::Action::SendGenericEmail - An Action which users can use to send mail - or can subclassed for more specialized mail sending behavior. - - - -=head1 SYNOPSIS - - require RT::Action::SendPasswordEmail; - - -=head1 DESCRIPTION - -Basically, you create another module RT::Action::YourAction which ISA -RT::Action::SendEmail. - -If you want to set the recipients of the mail to something other than -the addresses mentioned in the To, Cc, Bcc and headers in -the template, you should subclass RT::Action::SendEmail and override -either the SetRecipients method or the SetTo, SetCc, etc methods (see -the comments for the SetRecipients sub). - - -=begin testing - -ok (require RT::TestHarness); -ok (require RT::Action::SendPasswordEmail); - -=end testing - - -=head1 AUTHOR - -Jesse Vincent - -=head1 SEE ALSO - -perl(1). - -=cut - -# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable) - -# {{{ sub Commit - -#Do what we need to do and send it out. - -sub Commit { - my $self = shift; - #send the email - - - - - - my $MIMEObj = $self->TemplateObj->MIMEObj; - - - $MIMEObj->make_singlepart; - - #If we don\'t have any recipients to send to, don\'t send a message; - unless ($MIMEObj->head->get('To')) { - $RT::Logger->debug("$self: No recipients found. Not sending.\n"); - return(1); - } - - if ($RT::MailCommand eq 'sendmailpipe') { - open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0); - print MAIL $MIMEObj->as_string; - close(MAIL); - } - else { - unless ($MIMEObj->send($RT::MailCommand, $RT::MailParams)) { - $RT::Logger->crit("$self: Could not send mail for ". - $self->TransactionObj . "\n"); - 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->TemplateObj->MIMEObj->head->get('Reply-To')) { - $self->SetHeader('Reply-To',$RT::CorrespondAddress ); - } - - - $self->SetHeader('Precedence', "bulk"); - $self->SetHeader('X-RT-Loop-Prevention', $RT::rtname); - $self->SetHeader - ('Managed-by',"Request Tracker $RT::VERSION (http://www.fsck.com/projects/rt/)"); - - $self->TemplateObj->Parse(Argument => $self->Argument); - - - return 1; -} - -# }}} - -# }}} - - -# {{{ sub SetTo - -=head2 SetTo EMAIL - -Sets this message's "To" field to EMAIL - -=cut - -sub SetTo { - my $self = shift; - my $to = shift; - $self->SetHeader('To',$to); -} - -# }}} - -# {{{ sub SetHeader - -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); -} - -# }}} - - - -__END__ - -# {{{ POD - -# }}} - -1; - diff --git a/rt/lib/RT/Action/StallDependent.pm b/rt/lib/RT/Action/StallDependent.pm deleted file mode 100644 index 09d3448a8..000000000 --- a/rt/lib/RT/Action/StallDependent.pm +++ /dev/null @@ -1,68 +0,0 @@ -# This Action will stall the BASE if a dependency or membership link -# (according to argument) is created and if BASE is open. - -# TODO: Rename this .pm - -package RT::Action::StallDependent; -require RT::Action::Generic; -@ISA=qw|RT::Action::Generic|; - -# {{{ sub Describe -sub Describe { - my $self = shift; - return (ref $self . " will stall a [local] BASE if it's dependent [or member] of a linked up request."); -} -# }}} - - -# {{{ sub Prepare -sub Prepare { - # nothing to prepare - return 1; -} -# }}} - -sub Commit { - my $self = shift; - # Find all Dependent - my $arg=$self->Argument || "DependsOn"; - unless ($self->TransactionObj->Data =~ /^([^ ]+) $arg /) { - warn; return 0; - } - my $base_id=$1; - my $base; - if ($1 eq "THIS") { - $base=$self->TicketObj; - } else { - $base_id=&RT::Link::_IsLocal(undef, $base_id) || return 0; - $base=RT::Ticket->new($self->TicketObj->CurrentUser); - $base->Load($base_id); - } - $base->Stall if $base->Status eq 'open'; - return 0; -} - - -# {{{ sub IsApplicable - -# Only applicable if: -# 1. the link action is a dependency -# 2. BASE is a local ticket - -sub IsApplicable { - my $self = shift; - - my $arg=$self->Argument || "DependsOn"; - - # 1: - $self->TransactionObj->Data =~ /^([^ ]*) $arg / || return 0; - - # 2: - # (dirty!) - &RT::Link::_IsLocal(undef,$1) || return 0; - - return 1; -} -# }}} - -1; -- cgit v1.2.1 From 945721f48f74d5cfffef7c7cf3a3d6bc2521f5dd Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 15 Jul 2003 13:16:32 +0000 Subject: import of rt 3.0.4 --- rt/lib/RT/Action/AutoOpen.pm | 86 +++++ rt/lib/RT/Action/Autoreply.pm | 40 +- rt/lib/RT/Action/CreateTickets.pm | 564 ++++++++++++++++++++++++++++ rt/lib/RT/Action/EscalatePriority.pm | 142 +++++++ rt/lib/RT/Action/Generic.pm | 56 ++- rt/lib/RT/Action/Notify.pm | 83 +++-- rt/lib/RT/Action/NotifyAsComment.pm | 34 +- rt/lib/RT/Action/ResolveMembers.pm | 35 +- rt/lib/RT/Action/SendEmail.pm | 699 +++++++++++++++++++++++------------ rt/lib/RT/Action/SetPriority.pm | 61 +++ rt/lib/RT/Action/UserDefined.pm | 71 ++++ 11 files changed, 1589 insertions(+), 282 deletions(-) create mode 100644 rt/lib/RT/Action/AutoOpen.pm create mode 100644 rt/lib/RT/Action/CreateTickets.pm create mode 100644 rt/lib/RT/Action/EscalatePriority.pm create mode 100644 rt/lib/RT/Action/SetPriority.pm create mode 100644 rt/lib/RT/Action/UserDefined.pm (limited to 'rt/lib/RT/Action') 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 +# +# (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 +# +# (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 +# +# (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- => 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 + +=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 +# +# (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 -# This software is redistributable under the terms of the GNU GPL - +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (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 +# +# (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 notify the creator of the transaction. +Explicitly B 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 +# +# (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 +# +# (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 +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (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 -# 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', "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', + "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', "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', + "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 +# +# (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 +# +# (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; + -- cgit v1.2.1 From 289340780927b5bac2c7604d7317c3063c6dd8cc Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 11 Mar 2004 02:05:38 +0000 Subject: import of rt 3.0.9 --- rt/lib/RT/Action/AutoOpen.pm | 2 +- rt/lib/RT/Action/Autoreply.pm | 16 ++++++++++++---- rt/lib/RT/Action/CreateTickets.pm | 20 +++++++++++--------- rt/lib/RT/Action/SendEmail.pm | 4 ++-- 4 files changed, 26 insertions(+), 16 deletions(-) (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm index ea6da1952..7c8c2b89d 100644 --- a/rt/lib/RT/Action/AutoOpen.pm +++ b/rt/lib/RT/Action/AutoOpen.pm @@ -67,7 +67,7 @@ sub Commit { my $oldstatus = $self->TicketObj->Status(); $self->TicketObj->__Set( Field => 'Status', Value => 'open' ); $self->TicketObj->_NewTransaction( - Type => 'Set', + Type => 'Status', Field => 'Status', OldValue => $oldstatus, NewValue => 'open', diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index 81f7bddfa..f58b8f284 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -74,10 +74,18 @@ sub SetReturnAddress { } unless ($self->TemplateObj->MIMEObj->head->get('From')) { - my $friendly_name = $self->TicketObj->QueueObj->Description || - $self->TicketObj->QueueObj->Name; - $friendly_name =~ s/"/\\"/g; - $self->SetHeader('From', "\"$friendly_name\" <$replyto>"); + if ($RT::UseFriendlyFromLine) { + my $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')) { diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm index 0ab206771..e1c6f4a9b 100644 --- a/rt/lib/RT/Action/CreateTickets.pm +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -41,7 +41,7 @@ Create one or more tickets according to an externally supplied template. ===Create-Ticket: codereview Subject: Code review for {$Tickets{'TOP'}->Subject} - Depended-On-By: {$Tickets{'TOP'}->Id} + Depended-On-By: TOP Content: Someone has created a ticket. you should review and approve it, so they can finish their work ENDOFCONTENT @@ -84,13 +84,13 @@ 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. +shorthand TOP. A simple example: ===Create-Ticket: codereview Subject: Code review for {$Tickets{'TOP'}->Subject} - Depended-On-By: {$Tickets{'TOP'}->Id} + Depended-On-By: TOP Content: Someone has created a ticket. you should review and approve it, so they can finish their work ENDOFCONTENT @@ -128,8 +128,8 @@ A convoluted example Queue: Approvals Type: Approval AdminCc: {join ("\nAdminCc: ",@admins) } - Depended-On-By: {$Tickets{"TOP"}->Id} - Refers-To: {$Tickets{"TOP"}->Id} + Depended-On-By: TOP + Refers-To: TOP Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain @@ -139,7 +139,7 @@ A convoluted example ENDOFCONTENT ===Create-Ticket: two Subject: Manager approval - Depended-On-By: {$Tickets{"TOP"}->Id} + Depended-On-By: TOP Refers-On: {$Tickets{"approval"}->Id} Queue: Approvals Content-Type: text/plain @@ -239,8 +239,8 @@ my $approvals = Queue: Approvals Type: Approval AdminCc: {join ("\nAdminCc: ",@admins) } -Depended-On-By: {$Tickets{"TOP"}->Id} -Refers-To: {$Tickets{"TOP"}->Id} +Depended-On-By: TOP +Refers-To: TOP Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject} Due: {time + 86400} Content-Type: text/plain @@ -431,6 +431,8 @@ sub Commit { $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses; + $args{'type'} ||= 'ticket'; + my %ticketargs = ( Queue => $args{'queue'}, Subject=> $args{'subject'}, Status => 'new', @@ -452,7 +454,7 @@ sub Commit { foreach my $key (keys(%args)) { - $key =~ /^customfield-(\d+)$/ or next; + $key =~ /^customfield(\d+)$/ or next; $ticketargs{ "CustomField-" . $1 } = $args{$key}; } diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index dac8fc8e7..659238088 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -129,7 +129,7 @@ sub Commit { $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) ) if ( $self->{'Cc'} && @{ $self->{'Cc'} } ); $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) ) - if ( $self->{'Cc'} && @{ $self->{'Bcc'} } ); + if ( $self->{'Bcc'} && @{ $self->{'Bcc'} } ); $self->SetHeader('MIME-Version', '1.0'); @@ -266,7 +266,7 @@ sub SendMessage { and ( !$MIMEObj->head->get('To') ) ); if ( $RT::MailCommand eq 'sendmailpipe' ) { eval { - open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ); + open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!; print MAIL $MIMEObj->as_string; close(MAIL); }; -- cgit v1.2.1 From c582e92888b4a5553e1b4e5214cf35217e4a0cf0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 11 Nov 2004 12:13:50 +0000 Subject: import rt 3.0.12 --- rt/lib/RT/Action/EscalatePriority.pm | 7 ++++--- rt/lib/RT/Action/SendEmail.pm | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm index 7ed63ae01..e24e0541a 100644 --- a/rt/lib/RT/Action/EscalatePriority.pm +++ b/rt/lib/RT/Action/EscalatePriority.pm @@ -27,9 +27,10 @@ =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). +EscalatePriority is a ScripAction which is NOT intended to be called +per transaction. 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 for more details) EsclatePriority uses the following formula to change a ticket's priority: diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index 659238088..645c5d99d 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -239,7 +239,7 @@ sub Prepare { =head2 SendMessage MIMEObj sends the message using RT's preferred API. -TODO: Break this out to a seperate module +TODO: Break this out to a separate module =cut @@ -279,7 +279,7 @@ sub SendMessage { local $ENV{MAILADDRESS}; if ( $RT::MailCommand eq 'sendmail' ) { - push @mailer_args, $RT::SendmailArguments; + push @mailer_args, split(/\s+/, $RT::SendmailArguments); } elsif ( $RT::MailCommand eq 'smtp' ) { $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From'); @@ -663,15 +663,41 @@ Takes a string and a possible encoding and returns the string wrapped in MIME go sub MIMEEncodeString { my $self = shift; my $value = shift; - my $enc = 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; 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 ); + my $res = Encode::from_to( $value, "utf-8", $charset ); + + if ($max > 0) { + # copy value and split in chuncks + my $str=$value; + my @chunks = unpack("a$max" x int(length($str)/$max + + ((length($str) % $max) ? 1:0)), $str); + # encode an join chuncks + $value = join " ", + map encode_mimeword( $_, $encoding, $charset ), @chunks ; + return($value); + } else { + # gives an error... + $RT::Logger->crit("Can't encode! Charset or encoding too big.\n"); + } } # }}} -- cgit v1.2.1 From d39d52aac8f38ea9115628039f0df5aa3ac826de Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 3 Dec 2004 20:40:48 +0000 Subject: import rt 3.2.2 --- rt/lib/RT/Action/AutoOpen.pm | 38 +- rt/lib/RT/Action/Autoreply.pm | 50 +- rt/lib/RT/Action/CreateTickets.pm | 1256 ++++++++++++++++++++++++------ rt/lib/RT/Action/EscalatePriority.pm | 38 +- rt/lib/RT/Action/Generic.pm | 59 +- rt/lib/RT/Action/Notify.pm | 73 +- rt/lib/RT/Action/NotifyAsComment.pm | 38 +- rt/lib/RT/Action/RecordComment.pm | 142 ++++ rt/lib/RT/Action/RecordCorrespondence.pm | 143 ++++ rt/lib/RT/Action/ResolveMembers.pm | 38 +- rt/lib/RT/Action/SendEmail.pm | 680 ++++++++-------- rt/lib/RT/Action/SetPriority.pm | 38 +- rt/lib/RT/Action/UserDefined.pm | 38 +- 13 files changed, 1990 insertions(+), 641 deletions(-) create mode 100644 rt/lib/RT/Action/RecordComment.pm create mode 100644 rt/lib/RT/Action/RecordCorrespondence.pm (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm index 7c8c2b89d..b28c50d3e 100644 --- a/rt/lib/RT/Action/AutoOpen.pm +++ b/rt/lib/RT/Action/AutoOpen.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK # This Action will open the BASE if a dependent is resolved. package RT::Action::AutoOpen; diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index f58b8f284..6d2894f54 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# # +# CONTRIBUTION SUBMISSION POLICY: # -# END LICENSE BLOCK +# (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::Autoreply; require RT::Action::SendEmail; @@ -28,6 +50,18 @@ use strict; use vars qw/@ISA/; @ISA = qw(RT::Action::SendEmail); +=head2 Prepare + +Set up the relevant recipients, then call our parent. + +=cut + + +sub Prepare { + my $self = shift; + $self->SetRecipients(); + $self->SUPER::Prepare(); +} # {{{ sub SetRecipients diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm index e1c6f4a9b..68f402e4e 100644 --- a/rt/lib/RT/Action/CreateTickets.pm +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,17 +20,34 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# # +# CONTRIBUTION SUBMISSION POLICY: # -# END LICENSE BLOCK +# (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::CreateTickets; require RT::Action::Generic; use strict; +use warnings; use vars qw/@ISA/; @ISA = qw(RT::Action::Generic); @@ -39,7 +62,7 @@ Create one or more tickets according to an externally supplied template. =head1 SYNOPSIS - ===Create-Ticket: codereview + ===Create-Ticket codereview Subject: Code review for {$Tickets{'TOP'}->Subject} Depended-On-By: TOP Content: Someone has created a ticket. you should review and approve it, @@ -220,27 +243,11 @@ 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: TOP -Refers-To: TOP +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 @@ -250,11 +257,11 @@ Blah ENDOFCONTENT ===Create-Ticket: two Subject: Manager approval. -Depends-On: {$Tickets{"approval"}->Id} +Depended-On-By: approval Queue: Approvals Content-Type: text/plain Content: -Your minion approved this ticket. you ok with that? +Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that? ENDOFCONTENT '; @@ -281,10 +288,173 @@ 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", +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} = <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 @@ -300,267 +470,869 @@ 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', }, + MemberOf => { + Type => 'MemberOf', + Mode => 'Target', + }, + Parents => { + Type => 'MemberOf', + Mode => 'Target', + }, + Members => { + Type => 'MemberOf', + Mode => 'Base', + }, + Children => { + Type => 'MemberOf', + Mode => 'Base', + }, + HasMember => { + Type => 'MemberOf', + Mode => 'Base', + }, + RefersTo => { + Type => 'RefersTo', + Mode => 'Target', + }, + ReferredToBy => { + Type => 'RefersTo', + Mode => 'Base', + }, + DependsOn => { + Type => 'DependsOn', + Mode => 'Target', + }, + DependedOnBy => { + Type => 'DependsOn', + Mode => 'Base', + }, ); # {{{ Scrip methods (Commit, Prepare) -# {{{ sub Commit +# {{{ sub Commit #Do what we need to do and send it out. sub Commit { my $self = shift; - my (@links, @postponed); + + # Create all the tickets we care about + return (1) unless $self->TicketObj->Type eq 'ticket'; + + $self->CreateByTemplate( $self->TicketObj ); + $self->UpdateByTemplate( $self->TicketObj ); + 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"); + + } + + $self->Parse( Content => $self->TemplateObj->Content, _ActiveContent => 1); + return 1; + +} + +# }}} + +# }}} + +sub CreateByTemplate { + my $self = shift; + my $top = shift; + + $RT::Logger->debug("In CreateByTemplate"); + + my @results; # 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"); + my $ticketargs; + my ( @links, @postponed ); + foreach my $template_id ( @{ $self->{'create_tickets'} } ) { + $T::Tickets{'TOP'} = $T::TOP = $top if $top; + $RT::Logger->debug("Workflow: processing $template_id of $T::TOP") + if $T::TOP; + + $T::ID = $template_id; + @T::AllID = @{ $self->{'create_tickets'} }; + + ( $T::Tickets{$template_id}, $ticketargs ) = + $self->ParseLines( $template_id, \@links, \@postponed ); + + # Now we have a %args to work with. + # Make sure we have at least the minimum set of + # reasonable data and do our thang + + my ( $id, $transid, $msg ) = + $T::Tickets{$template_id}->Create(%$ticketargs); + + foreach my $res ( split( '\n', $msg ) ) { + push @results, + $T::Tickets{$template_id} + ->loc( "Ticket [_1]", $T::Tickets{$template_id}->Id ) . ': ' + . $res; + } + if ( !$id ) { + if ( $self->TicketObj ) { + $msg = + "Couldn't create related ticket $template_id for " + . $self->TicketObj->Id . " " + . $msg; + } + else { + $msg = "Couldn't create ticket $template_id " . $msg; + } + + $RT::Logger->error($msg); + next; + } - $T::ID = $template_id; - @T::AllID = @{ $self->{'template_order'} }; + $RT::Logger->debug("Assigned $template_id with $id"); + $T::Tickets{$template_id}->SetOriginObj( $self->TicketObj ) + if $self->TicketObj + && $T::Tickets{$template_id}->can('SetOriginObj'); - my $template = Text::Template->new( - TYPE => 'STRING', - SOURCE => $self->{'templates'}->{$template_id} + } + + $self->PostProcess( \@links, \@postponed ); + + return @results; +} + +sub UpdateByTemplate { + my $self = shift; + my $top = shift; + + # XXX: cargo cult programming that works. i'll be back. + use bytes; + + my @results; + %T::Tickets = (); + + my $ticketargs; + my ( @links, @postponed ); + foreach my $template_id ( @{ $self->{'update_tickets'} } ) { + $RT::Logger->debug("Update Workflow: processing $template_id"); + + $T::ID = $template_id; + @T::AllID = @{ $self->{'update_tickets'} }; + + ( $T::Tickets{$template_id}, $ticketargs ) = + $self->ParseLines( $template_id, \@links, \@postponed ); + + # Now we have a %args to work with. + # Make sure we have at least the minimum set of + # reasonable data and do our thang + + my @attribs = qw( + Subject + FinalPriority + Priority + TimeEstimated + TimeWorked + TimeLeft + Status + Queue + Due + Starts + Started + Resolved + ); + + my $id = $template_id; + $id =~ s/update-(\d+).*/$1/; + $T::Tickets{$template_id}->Load($id); + + my $msg; + if ( !$T::Tickets{$template_id}->Id ) { + $msg = "Couldn't update ticket $template_id " . $msg; + + $RT::Logger->error($msg); + next; + } + + my $current = $self->GetBaseTemplate( $T::Tickets{$template_id} ); + + $template_id =~ m/^update-(.*)/; + my $base_id = "base-$1"; + my $base = $self->{'templates'}->{$base_id}; + if ($base) { + $base =~ s/\r//g; + $base =~ s/\n+$//; + $current =~ s/\n+$//; + + # If we have no base template, set what we can. + if ($base ne $current) { + push @results, + "Could not update ticket " + . $T::Tickets{$template_id}->Id + . ": Ticket has changed"; + next; + } + } + push @results, $T::Tickets{$template_id}->Update( + AttributesRef => \@attribs, + ARGSRef => $ticketargs ); - $RT::Logger->debug("Workflow: evaluating\n$self->{templates}{$template_id}"); + push @results, + $self->UpdateWatchers( $T::Tickets{$template_id}, $ticketargs ); + + next unless exists $ticketargs->{'UpdateType'}; + if ( $ticketargs->{'UpdateType'} =~ /^(private|public)$/ ) { + my ( $Transaction, $Description, $Object ) = + $T::Tickets{$template_id}->Comment( + CcMessageTo => $ticketargs->{'Cc'}, + BccMessageTo => $ticketargs->{'Bcc'}, + MIMEObj => $ticketargs->{'MIMEObj'}, + TimeTaken => $ticketargs->{'TimeWorked'} + ); + push( @results, + $T::Tickets{$template_id} + ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': ' + . $Description ); + } + elsif ( $ticketargs->{'UpdateType'} eq 'response' ) { + my ( $Transaction, $Description, $Object ) = + $T::Tickets{$template_id}->Correspond( + CcMessageTo => $ticketargs->{'Cc'}, + BccMessageTo => $ticketargs->{'Bcc'}, + MIMEObj => $ticketargs->{'MIMEObj'}, + TimeTaken => $ticketargs->{'TimeWorked'} + ); + push( @results, + $T::Tickets{$template_id} + ->loc( "Ticket [_1]", $T::Tickets{$template_id}->id ) . ': ' + . $Description ); + } + else { + push( @results, + $T::Tickets{$template_id} + ->loc("Update type was neither correspondence nor comment.") + . " " + . $T::Tickets{$template_id}->loc("Update not recorded.") ); + } + } + + $self->PostProcess( \@links, \@postponed ); - my $err; - my $filled_in = $template->fill_in( PACKAGE => 'T', BROKEN => sub { - $err = { @_ }->{error}; - } ); + return @results; +} + +=head2 Parse TEMPLATE_CONTENT, DEFAULT_QUEUE, DEFAULT_REQEUESTOR ACTIVE + +Parse a template from TEMPLATE_CONTENT + +If $active is set to true, then we'll use Text::Template to parse the templates, +allowing you to embed active perl in your templates. + +=cut - $RT::Logger->debug("Workflow: yielding\n$filled_in"); +sub Parse { + my $self = shift; + my %args = ( Content => undef, + Queue => undef, + Requestor => undef, + _ActiveContent => undef, + @_); - 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; + if ($args{'_ActiveContent'}) { + $self->{'UsePerlTextTemplate'} =1; + } else { + + $self->{'UsePerlTextTemplate'} = 0; + } + + my @template_order; + my $template_id; + my ( $queue, $requestor ); + if ( substr( $args{'Content'}, 0, 3 ) eq '===' ) { + $RT::Logger->debug("Line: ==="); + foreach my $line ( split( /\n/, $args{'Content'} ) ) { + $line =~ s/\r$//; + $RT::Logger->debug("Line: $line"); + if ( $line =~ /^===/ ) { + if ( $template_id && !$queue && $args{'Queue'} ) { + $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n"; + } + if ( $template_id && !$requestor && $args{'Requestor'} ) { + $self->{'templates'}->{$template_id} .= + "Requestor: $args{'Requestor'}\n"; + } + $queue = 0; + $requestor = 0; + } + if ( $line =~ /^===Create-Ticket: (.*)$/ ) { + $template_id = "create-$1"; + $RT::Logger->debug("**** Create ticket: $template_id"); + push @{ $self->{'create_tickets'} }, $template_id; + } + elsif ( $line =~ /^===Update-Ticket: (.*)$/ ) { + $template_id = "update-$1"; + $RT::Logger->debug("**** Update ticket: $template_id"); + push @{ $self->{'update_tickets'} }, $template_id; + } + elsif ( $line =~ /^===Base-Ticket: (.*)$/ ) { + $template_id = "base-$1"; + $RT::Logger->debug("**** Base ticket: $template_id"); + push @{ $self->{'base_tickets'} }, $template_id; + } + elsif ( $line =~ /^===#.*$/ ) { # a comment + next; + } + else { + if ( $line =~ /^Queue:(.*)/i ) { + $queue = 1; + my $value = $1; + $value =~ s/^\s//; + $value =~ s/\s$//; + if ( !$value && $args{'Queue'}) { + $value = $args{'Queue'}; + $line = "Queue: $value"; + } + } + if ( $line =~ /^Requestor:(.*)/i ) { + $requestor = 1; + my $value = $1; + $value =~ s/^\s//; + $value =~ s/\s$//; + if ( !$value && $args{'Requestor'}) { + $value = $args{'Requestor'}; + $line = "Requestor: $value"; + } + } + $self->{'templates'}->{$template_id} .= $line . "\n"; + } + } + if ( $template_id && !$queue && $args{'Queue'} ) { + $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n"; } + } + elsif ( substr( $args{'Content'}, 0, 2 ) =~ /^id$/i ) { + $RT::Logger->debug("Line: id"); + use Regexp::Common qw(delimited); + my $first = substr( $args{'Content'}, 0, index( $args{'Content'}, "\n" ) ); + $first =~ s/\r$//; + + my $delimiter; + if ( $first =~ /\t/ ) { + $delimiter = "\t"; + } + else { + $delimiter = ','; + } + my @fields = split( /$delimiter/, $first ); + - 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"; + my $delimiter_re = qr[$delimiter]; + + my $delimited = qr[[^$delimiter]+]; + my $empty = qr[^[$delimiter](?=[$delimiter])]; + my $justquoted = qr[$RE{quoted}]; + + $args{'Content'} = substr( $args{'Content'}, index( $args{'Content'}, "\n" ) + 1 ); + $RT::Logger->debug("First: $first"); + + my $queue; + foreach my $line ( split( /\n/, $args{'Content'} ) ) { + next unless $line; + $RT::Logger->debug("Line: $line"); + + # first item is $template_id + my $i = 0; + my $template_id; + while ($line && $line =~ s/^($justquoted|.*?)(?:$delimiter_re|$)//ix) { + if ( $i == 0 ) { + $queue = 0; + $requestor = 0; + my $tid = $1; + $tid =~ s/^\s//; + $tid =~ s/\s$//; + next unless $tid; + + + if ($tid =~ /^\d+$/) { + $template_id = 'update-' . $tid; + push @{ $self->{'update_tickets'} }, $template_id; + + } elsif ($tid =~ /^#base-(\d+)$/) { + + $template_id = 'base-' . $1; + push @{ $self->{'base_tickets'} }, $template_id; + + } else { + $template_id = 'create-' . $tid; + push @{ $self->{'create_tickets'} }, $template_id; + } + $RT::Logger->debug("template_id: $tid"); + } + else { + my $value = $1; + $value = '' if ( $value =~ /^$delimiter$/ ); + if ($value =~ /^$RE{delimited}{-delim=>qq{\'\"}}$/) { + substr($value,0,1) = ""; + substr($value,-1,1) = ""; + } + my $field = $fields[$i]; + next unless $field; + $field =~ s/^\s//; + $field =~ s/\s$//; + if ( $field =~ /Body/i + || $field =~ /Data/i + || $field =~ /Message/i ) + { + $field = 'Content'; + } + if ( $field =~ /Summary/i ) { + $field = 'Subject'; + } + if ( $field =~ /Queue/i ) { + $queue = 1; + if ( !$value && $args{'Queue'} ) { + $value = $args{'Queue'}; + } + } + if ( $field =~ /Requestor/i ) { + $requestor = 1; + if ( !$value && $args{'Requestor'} ) { + $value = $args{'Requestor'}; } + } + $self->{'templates'}->{$template_id} .= $field . ": "; + $self->{'templates'}->{$template_id} .= $value || ""; + $self->{'templates'}->{$template_id} .= "\n"; + $self->{'templates'}->{$template_id} .= "ENDOFCONTENT\n" + if $field =~ /content/i; } + $i++; } - } + if ( !$queue && $args{'Queue'} ) { + $self->{'templates'}->{$template_id} .= "Queue: $args{'Queue'}\n"; + } + if ( !$requestor && $args{'Requestor'} ) { + $self->{'templates'}->{$template_id} .= + "Requestor: $args{'Requestor'}\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; - - $args{'type'} ||= 'ticket'; - - 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}; - } +sub ParseLines { + my $self = shift; + my $template_id = shift; + my $links = shift; + my $postponed = shift; - 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'); + my $content = $self->{'templates'}->{$template_id}; + + if ( $self->{'UsePerlTextTemplate'} ) { + + $RT::Logger->debug( + "Workflow: evaluating\n$self->{templates}{$template_id}"); + + my $template = Text::Template->new( + TYPE => 'STRING', + SOURCE => $content + ); + + my $err; + $content = $template->fill_in( + PACKAGE => 'T', + BROKEN => sub { + $err = {@_}->{error}; + } + ); + + $RT::Logger->debug("Workflow: yielding\n$content"); + + if ($err) { + $RT::Logger->error( "Ticket creation failed: " . $err ); + while ( my ( $k, $v ) = each %T::X ) { + $RT::Logger->debug( + "Eliminating $template_id from ${k}'s parents."); + delete $v->{$template_id}; + } + next; + } } + + my $TicketObj ||= RT::Ticket->new($self->CurrentUser); + + my %args; + my @lines = ( split( /\n/, $content ) ); + while ( defined( my $line = shift @lines ) ) { + if ( $line =~ /^(.*?):(?:\s+)(.*?)(?:\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; + } - # postprocessing: add links + 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"; + } + } + else { - 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); - } + # if it's not content, strip leading and trailing spaces + if ( $args{$tag} ) { + $args{$tag} =~ s/^\s+//g; + $args{$tag} =~ s/\s+$//g; + } + } + } + } - } + foreach my $date qw(due starts started resolved) { + my $dateobj = RT::Date->new($self->CurrentUser); + 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; } - # postponed actions -- Status only, currently - while (my $ticket = shift(@postponed)) { - $RT::Logger->debug("Handling postponed actions for $ticket"); - my %args = %{shift(@postponed)}; + $args{'requestor'} ||= $self->TicketObj->Requestors->MemberEmailAddresses + if $self->TicketObj; + + $args{'type'} ||= 'ticket'; + + 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'} || 0, + FinalPriority => $args{'finalpriority'} || 0, + Type => $args{'type'}, + ); - $ticket->SetStatus($args{Status}) if defined $args{Status}; + if ($args{content}) { + my $mimeobj = MIME::Entity->new(); + $mimeobj->build( + Type => $args{'contenttype'}, + Data => $args{'content'} + ); + $ticketargs{MIMEObj} = $mimeobj; + $ticketargs{UpdateType} = $args{'updatetype'} if $args{'updatetype'}; } - return(1); + foreach my $key ( keys(%args) ) { + $key =~ /^customfield(\d+)$/ or next; + $ticketargs{ "CustomField-" . $1 } = $args{$key}; + } + + $self->GetDeferred( \%args, $template_id, $links, $postponed ); + + return $TicketObj, \%ticketargs; } -# }}} -# {{{ 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"); - - } - +sub GetDeferred { + my $self = shift; + my $args = shift; + my $id = shift; + my $links = shift; + my $postponed = shift; + + # Deferred processing + push @$links, + ( + $id, + { + DependsOn => $args->{'dependson'}, + DependedOnBy => $args->{'dependedonby'}, + RefersTo => $args->{'refersto'}, + ReferredToBy => $args->{'referredtoby'}, + Children => $args->{'children'}, + Parents => $args->{'parents'}, + } + ); + + push @$postponed, ( + + # Status is postponed so we don't violate dependencies + $id, { Status => $args->{'status'}, } + ); +} - +sub GetUpdateTemplate { + my $self = shift; + my $t = shift; + + my $string; + $string .= "Queue: " . $t->QueueObj->Name . "\n"; + $string .= "Subject: " . $t->Subject . "\n"; + $string .= "Status: " . $t->Status . "\n"; + $string .= "UpdateType: response\n"; + $string .= "Content: \n"; + $string .= "ENDOFCONTENT\n"; + $string .= "Due: " . $t->DueObj->AsString . "\n"; + $string .= "Starts: " . $t->StartsObj->AsString . "\n"; + $string .= "Started: " . $t->StartedObj->AsString . "\n"; + $string .= "Resolved: " . $t->ResolvedObj->AsString . "\n"; + $string .= "Owner: " . $t->OwnerObj->Name . "\n"; + $string .= "Requestor: " . $t->RequestorAddresses . "\n"; + $string .= "Cc: " . $t->CcAddresses . "\n"; + $string .= "AdminCc: " . $t->AdminCcAddresses . "\n"; + $string .= "TimeWorked: " . $t->TimeWorked . "\n"; + $string .= "TimeEstimated: " . $t->TimeEstimated . "\n"; + $string .= "TimeLeft: " . $t->TimeLeft . "\n"; + $string .= "InitialPriority: " . $t->Priority . "\n"; + $string .= "FinalPriority: " . $t->FinalPriority . "\n"; + + foreach my $type ( sort keys %LINKTYPEMAP ) { + + # don't display duplicates + if ( $type eq "HasMember" + || $type eq "Members" + || $type eq "MemberOf" ) + { + next; + } + $string .= "$type: "; + + my $mode = $LINKTYPEMAP{$type}->{Mode}; + my $method = $LINKTYPEMAP{$type}->{Type}; + + my $links; + while ( my $link = $t->$method->Next ) { + $links .= ", " if $links; + + my $object = $mode . "Obj"; + my $member = $link->$object; + $links .= $member->Id if $member; + } + $string .= $links; + $string .= "\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 $string; } - - return 1; - + +sub GetBaseTemplate { + my $self = shift; + my $t = shift; + + my $string; + $string .= "Queue: " . $t->Queue . "\n"; + $string .= "Subject: " . $t->Subject . "\n"; + $string .= "Status: " . $t->Status . "\n"; + $string .= "Due: " . $t->DueObj->Unix . "\n"; + $string .= "Starts: " . $t->StartsObj->Unix . "\n"; + $string .= "Started: " . $t->StartedObj->Unix . "\n"; + $string .= "Resolved: " . $t->ResolvedObj->Unix . "\n"; + $string .= "Owner: " . $t->Owner . "\n"; + $string .= "Requestor: " . $t->RequestorAddresses . "\n"; + $string .= "Cc: " . $t->CcAddresses . "\n"; + $string .= "AdminCc: " . $t->AdminCcAddresses . "\n"; + $string .= "TimeWorked: " . $t->TimeWorked . "\n"; + $string .= "TimeEstimated: " . $t->TimeEstimated . "\n"; + $string .= "TimeLeft: " . $t->TimeLeft . "\n"; + $string .= "InitialPriority: " . $t->Priority . "\n"; + $string .= "FinalPriority: " . $t->FinalPriority . "\n"; + + return $string; } -# }}} +sub GetCreateTemplate { + my $self = shift; -# }}} + my $string; + + $string .= "Queue: General\n"; + $string .= "Subject: \n"; + $string .= "Status: new\n"; + $string .= "Content: \n"; + $string .= "ENDOFCONTENT\n"; + $string .= "Due: \n"; + $string .= "Starts: \n"; + $string .= "Started: \n"; + $string .= "Resolved: \n"; + $string .= "Owner: \n"; + $string .= "Requestor: \n"; + $string .= "Cc: \n"; + $string .= "AdminCc:\n"; + $string .= "TimeWorked: \n"; + $string .= "TimeEstimated: \n"; + $string .= "TimeLeft: \n"; + $string .= "InitialPriority: \n"; + $string .= "FinalPriority: \n"; + + foreach my $type ( keys %LINKTYPEMAP ) { + + # don't display duplicates + if ( $type eq "HasMember" + || $type eq 'Members' + || $type eq 'MemberOf' ) + { + next; + } + $string .= "$type: \n"; + } + return $string; +} + +sub UpdateWatchers { + my $self = shift; + my $ticket = shift; + my $args = shift; + + my @results; + + foreach my $type qw(Requestor Cc AdminCc) { + my $method = $type . 'Addresses'; + my $oldaddr = $ticket->$method; + + + # Skip unless we have a defined field + next unless defined $args->{$type}; + my $newaddr = $args->{$type}; + + my @old = split( ', ', $oldaddr ); + my @new = split( ', ', $newaddr ); + my %oldhash = map { $_ => 1 } @old; + my %newhash = map { $_ => 1 } @new; + + my @add = grep( !defined $oldhash{$_}, @new ); + my @delete = grep( !defined $newhash{$_}, @old ); + + foreach (@add) { + my ( $val, $msg ) = $ticket->AddWatcher( + Type => $type, + Email => $_ + ); + + push @results, + $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg; + } + + foreach (@delete) { + my ( $val, $msg ) = $ticket->DeleteWatcher( + Type => $type, + Email => $_ + ); + push @results, + $ticket->loc( "Ticket [_1]", $ticket->Id ) . ': ' . $msg; + } + } + return @results; +} + +sub PostProcess { + my $self = shift; + my $links = shift; + my $postponed = shift; + + # postprocessing: add links + + while ( my $template_id = shift(@$links) ) { + my $ticket = $T::Tickets{$template_id}; + $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} ) ) + { + next unless $link; + + if ($link =~ /^TOP$/i) { + $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{TOP}->Id ); + $link = $T::Tickets{TOP}->Id; + + } + elsif ( $link !~ m/^\d+$/ ) { + my $key = "create-$link"; + if ( !exists $T::Tickets{$key} ) { + $RT::Logger->debug( "Skipping $type link for $key (non-existent)"); + next; + } + $RT::Logger->debug( "Building $type link for $link: " . $T::Tickets{$key}->Id ); + $link = $T::Tickets{$key}->Id; + } + else { + $RT::Logger->debug("Building $type link for $link"); + } + + 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 $template_id = shift(@$postponed) ) { + my $ticket = $T::Tickets{$template_id}; + $RT::Logger->debug("Handling postponed actions for ".$ticket->id); + my %args = %{ shift(@$postponed) }; + $ticket->SetStatus( $args{Status} ) if defined $args{Status}; + } + +} eval "require RT::Action::CreateTickets_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/CreateTickets_Vendor.pm}); +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}); +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 index e24e0541a..ace72dd1b 100644 --- a/rt/lib/RT/Action/EscalatePriority.pm +++ b/rt/lib/RT/Action/EscalatePriority.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK =head1 NAME RT::Action::EscalatePriority diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm index 007d299c7..5e80f40d7 100755 --- a/rt/lib/RT/Action/Generic.pm +++ b/rt/lib/RT/Action/Generic.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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.) # -# END LICENSE BLOCK +# 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::Generic - a generic baseclass for RT Actions @@ -44,6 +66,9 @@ ok (require RT::Action::Generic); package RT::Action::Generic; use strict; +use Scalar::Util; + +use base qw/RT::Base/; # {{{ sub new sub new { @@ -56,13 +81,6 @@ sub new { } # }}} -# {{{ sub new -sub loc { - my $self = shift; - return $self->{'ScripObj'}->loc(@_); -} -# }}} - # {{{ sub _Init sub _Init { my $self = shift; @@ -72,6 +90,7 @@ sub _Init { TemplateObj => undef, Argument => undef, Type => undef, + CurrentUser => undef, @_ ); @@ -81,6 +100,16 @@ sub _Init { $self->{'TransactionObj'} = $args{'TransactionObj'}; $self->{'TemplateObj'} = $args{'TemplateObj'}; $self->{'Type'} = $args{'Type'}; + $self->CurrentUser( $args{'CurrentUser'}); + Scalar::Util::weaken($self->{'ScripObj'}); + Scalar::Util::weaken($self->{'TicketObj'}); + Scalar::Util::weaken($self->{'TemplateObj'}); + Scalar::Util::weaken($self->{'TransactionObj'}); + + + + + } # }}} diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm index 1e4e4c073..4131a8c68 100755 --- a/rt/lib/RT/Action/Notify.pm +++ b/rt/lib/RT/Action/Notify.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,20 +20,51 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} 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); + +=head2 Prepare + +Set up the relevant recipients, then call our parent. + +=cut + + +sub Prepare { + my $self = shift; + $self->SetRecipients(); + $self->SUPER::Prepare(); +} + # {{{ sub SetRecipients =head2 SetRecipients @@ -47,10 +84,18 @@ sub SetRecipients { my ( @To, @PseudoTo, @Cc, @Bcc ); - if ($arg =~ /\bOtherRecipients\b/) { - 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 =~ /\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; + } + } } @@ -118,7 +163,7 @@ sub SetRecipients { @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc ); } @{ $self->{'PseudoTo'} } = @PseudoTo; - return (1); + } diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm index 210e4ab15..475e3555f 100755 --- a/rt/lib/RT/Action/NotifyAsComment.pm +++ b/rt/lib/RT/Action/NotifyAsComment.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK package RT::Action::NotifyAsComment; require RT::Action::Notify; diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm new file mode 100644 index 000000000..b548a26eb --- /dev/null +++ b/rt/lib/RT/Action/RecordComment.pm @@ -0,0 +1,142 @@ +# {{{ BEGIN BPS TAGGED BLOCK +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# +# +# (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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (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::RecordComment; +require RT::Action::Generic; +use strict; +use vars qw/@ISA/; +@ISA = qw(RT::Action::Generic); + +=head1 NAME + +RT::Action::RecordComment - An Action which can be used from an +external tool, or in any situation where a ticket transaction has not +been started, to make a comment on the ticket. + +=head1 SYNOPSIS + +my $action_obj = RT::Action::RecordComment->new('TicketObj' => $ticket_obj, + 'TemplateObj' => $template_obj, + ); +my $result = $action_obj->Prepare(); +$action_obj->Commit() if $result; + +=head1 METHODS + +=head2 Prepare + +Check for the existence of a Transaction. If a Transaction already +exists, and is of type "Comment" or "Correspond", abort because that +will give us a loop. + +=cut + + +sub Prepare { + my $self = shift; + if (defined $self->{'TransactionObj'} && + $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { + return undef; + } + return 1; +} + +=head2 Commit + +Create a Transaction by calling the ticket's Comment method on our +parsed Template, which may have an RT-Send-Cc or RT-Send-Bcc header. +The Transaction will be of type Comment. This Transaction can then be +used by the scrips that actually send the email. + +=cut + +sub Commit { + my $self = shift; + $self->CreateTransaction(); +} + +sub CreateTransaction { + my $self = shift; + + my ($result, $msg) = $self->{'TemplateObj'}->Parse( + TicketObj => $self->{'TicketObj'}); + return undef unless $result; + + my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Comment( + MIMEObj => $self->TemplateObj->MIMEObj); + $self->{'TransactionObj'} = $transaction; +} + + +eval "require RT::Action::RecordComment_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/RecordComment_Vendor.pm}); +eval "require RT::Action::RecordComment_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/RecordComment_Local.pm}); + +1; diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm new file mode 100644 index 000000000..c6770c574 --- /dev/null +++ b/rt/lib/RT/Action/RecordCorrespondence.pm @@ -0,0 +1,143 @@ +# {{{ BEGIN BPS TAGGED BLOCK +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# +# +# (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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (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::RecordCorrespondence; +require RT::Action::Generic; +use strict; +use vars qw/@ISA/; +@ISA = qw(RT::Action::Generic); + +=head1 NAME + +RT::Action::RecordCorrespondence - An Action which can be used from an +external tool, or in any situation where a ticket transaction has not +been started, to make a comment on the ticket. + +=head1 SYNOPSIS + +my $action_obj = RT::Action::RecordCorrespondence->new( + 'TicketObj' => $ticket_obj, + 'TemplateObj' => $template_obj, + ); +my $result = $action_obj->Prepare(); +$action_obj->Commit() if $result; + +=head1 METHODS + +=head2 Prepare + +Check for the existence of a Transaction. If a Transaction already +exists, and is of type "Comment" or "Correspond", abort because that +will give us a loop. + +=cut + + +sub Prepare { + my $self = shift; + if (defined $self->{'TransactionObj'} && + $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { + return undef; + } + return 1; +} + +=head2 Commit + +Create a Transaction by calling the ticket's Correspond method on our +parsed Template, which may have an RT-Send-Cc or RT-Send-Bcc header. +The Transaction will be of type Correspond. This Transaction can then +be used by the scrips that actually send the email. + +=cut + +sub Commit { + my $self = shift; + $self->CreateTransaction(); +} + +sub CreateTransaction { + my $self = shift; + + my ($result, $msg) = $self->{'TemplateObj'}->Parse( + TicketObj => $self->{'TicketObj'}); + return undef unless $result; + + my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Correspond( + MIMEObj => $self->TemplateObj->MIMEObj); + $self->{'TransactionObj'} = $transaction; +} + + +eval "require RT::Action::RecordCorrespondence_Vendor"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/RecordCorrespondence_Vendor.pm}); +eval "require RT::Action::RecordCorrespondence_Local"; +die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/RecordCorrespondence_Local.pm}); + +1; diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm index 02ff3a58c..4d751eb6d 100644 --- a/rt/lib/RT/Action/ResolveMembers.pm +++ b/rt/lib/RT/Action/ResolveMembers.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK # This Action will resolve all members of a resolved group ticket package RT::Action::ResolveMembers; diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index 645c5d99d..a85c169b8 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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.) # -# END LICENSE BLOCK +# 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 # Portions Copyright 2000 Tobias Brox package RT::Action::SendEmail; @@ -33,6 +55,7 @@ use vars qw/@ISA/; use MIME::Words qw(encode_mimeword); use RT::EmailParser; +use Mail::Address; =head1 NAME @@ -51,13 +74,6 @@ RT::Action::AutoReply is a good example subclass. Basically, you create another module RT::Action::YourAction which ISA RT::Action::SendEmail. -If you want to set the recipients of the mail to something other than -the addresses mentioned in the To, Cc, Bcc and headers in -the template, you should subclass RT::Action::SendEmail and override -either the SetRecipients method or the SetTo, SetCc, etc methods (see -the comments for the SetRecipients sub). - - =begin testing ok (require RT::Action::SendEmail); @@ -77,165 +93,127 @@ perl(1). # {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable) -# {{{ sub _Init -# We use _Init from RT::Action -# }}} # {{{ sub Commit -#Do what we need to do and send it out. + 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 + return($self->SendMessage($self->TemplateObj->MIMEObj)); +} - # 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->Attachments->First() ) { +# {{{ sub Prepare - my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To'); +sub Prepare { + my $self = shift; - 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'} } ); - } - } + my ( $result, $message ) = $self->TemplateObj->Parse( + Argument => $self->Argument, + TicketObj => $self->TicketObj, + TransactionObj => $self->TransactionObj + ); + if ( !$result ) { + return (undef); } + my $MIMEObj = $self->TemplateObj->MIMEObj; + + # Header + $self->SetRTSpecialHeaders(); + + $self->RemoveInappropriateRecipients(); + # 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->{'To'} } ); - $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) ) - if ( $self->{'Cc'} && @{ $self->{'Cc'} } ); - $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) ) - if ( $self->{'Bcc'} && @{ $self->{'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'} } ); + # 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('MIME-Version', '1.0'); + # We should never have to set the MIME-Version header + $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' ); + 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. + $self->AddAttachments() if ( $MIMEObj->head->get('RT-Attach-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'); + return $result; - 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'); - } - } +=head2 To - my $retval = $self->SendMessage($MIMEObj); +Returns an array of Mail::Address objects containing all the To: recipients for this notification +=cut - return ($retval); +sub To { + my $self = shift; + return ($self->_AddressesFromHeader('To')); } -# }}} +=head2 Cc -# {{{ sub Prepare +Returns an array of Mail::Address objects containing all the Cc: recipients for this notification -sub Prepare { - my $self = shift; +=cut - # This actually populates the MIME::Entity fields in the Template Object +sub Cc { + my $self = shift; + return ($self->_AddressesFromHeader('Cc')); +} - unless ( $self->TemplateObj ) { - $RT::Logger->warning("No template object handed to $self\n"); - } +=head2 Bcc - unless ( $self->TransactionObj ) { - $RT::Logger->warning("No transaction object handed to $self\n"); +Returns an array of Mail::Address objects containing all the Bcc: recipients for this notification - } +=cut - unless ( $self->TicketObj ) { - $RT::Logger->warning("No ticket object handed to $self\n"); - } +sub Bcc { + my $self = shift; + return ($self->_AddressesFromHeader('Bcc')); - 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; +sub _AddressesFromHeader { + my $self = shift; + my $field = shift; + my $header = $self->TemplateObj->MIMEObj->head->get($field); + my @addresses = Mail::Address->parse($header); + return (@addresses); } -# }}} - -# }}} # {{{ SendMessage + =head2 SendMessage MIMEObj sends the message using RT's preferred API. @@ -244,61 +222,72 @@ TODO: Break this out to a separate module =cut sub SendMessage { - my $self = shift; + my $self = shift; my $MIMEObj = shift; 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 ); #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"); + 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'} && ( @{ $self->{'PseudoTo'} } ) - and ( !$MIMEObj->head->get('To') ) ); if ( $RT::MailCommand eq 'sendmailpipe' ) { eval { open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!; print MAIL $MIMEObj->as_string; close(MAIL); - }; - if ($@) { - $RT::Logger->crit($msgid. "Could not send mail. -".$@ ); + }; + if ($@) { + $RT::Logger->crit( $msgid . "Could not send mail. -" . $@ ); } } else { - my @mailer_args = ($RT::MailCommand); - local $ENV{MAILADDRESS}; + my @mailer_args = ($RT::MailCommand); + + local $ENV{MAILADDRESS}; if ( $RT::MailCommand eq 'sendmail' ) { - push @mailer_args, split(/\s+/, $RT::SendmailArguments); + push @mailer_args, split(/\s+/, $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); + $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; } - else { - push @mailer_args, $RT::MailParams; - } - unless ( $MIMEObj->send( @mailer_args ) ) { - $RT::Logger->crit($msgid. "Could not send mail." ); + unless ( $MIMEObj->send(@mailer_args) ) { + $RT::Logger->crit( $msgid . "Could not send mail." ); return (0); } } - - my $success = ($msgid. " sent To: ".$MIMEObj->head->get('To') . " Cc: ".$MIMEObj->head->get('Cc') . " Bcc: ".$MIMEObj->head->get('Bcc')); + my $success = + ( $msgid + . " sent To: " + . $MIMEObj->head->get('To') . " Cc: " + . $MIMEObj->head->get('Cc') . " Bcc: " + . $MIMEObj->head->get('Bcc') ); $success =~ s/\n//gi; + + $self->RecordOutgoingMailTransaction($MIMEObj) if ($RT::RecordOutgoingEmail); + $RT::Logger->info($success); return (1); @@ -306,51 +295,139 @@ sub SendMessage { # }}} -# {{{ Deal with message headers (Set* subs, designed for easy overriding) +# {{{ AddAttachments -# {{{ sub SetRTSpecialHeaders +=head2 AddAttachments -=head2 SetRTSpecialHeaders +Takes any attachments to this transaction and attaches them to the message +we're building. + +=cut -This routine adds all the random headers that RT wants in a mail message -that don't matter much to anybody else. + +sub AddAttachments { + my $self = shift; + + my $MIMEObj = $self->TemplateObj->MIMEObj; + + $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 ), + 'RT-Attachment:' => $self->TicketObj->Id."/".$self->TransactionObj->Id."/".$attach->id, + Encoding => '-SUGGEST' + ); + } + +} + +# }}} + +# {{{ RecordOutgoingMailTransaction + +=head2 RecordOutgoingMailTransaction MIMEObj + +Record a transaction in RT with this outgoing message for future record-keeping purposes =cut -sub SetRTSpecialHeaders { + + +sub RecordOutgoingMailTransaction { my $self = shift; + my $MIMEObj = shift; + + + my @parts = $MIMEObj->parts; + my @attachments; + my @keep; + 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."); + push @attachments, $attach; + } else { + $RT::Logger->debug("We found a part. we want to record it."); + push @keep, $part; + } + } + $MIMEObj->parts(\@keep); + foreach my $attachment (@attachments) { + $MIMEObj->head->add('RT-Attachment', $attachment); + } - $self->SetReferences(); + RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' ); - $self->SetMessageID(); + my $transaction = RT::Transaction->new($self->TransactionObj->CurrentUser); - $self->SetPrecedence(); + # XXX: TODO -> Record attachments as references to things in the attachments table, maybe. - $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/)" ); + my $type; + if ($self->TransactionObj->Type eq 'Comment') { + $type = 'CommentEmailRecord'; + } else { + $type = 'EmailRecord'; + } + + + + my ( $id, $msg ) = $transaction->Create( + Ticket => $self->TicketObj->Id, + Type => $type, + Data => $MIMEObj->head->get('Message-Id'), + MIMEObj => $MIMEObj, + ActivateScrips => 0 + ); - $self->SetHeader( 'RT-Originator', - $self->TransactionObj->CreatorObj->EmailAddress ); - return (); } -# {{{ sub SetReferences +# }}} +# + +# {{{ sub SetRTSpecialHeaders + +=head2 SetRTSpecialHeaders -=head2 SetReferences - - # This routine will set the References: and In-Reply-To headers, -# autopopulating it with all the correspondence on this ticket so -# far. This should make RT responses threadable. +This routine adds all the random headers that RT wants in a mail message +that don't matter much to anybody else. =cut -sub SetReferences { +sub SetRTSpecialHeaders { my $self = shift; + $self->SetSubject(); + $self->SetSubjectToken(); + $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding ) + if ($RT::EmailOutputEncoding); + $self->SetReturnAddress(); + # 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 @@ -358,46 +435,93 @@ sub SetReferences { # References. $self->SetHeader( 'In-Reply-To', - "TicketObj->id() . "\@" . $RT::rtname . ">" ); + "TicketObj->id() . "\@" . $RT::rtname . ">" ); # TODO We should always add References headers for all message-ids # of previous messages related to this ticket. + + $self->SetHeader( 'Message-ID', + "TicketObj->id() . "-" + . $self->TransactionObj->id() . "-" + . $self->ScripObj->Id . "." + . rand(20) . "\@" + . $RT::Organization . ">" ) + unless $self->TemplateObj->MIMEObj->head->get('Message-ID'); + + $self->SetHeader( 'Precedence', "bulk" ) + unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ); + + $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 ); + } # }}} -# {{{ sub SetMessageID -=head2 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. +# {{{ RemoveInappropriateRecipients + +=head2 RemoveInappropriateRecipients + +Remove addresses that are RT addresses or that are on this transaction's blacklist =cut -sub SetMessageID { +sub RemoveInappropriateRecipients { my $self = shift; - # 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#" + my @blacklist; - $self->SetHeader( 'Message-ID', - "TicketObj->id() . "-" - . $self->TransactionObj->id() . "." - . rand(20) . "\@" - . $RT::Organization . ">" ) - unless $self->TemplateObj->MIMEObj->head->get('Message-ID'); -} + # 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->Attachments->First() ) { + my $squelch = + $self->TransactionObj->Attachments->First->GetHeader( + 'RT-Squelch-Replies-To'); + + if ($squelch) { + @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; + } + + # 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'} } ); + } +} + +# }}} # {{{ sub SetReturnAddress =head2 SetReturnAddress is_comment => BOOLEAN @@ -409,8 +533,10 @@ Calculate and set From and Reply-To headers based on the is_comment flag. sub SetReturnAddress { my $self = shift; - my %args = ( is_comment => 0, - @_ ); + my %args = ( + is_comment => 0, + @_ + ); # From and Reply-To # $args{is_comment} should be set if the comment address is to be used. @@ -426,21 +552,26 @@ sub SetReturnAddress { } 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 ); - } + 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') ) { @@ -473,82 +604,6 @@ sub SetHeader { # }}} -# {{{ sub SetRecipients - -=head2 SetRecipients - -Dummy method to be overriden by subclasses which want to set the recipients. - -=cut - -sub SetRecipients { - my $self = shift; - 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 $addresses = shift; - return $self->SetHeader( 'To', $addresses ); -} - -# }}} - -# {{{ sub SetCc - -=head2 SetCc - -Takes a string that is the addresses you want to Cc - -=cut - -sub SetCc { - my $self = shift; - my $addresses = shift; - - return $self->SetHeader( 'Cc', $addresses ); -} - -# }}} - -# {{{ sub SetBcc - -=head2 SetBcc - -Takes a string that is the addresses you want to Bcc - -=cut - -sub SetBcc { - my $self = shift; - my $addresses = shift; - - return $self->SetHeader( 'Bcc', $addresses ); -} - -# }}} - -# {{{ sub SetPrecedence - -sub SetPrecedence { - my $self = shift; - - unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ) { - $self->SetHeader( 'Precedence', "bulk" ); - } -} - -# }}} # {{{ sub SetSubject @@ -564,36 +619,33 @@ sub SetSubject { my $self = shift; my $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(); - } - + my $message = $self->TransactionObj->Attachments; + if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) { + return (); + } + 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(); } - $subject =~ s/(\r\n|\n|\s)/ /gi; + } + else { + $subject = $self->TicketObj->Subject(); + } + + $subject =~ s/(\r\n|\n|\s)/ /gi; - chomp $subject; - $self->SetHeader( 'Subject', $subject ); + chomp $subject; + $self->SetHeader( 'Subject', $subject ); - } - return ($subject); } # }}} @@ -621,7 +673,7 @@ sub SetSubjectToken { # }}} -# {{{ +# {{{ SetHeadingAsEncoding =head2 SetHeaderAsEncoding($field_name, $charset_encoding) @@ -652,7 +704,7 @@ sub SetHeaderAsEncoding { } # }}} -# {{{ MIMENcodeString +# {{{ MIMEEncodeString =head2 MIMEEncodeString STRING ENCODING diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm index 515eeb58c..d3272a024 100644 --- a/rt/lib/RT/Action/SetPriority.pm +++ b/rt/lib/RT/Action/SetPriority.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK package RT::Action::SetPriority; require RT::Action::Generic; diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm index e2e3d72ce..c298a7c7f 100644 --- a/rt/lib/RT/Action/UserDefined.pm +++ b/rt/lib/RT/Action/UserDefined.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (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 @@ -14,13 +20,29 @@ # 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. +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# +# 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 LICENSE BLOCK +# }}} END BPS TAGGED BLOCK package RT::Action::UserDefined; -- cgit v1.2.1 From d4d0590bef31071e8809ec046717444b95b3f30a Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 15 Oct 2005 09:11:20 +0000 Subject: import rt 3.4.4 --- rt/lib/RT/Action/AutoOpen.pm | 8 +- rt/lib/RT/Action/Autoreply.pm | 6 +- rt/lib/RT/Action/CreateTickets.pm | 29 ++-- rt/lib/RT/Action/EscalatePriority.pm | 7 +- rt/lib/RT/Action/Generic.pm | 58 ++++---- rt/lib/RT/Action/Notify.pm | 12 +- rt/lib/RT/Action/NotifyAsComment.pm | 6 +- rt/lib/RT/Action/RecordComment.pm | 30 +---- rt/lib/RT/Action/RecordCorrespondence.pm | 30 +---- rt/lib/RT/Action/ResolveMembers.pm | 6 +- rt/lib/RT/Action/SendEmail.pm | 223 +++++++++++++++++++++++++------ rt/lib/RT/Action/SetPriority.pm | 6 +- rt/lib/RT/Action/UserDefined.pm | 6 +- 13 files changed, 266 insertions(+), 161 deletions(-) (limited to 'rt/lib/RT/Action') diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm index b28c50d3e..3423db93e 100644 --- a/rt/lib/RT/Action/AutoOpen.pm +++ b/rt/lib/RT/Action/AutoOpen.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} # This Action will open the BASE if a dependent is resolved. package RT::Action::AutoOpen; @@ -74,6 +74,8 @@ sub Prepare { if ( ( $self->TicketObj->Status eq 'open' ) || ( ( $self->TicketObj->Status eq 'new' ) && $self->TransactionObj->IsInbound ) + || ( defined $self->TransactionObj->Message->First + && $self->TransactionObj->Message->First->GetHeader('RT-Control') =~ /\bno-autoopen\b/i ) ) { return undef; diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm index 6d2894f54..c1ac5f897 100755 --- a/rt/lib/RT/Action/Autoreply.pm +++ b/rt/lib/RT/Action/Autoreply.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} package RT::Action::Autoreply; require RT::Action::SendEmail; diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm index 68f402e4e..b708f2ea5 100644 --- a/rt/lib/RT/Action/CreateTickets.pm +++ b/rt/lib/RT/Action/CreateTickets.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} package RT::Action::CreateTickets; require RT::Action::Generic; @@ -148,8 +148,8 @@ A convoluted example push (@admins, $admin->EmailAddress); } } - Queue: Approvals - Type: Approval + Queue: ___Approvals + Type: approval AdminCc: {join ("\nAdminCc: ",@admins) } Depended-On-By: TOP Refers-To: TOP @@ -164,7 +164,7 @@ A convoluted example Subject: Manager approval Depended-On-By: TOP Refers-On: {$Tickets{"approval"}->Id} - Queue: Approvals + Queue: ___Approvals Content-Type: text/plain Content: Your approval is requred for this ticket, too. @@ -213,7 +213,7 @@ A complete list of acceptable fields for this beastie: Fields marked with an * are required. -Fields marked with a + man have multiple values, simply +Fields marked with a + may 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 @@ -243,8 +243,8 @@ ok ($approvalsq->Id, "Created Approvals test queue"); my $approvals = '===Create-Ticket: approval -Queue: Approvals -Type: Approval +Queue: ___Approvals +Type: approval AdminCc: {join ("\nAdminCc: ",@admins) } Depended-On-By: {$Tickets{"TOP"}->Id} Refers-To: TOP @@ -258,7 +258,7 @@ ENDOFCONTENT ===Create-Ticket: two Subject: Manager approval. Depended-On-By: approval -Queue: Approvals +Queue: ___Approvals Content-Type: text/plain Content: Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that? @@ -565,12 +565,14 @@ sub CreateByTemplate { # XXX: cargo cult programming that works. i'll be back. use bytes; - %T::Tickets = (); + local %T::Tickets = %T::Tickets; + local $T::TOP = $T::TOP; + local $T::ID = $T::ID; + $T::Tickets{'TOP'} = $T::TOP = $top if $top; my $ticketargs; my ( @links, @postponed ); foreach my $template_id ( @{ $self->{'create_tickets'} } ) { - $T::Tickets{'TOP'} = $T::TOP = $top if $top; $RT::Logger->debug("Workflow: processing $template_id of $T::TOP") if $T::TOP; @@ -628,7 +630,8 @@ sub UpdateByTemplate { use bytes; my @results; - %T::Tickets = (); + local %T::Tickets = %T::Tickets; + local $T::ID = $T::ID; my $ticketargs; my ( @links, @postponed ); diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm index ace72dd1b..c54992166 100644 --- a/rt/lib/RT/Action/EscalatePriority.pm +++ b/rt/lib/RT/Action/EscalatePriority.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,8 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} + =head1 NAME RT::Action::EscalatePriority diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm index 5e80f40d7..cf7600a63 100755 --- a/rt/lib/RT/Action/Generic.pm +++ b/rt/lib/RT/Action/Generic.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,8 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} + =head1 NAME RT::Action::Generic - a generic baseclass for RT Actions @@ -84,31 +85,31 @@ sub new { # {{{ sub _Init sub _Init { my $self = shift; - my %args = ( TransactionObj => undef, - TicketObj => undef, - ScripObj => undef, - TemplateObj => undef, - Argument => undef, - Type => undef, - CurrentUser => undef, - @_ ); - - + 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->{'TemplateObj'} = $args{'TemplateObj'}; $self->{'Type'} = $args{'Type'}; - $self->CurrentUser( $args{'CurrentUser'}); - Scalar::Util::weaken($self->{'ScripObj'}); - Scalar::Util::weaken($self->{'TicketObj'}); - Scalar::Util::weaken($self->{'TemplateObj'}); - Scalar::Util::weaken($self->{'TransactionObj'}); - - - + 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'}); } # }}} @@ -150,6 +151,13 @@ sub ScripObj { } # }}} +# {{{ sub ScripActionObj +sub ScripActionObj { + my $self = shift; + return($self->{'ScripActionObj'}); +} +# }}} + # {{{ sub Type sub Type { my $self = shift; @@ -205,13 +213,11 @@ sub DESTROY { # 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; - $self->{'ScripObj'} = undef; - - - } # }}} diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm index 4131a8c68..0daaa5586 100755 --- a/rt/lib/RT/Action/Notify.pm +++ b/rt/lib/RT/Action/Notify.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} # package RT::Action::Notify; require RT::Action::SendEmail; @@ -158,9 +158,9 @@ sub SetRecipients { @{ $self->{'Bcc'} } = @Bcc; } else { - @{ $self->{'To'} } = grep ( !/^$creator$/, @To ); - @{ $self->{'Cc'} } = grep ( !/^$creator$/, @Cc ); - @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc ); + @{ $self->{'To'} } = grep ( lc $_ ne lc $creator, @To ); + @{ $self->{'Cc'} } = grep ( lc $_ ne lc $creator, @Cc ); + @{ $self->{'Bcc'} } = grep ( lc $_ ne lc $creator, @Bcc ); } @{ $self->{'PseudoTo'} } = @PseudoTo; diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm index 475e3555f..f7cc87543 100755 --- a/rt/lib/RT/Action/NotifyAsComment.pm +++ b/rt/lib/RT/Action/NotifyAsComment.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} package RT::Action::NotifyAsComment; require RT::Action::Notify; diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm index b548a26eb..d9740dcd1 100644 --- a/rt/lib/RT/Action/RecordComment.pm +++ b/rt/lib/RT/Action/RecordComment.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,31 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent -# -# (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 -# +# END BPS TAGGED BLOCK }}} package RT::Action::RecordComment; require RT::Action::Generic; use strict; diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm index c6770c574..62255ec53 100644 --- a/rt/lib/RT/Action/RecordCorrespondence.pm +++ b/rt/lib/RT/Action/RecordCorrespondence.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,31 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent -# -# (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 -# +# END BPS TAGGED BLOCK }}} package RT::Action::RecordCorrespondence; require RT::Action::Generic; use strict; diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm index 4d751eb6d..00813181c 100644 --- a/rt/lib/RT/Action/ResolveMembers.pm +++ b/rt/lib/RT/Action/ResolveMembers.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} # This Action will resolve all members of a resolved group ticket package RT::Action::ResolveMembers; diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm index a85c169b8..3c70dc401 100755 --- a/rt/lib/RT/Action/SendEmail.pm +++ b/rt/lib/RT/Action/SendEmail.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} # Portions Copyright 2000 Tobias Brox package RT::Action::SendEmail; @@ -150,6 +150,10 @@ sub Prepare { # try to convert message body from utf-8 to $RT::EmailOutputEncoding $self->SetHeader( 'Content-Type', 'text/plain; charset="utf-8"' ); + # fsck.com #5959: Since RT sends 8bit mail, we should say so. + $self->SetHeader( 'Content-Transfer-Encoding','8bit'); + + RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' ); $self->SetHeader( 'Content-Type', 'text/plain; charset="' . $RT::EmailOutputEncoding . '"' ); @@ -225,9 +229,11 @@ sub SendMessage { my $self = shift; my $MIMEObj = shift; - my $msgid = $MIMEObj->head->get('Message-Id'); + my $msgid = $MIMEObj->head->get('Message-ID'); chomp $msgid; + $self->ScripActionObj->{_Message_ID}++; + $RT::Logger->info( $msgid . " #" . $self->TicketObj->id . "/" . $self->TransactionObj->id @@ -394,12 +400,13 @@ sub RecordOutgoingMailTransaction { $type = 'EmailRecord'; } + my $msgid = $MIMEObj->head->get('Message-ID'); + chomp $msgid; - my ( $id, $msg ) = $transaction->Create( Ticket => $self->TicketObj->Id, Type => $type, - Data => $MIMEObj->head->get('Message-Id'), + Data => $msgid, MIMEObj => $MIMEObj, ActivateScrips => 0 ); @@ -427,28 +434,38 @@ sub SetRTSpecialHeaders { $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding ) if ($RT::EmailOutputEncoding); $self->SetReturnAddress(); - - # 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', - "TicketObj->id() . "\@" . $RT::rtname . ">" ); - - # TODO We should always add References headers for all message-ids - # of previous messages related to this ticket. - - $self->SetHeader( 'Message-ID', - "TicketObj->id() . "-" - . $self->TransactionObj->id() . "-" - . $self->ScripObj->Id . "." - . rand(20) . "\@" - . $RT::Organization . ">" ) - unless $self->TemplateObj->MIMEObj->head->get('Message-ID'); + $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; + + # 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+-0-0)\@$RT::Organization>$/ + "<$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', + "TicketObj->id . "-" + . $self->ScripObj->id . "-" # Scrip + . $self->ScripActionObj->{_Message_ID} . "@" # Email sent + . $RT::Organization + . ">" ); + } + } $self->SetHeader( 'Precedence', "bulk" ) unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ); @@ -482,28 +499,67 @@ sub RemoveInappropriateRecipients { my @blacklist; + my @types = qw/To Cc Bcc/; + # 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'} } ); + foreach my $type (@types) { + @{ $self->{$type} } = + RT::EmailParser::CullRTAddresses( "", @{ $self->{$type} } ); + } # 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->Attachments->First() ) { + if ( $self->TransactionObj->Attachments->First() ) { + if ( + $self->TransactionObj->Attachments->First->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 ) { + + # Don't send to any watchers. + @{ $self->{'To'} } = (); + @{ $self->{'Cc'} } = (); + @{ $self->{'Bcc'} } = (); + + } + elsif ( $RT::RedistributeAutoGeneratedMessages eq 'privileged' ) { + + # Only send to "privileged" watchers. + # + + foreach my $type (@types) { + + 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 ); + + } + } + + } + + } + my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To'); if ($squelch) { - @blacklist = split ( /,/, $squelch ); + @blacklist = split( /,/, $squelch ); } } -# Let's grab the SquelchMailTo attribue and push those entries into the @blacklist + # 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; @@ -514,10 +570,10 @@ sub RemoveInappropriateRecipients { 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'} } ); + foreach my $type (@types) { + @{ $self->{$type} } = + grep ( !/^\Q$person_to_yank\E$/, @{ $self->{$type} } ); + } } } @@ -671,8 +727,93 @@ sub SetSubjectToken { # }}} +=head2 SetReferencesHeaders + +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 { + return (undef); + } + + # There are two main cases -- this transaction was created with + # 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>/) { + + # 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>$/ + "<$1." . $self->TicketObj->id . + "-" . $self->ScripObj->id . + "-" . $self->ScripActionObj->{_Message_ID} . + "@" . $RT::Organization . ">"/eg + } + + # 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; + + # 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; + + # 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); + + # 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); + + # Add on the 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 + +=cut + +sub PseudoReference { + + my $self = shift; + my $pseudo_ref = 'TicketObj->id .'@'.$RT::Organization .'>'; + return $pseudo_ref; +} + + # {{{ SetHeadingAsEncoding =head2 SetHeaderAsEncoding($field_name, $charset_encoding) diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm index d3272a024..c129bf0a2 100644 --- a/rt/lib/RT/Action/SetPriority.pm +++ b/rt/lib/RT/Action/SetPriority.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} package RT::Action::SetPriority; require RT::Action::Generic; diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm index c298a7c7f..90653f68a 100644 --- a/rt/lib/RT/Action/UserDefined.pm +++ b/rt/lib/RT/Action/UserDefined.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,7 +42,7 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} package RT::Action::UserDefined; -- cgit v1.2.1