]> git.uio.no Git - usit-rt.git/blame - sbin/rt-email-digest
Merge branch 'master' of git.uio.no:usit-rt
[usit-rt.git] / sbin / rt-email-digest
CommitLineData
84fb5b46
MKG
1#!/usr/bin/perl
2# BEGIN BPS TAGGED BLOCK {{{
3#
4# COPYRIGHT:
5#
6# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
7# <sales@bestpractical.com>
8#
9# (Except where explicitly superseded by other copyright notices)
10#
11#
12# LICENSE:
13#
14# This work is made available to you under the terms of Version 2 of
15# the GNU General Public License. A copy of that license should have
16# been provided with this software, but in any event can be snarfed
17# from www.gnu.org.
18#
19# This work is distributed in the hope that it will be useful, but
20# WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22# General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program; if not, write to the Free Software
26# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27# 02110-1301 or visit their web page on the internet at
28# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29#
30#
31# CONTRIBUTION SUBMISSION POLICY:
32#
33# (The following paragraph is not intended to limit the rights granted
34# to you to modify and distribute this software under the terms of
35# the GNU General Public License and is only of importance to you if
36# you choose to contribute your changes and enhancements to the
37# community by submitting them to Best Practical Solutions, LLC.)
38#
39# By intentionally submitting any modifications, corrections or
40# derivatives to this work, or any other work intended for use with
41# Request Tracker, to Best Practical Solutions, LLC, you confirm that
42# you are the copyright holder for those contributions and you grant
43# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44# royalty-free, perpetual, license to use, copy, create derivative
45# works based on those contributions, and sublicense and distribute
46# those contributions and any derivatives thereof.
47#
48# END BPS TAGGED BLOCK }}}
49use warnings;
50use strict;
51
52BEGIN {
53 require File::Spec;
54 my @libs = ("lib", "local/lib");
55 my $bin_path;
56
57 for my $lib (@libs) {
58 unless ( File::Spec->file_name_is_absolute($lib) ) {
59 unless ($bin_path) {
60 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
61 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
62 }
63 else {
64 require FindBin;
65 no warnings "once";
66 $bin_path = $FindBin::Bin;
67 }
68 }
69 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
70 }
71 unshift @INC, $lib;
72 }
73
74}
75
76use Date::Format qw( strftime );
77use Getopt::Long;
78use RT;
79use RT::Interface::CLI qw( CleanEnv loc );
80use RT::Interface::Email;
81
82CleanEnv();
83RT::LoadConfig();
84RT::Init();
85
86sub usage {
87 my ($error) = @_;
88 print loc("Usage:") . " $0 -m (daily|weekly) [--print] [--help]\n";
89 print loc(
90 "[_1] is a utility, meant to be run from cron, that dispatches all deferred RT notifications as a per-user digest.",
91 $0
92 ) . "\n";
93 print "\n\t-m, --mode\t"
94 . loc("Specify whether this is a daily or weekly run.") . "\n";
95 print "\t-p, --print\t"
96 . loc("Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent")
97 . "\n";
98 print "\t-h, --help\t" . loc("Print this message") . "\n";
99
100 if ( $error eq 'help' ) {
101 exit 0;
102 } else {
103 print loc("Error") . ": " . loc($error) . "\n";
104 exit 1;
105 }
106}
107
108my ( $frequency, $print, $help ) = ( '', '', '' );
109GetOptions(
110 'mode=s' => \$frequency,
111 'print' => \$print,
112 'help' => \$help,
113);
114
115usage('help') if $help;
116usage("Mode argument must be 'daily' or 'weekly'")
117 unless $frequency =~ /^(daily|weekly)$/;
118
119run( $frequency, $print );
120
121sub run {
122 my $frequency = shift;
123 my $print = shift;
124
125## Find all the tickets that have been modified within the time frame
126## described by $frequency.
127
128 my ( $all_digest, $sent_transactions ) = find_transactions($frequency);
129
130## Iterate through our huge hash constructing the digest message
131## for each user and sending it.
132
133 foreach my $user ( keys %$all_digest ) {
134 my ( $contents_list, $contents_body ) = build_digest_for_user( $user, $all_digest->{$user} );
135 # Now we have a content head and a content body. We can send a message.
136 if ( send_digest( $user, $contents_list, $contents_body ) ) {
137 print "Sent message to $user\n";
138 mark_transactions_sent( $frequency, $user, values %{$sent_transactions->{$user}} ) unless ($print);
139 } else {
140 print "Failed to send message to $user\n";
141 }
142 }
143}
144exit 0;
145
146# Subroutines.
147
148sub send_digest {
149 my ( $to, $index, $messages ) = @_;
150
151 # Combine the index and the messages.
152
153 my $body = "============== Tickets with activity in the last "
154 . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n";
155
156 $body .= $index;
157 $body .= "\n\n============== Messages recorded in the last "
158 . ( $frequency eq 'daily' ? "day" : "seven days" ) . "\n\n";
159 $body .= $messages;
160
161 # Load our template. If we cannot load the template, abort
162 # immediately rather than failing through many loops.
163 my $digest_template = RT::Template->new( RT->SystemUser );
164 my ( $ret, $msg ) = $digest_template->Load('Email Digest');
165 unless ($ret) {
166 print loc("Failed to load template")
167 . " 'Email Digest': "
168 . $msg
169 . ". Cannot continue.\n";
170 exit 1;
171 }
172 ( $ret, $msg ) = $digest_template->Parse( Argument => $body );
173 unless ($ret) {
174 print loc("Failed to parse template")
175 . " 'Email Digest'. Cannot continue.\n";
176 exit 1;
177 }
178
179 # Set our sender and recipient.
180 $digest_template->MIMEObj->head->replace( 'From', RT::Config->Get('CorrespondAddress') );
181 $digest_template->MIMEObj->head->replace( 'To', $to );
182
183 if ($print) {
184 $digest_template->MIMEObj->print;
185 return 1;
186 } else {
187 return RT::Interface::Email::SendEmail( Entity => $digest_template->MIMEObj)
188 }
189}
190
191# =item mark_transactions_sent( $frequency, $user, @txn_list );
192#
193# Takes a frequency string (either 'daily' or 'weekly'), a user and one or more
194# transaction objects as its arguments. Marks the given deferred
195# notifications as sent.
196#
197# =cut
198
199sub mark_transactions_sent {
200 my ( $freq, $user, @txns ) = @_;
201 return unless $freq =~ /(daily|weekly)/;
202 return unless @txns;
203 foreach my $txn (@txns) {
204
205 # Grab the attribute, mark the "sent" as true, and store the new
206 # value.
207 if ( my $attr = $txn->FirstAttribute('DeferredRecipients') ) {
208 my $deferred = $attr->Content;
209 $deferred->{$freq}->{$user}->{'_sent'} = 1;
210 $txn->SetAttribute(
211 Name => 'DeferredRecipients',
212 Description => 'Deferred recipients for this message',
213 Content => $deferred,
214 );
215 }
216 }
217}
218
219sub since_date {
220 my $frequency = shift;
221
222 # Specify a short time for digest overlap, in case we aren't starting
223 # this process exactly on time.
224 my $OVERLAP_HEDGE = -30;
225
226 my $since_date = RT::Date->new( RT->SystemUser );
227 $since_date->Set( Format => 'unix', Value => time() );
228 if ( $frequency eq 'daily' ) {
229 $since_date->AddDays(-1);
230 } else {
231 $since_date->AddDays(-7);
232 }
233
234 $since_date->AddSeconds($OVERLAP_HEDGE);
235
236 return $since_date;
237}
238
239sub find_transactions {
240 my $frequency = shift;
241 my $since_date = since_date($frequency);
242
243 my $txns = RT::Transactions->new( RT->SystemUser );
244
245 # First limit to recent transactions.
246 $txns->Limit(
247 FIELD => 'Created',
248 OPERATOR => '>',
249 VALUE => $since_date->ISO
250 );
251
252 # Next limit to ticket transactions.
253 $txns->Limit(
254 FIELD => 'ObjectType',
255 OPERATOR => '=',
256 VALUE => 'RT::Ticket',
257 ENTRYAGGREGATOR => 'AND'
258 );
259 my $all_digest = {};
260 my $sent_transactions = {};
261
262 while ( my $txn = $txns->Next ) {
263 my $ticket = $txn->Ticket;
264 my $queue = $txn->TicketObj->QueueObj->Name;
265 # Xxx todo - may clobber if two queues have the same name
266 foreach my $user ( $txn->DeferredRecipients($frequency) ) {
267 $all_digest->{$user}->{$queue}->{$ticket}->{ $txn->id } = $txn->ContentObj;
268 $sent_transactions->{$user}->{ $txn->id } = $txn;
269 }
270 }
271
272 return ( $all_digest, $sent_transactions );
273}
274
275sub build_digest_for_user {
276 my $user = shift;
277 my $user_digest = shift;
278
279 my $contents_list = ''; # Holds the digest index.
280 my $contents_body = ''; # Holds the digest body.
281
282 # Has the user been disabled since a message was deferred on his/her
283 # behalf?
284 my $user_obj = RT::User->new( RT->SystemUser );
285 $user_obj->LoadByEmail($user);
286 if ( $user_obj->PrincipalObj->Disabled ) {
287 print STDERR loc("Skipping disabled user") . " $user\n";
288 next;
289 }
290
291 print loc("Message for user") . " $user:\n\n" if $print;
292 foreach my $queue ( keys %$user_digest ) {
293 $contents_list .= "Queue $queue:\n";
294 $contents_body .= "Queue $queue:\n";
295 foreach my $ticket ( sort keys %{ $user_digest->{$queue} } ) {
296 my $tkt_txns = $user_digest->{$queue}->{$ticket};
297 my $ticket_obj = RT::Ticket->new( RT->SystemUser );
298 $ticket_obj->Load($ticket);
299
300 # Spit out the index entry for this ticket.
301 my $ticket_title = sprintf(
302 "#%d %s [%s]\t%s\n",
303 $ticket, $ticket_obj->Status, $ticket_obj->OwnerObj->Name,
304 $ticket_obj->Subject
305 );
306 $contents_list .= $ticket_title;
307
308 # Spit out the messages for the transactions on this ticket.
309 $contents_body .= "\n== $ticket_title\n";
310 foreach my $txn ( sort keys %$tkt_txns ) {
311 my $msg = $tkt_txns->{$txn};
312
313 # $msg contains an RT::Attachment with our outgoing
314 # message. Print a few headers for clarity's sake.
315 $contents_body .= "From: " . $msg->GetHeader('From') . "\n";
316 my $date = $msg->GetHeader('Date ');
317 unless ($date) {
318 my $txn_obj = RT::Transaction->new( RT->SystemUser );
319 $txn_obj->Load($txn);
320 my $date_obj = RT::Date->new( RT->SystemUser );
321 $date_obj->Set(
322 Format => 'sql',
323 Value => $txn_obj->Created
324 );
325 $date = strftime( '%a, %d %b %Y %H:%M:%S %z',
326 @{ [ localtime( $date_obj->Unix ) ] } );
327 }
328 $contents_body .= "Date: $date\n\n";
329 $contents_body .= $msg->Content . "\n";
330 $contents_body .= "-------\n";
331 } # foreach transaction
332 } # foreach ticket
333 } # foreach queue
334
335 return ( $contents_list, $contents_body );
336
337}
338
339__END__
340
341=head1 NAME
342
343rt-email-digest - dispatch deferred notifications as a per-user digest
344
345=head1 SYNOPSIS
346
347 rt-email-digest -m (daily|weekly) [--print] [--help]
348
349=head1 DESCRIPTION
350
351This script is a tool to dispatch all deferred RT notifications as a per-user
352object.
353
354=head1 OPTIONS
355
356=over
357
358=item mode
359
360Specify whether this is a daily or weekly run.
361
362--mode is equal to -m
363
364=item print
365
366Print the resulting digest messages to STDOUT; don't mail them. Do not mark them as sent
367
368--print is equal to -p
369
370=item help
371
372Print this message
373
374--help is equal to -h
375
376=back