diff options
Diffstat (limited to 'rt/sbin/rt-email-digest.in')
-rw-r--r-- | rt/sbin/rt-email-digest.in | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/rt/sbin/rt-email-digest.in b/rt/sbin/rt-email-digest.in new file mode 100644 index 000000000..2fc7c0089 --- /dev/null +++ b/rt/sbin/rt-email-digest.in @@ -0,0 +1,337 @@ +#!@PERL@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# <jesse@bestpractical.com> +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} +use warnings; +use strict; + +BEGIN { + require File::Spec; + my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + +} + +use Date::Format qw( strftime ); +use Getopt::Long; +use RT; +use RT::Interface::CLI qw( CleanEnv loc ); +use RT::Interface::Email; + +CleanEnv(); +RT::LoadConfig(); +RT::Init(); + +sub usage { + my ($error) = @_; + print loc("Usage: ") . "$0 -m (daily|weekly) [--print] [--help]\n"; + print loc( + "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.", + $0 + ) . "\n"; + print "\n\t-m, --mode\t" + . loc("Specify whether this is a daily or weekly run.") . "\n"; + print "\t-p, --print\t" + . loc("Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent") + . "\n"; + print "\t-h, --help\t" . loc("Print this message") . "\n"; + + if ( $error eq 'help' ) { + exit 0; + } else { + print loc("Error") . ": " . loc($error) . "\n"; + exit 1; + } +} + +my ( $frequency, $print, $help ) = ( '', '', '' ); +GetOptions( + 'mode=s' => \$frequency, + 'print' => \$print, + 'help' => \$help, +); + +usage('help') if $help; +usage("Mode argument must be 'daily' or 'weekly'") + unless $frequency =~ /^(daily|weekly)$/; + +run( $frequency, $print ); + +sub run { + my $frequency = shift; + my $print = shift; + +## Find all the tickets that have been modified within the time frame +## described by $frequency. + + my ( $all_digest, $sent_transactions ) = find_transactions($frequency); + +## Iterate through our huge hash constructing the digest message +## for each user and sending it. + + foreach my $user ( keys %$all_digest ) { + my ( $contents_list, $contents_body ) = build_digest_for_user( $user, $all_digest->{$user} ); + # Now we have a content head and a content body. We can send a message. + if ( send_digest( $user, $contents_list, $contents_body ) ) { + print "Sent message to $user\n"; + mark_transactions_sent( $frequency, $user, values %{$sent_transactions->{$user}} ) unless ($print); + } else { + print "Failed to send message to $user\n"; + } + } +} +exit 0; + +# Subroutines. + +sub send_digest { + my ( $to, $index, $messages ) = @_; + + # Combine the index and the messages. + + my $body = "============== Tickets with activity in the last " + . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n"; + + $body .= $index; + $body .= "\n\n============== Messages recorded in the last " + . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n"; + $body .= $messages; + + # Load our template. If we cannot load the template, abort + # immediately rather than failing through many loops. + my $digest_template = RT::Template->new( RT->SystemUser ); + my ( $ret, $msg ) = $digest_template->Load('Email Digest'); + unless ($ret) { + print loc("Failed to load template") + . " 'Email Digest': " + . $msg + . ". Cannot continue.\n"; + exit 1; + } + ( $ret, $msg ) = $digest_template->Parse( Argument => $body ); + unless ($ret) { + print loc("Failed to parse template") + . " 'Email Digest'. Cannot continue.\n"; + exit 1; + } + + # Set our sender and recipient. + $digest_template->MIMEObj->head->replace( 'From', RT::Config->Get('CorrespondAddress') ); + $digest_template->MIMEObj->head->replace( 'To', $to ); + + if ($print) { + $digest_template->MIMEObj->print; + return 1; + } else { + return RT::Interface::Email::SendEmail( Entity => $digest_template->MIMEObj) + } +} + +=item mark_transactions_sent( $frequency, $user, @txn_list ); + +Takes a frequency string (either 'daily' or 'weekly'), a user and one or more +transaction objects as its arguments. Marks the given deferred +notifications as sent. + +=cut + +sub mark_transactions_sent { + my ( $freq, $user, @txns ) = @_; + return unless $freq =~ /(daily|weekly)/; + return unless @txns; + foreach my $txn (@txns) { + + # Grab the attribute, mark the "sent" as true, and store the new + # value. + if ( my $attr = $txn->FirstAttribute('DeferredRecipients') ) { + my $deferred = $attr->Content; + $deferred->{$freq}->{$user}->{'_sent'} = 1; + $txn->SetAttribute( + Name => 'DeferredRecipients', + Description => 'Deferred recipients for this message', + Content => $deferred, + ); + } + } +} + +sub since_date { + my $frequency = shift; + + # Specify a short time for digest overlap, in case we aren't starting + # this process exactly on time. + my $OVERLAP_HEDGE = -30; + + my $since_date = RT::Date->new( RT->SystemUser ); + $since_date->Set( Format => 'unix', Value => time() ); + if ( $frequency eq 'daily' ) { + $since_date->AddDays(-1); + } else { + $since_date->AddDays(-7); + } + + $since_date->AddSeconds($OVERLAP_HEDGE); + + return $since_date; +} + +sub find_transactions { + my $frequency = shift; + my $since_date = since_date($frequency); + + my $txns = RT::Transactions->new( RT->SystemUser ); + + # First limit to recent transactions. + $txns->Limit( + FIELD => 'Created', + OPERATOR => '>', + VALUE => $since_date->ISO + ); + + # Next limit to ticket transactions. + $txns->Limit( + FIELD => 'ObjectType', + OPERATOR => '=', + VALUE => 'RT::Ticket', + ENTRYAGGREGATOR => 'AND' + ); + my $all_digest = {}; + my $sent_transactions = {}; + + while ( my $txn = $txns->Next ) { + my $ticket = $txn->Ticket; + my $queue = $txn->TicketObj->QueueObj->Name; + # Xxx todo - may clobber if two queues have the same name + foreach my $user ( $txn->DeferredRecipients($frequency) ) { + $all_digest->{$user}->{$queue}->{$ticket}->{ $txn->id } = $txn->ContentObj; + $sent_transactions->{$user}->{ $txn->id } = $txn; + } + } + + return ( $all_digest, $sent_transactions ); +} + +sub build_digest_for_user { + my $user = shift; + my $user_digest = shift; + + my $contents_list = ''; # Holds the digest index. + my $contents_body = ''; # Holds the digest body. + + # Has the user been disabled since a message was deferred on his/her + # behalf? + my $user_obj = RT::User->new( RT->SystemUser ); + $user_obj->LoadByEmail($user); + if ( $user_obj->PrincipalObj->Disabled ) { + print STDERR loc("Skipping disabled user") . " $user\n"; + next; + } + + print loc("Message for user") . " $user:\n\n" if $print; + foreach my $queue ( keys %$user_digest ) { + $contents_list .= "Queue $queue:\n"; + $contents_body .= "Queue $queue:\n"; + foreach my $ticket ( sort keys %{ $user_digest->{$queue} } ) { + my $tkt_txns = $user_digest->{$queue}->{$ticket}; + my $ticket_obj = RT::Ticket->new( RT->SystemUser ); + $ticket_obj->Load($ticket); + + # Spit out the index entry for this ticket. + my $ticket_title = sprintf( + "#%d %s [%s]\t%s\n", + $ticket, $ticket_obj->Status, $ticket_obj->OwnerObj->Name, + $ticket_obj->Subject + ); + $contents_list .= $ticket_title; + + # Spit out the messages for the transactions on this ticket. + $contents_body .= "\n== $ticket_title\n"; + foreach my $txn ( sort keys %$tkt_txns ) { + my $msg = $tkt_txns->{$txn}; + + # $msg contains an RT::Attachment with our outgoing + # message. Print a few headers for clarity's sake. + $contents_body .= "From: " . $msg->GetHeader('From') . "\n"; + my $date = $msg->GetHeader('Date '); + unless ($date) { + my $txn_obj = RT::Transaction->new( RT->SystemUser ); + $txn_obj->Load($txn); + my $date_obj = RT::Date->new( RT->SystemUser ); + $date_obj->Set( + Format => 'sql', + Value => $txn_obj->Created + ); + $date = strftime( '%a, %d %b %Y %H:%M:%S %z', + @{ [ localtime( $date_obj->Unix ) ] } ); + } + $contents_body .= "Date: $date\n\n"; + $contents_body .= $msg->Content . "\n"; + $contents_body .= "-------\n"; + } # foreach transaction + } # foreach ticket + } # foreach queue + + return ( $contents_list, $contents_body ); + +} |