Upgrade to 4.2.8
[usit-rt.git] / local / bin / rt-remind
CommitLineData
84fb5b46
MKG
1#!/usr/bin/perl
2
3# RT reminder script
4#
5# This script will remind people about tickets they have not responded to
6# or dealt with in a reasonable length of time. In its normal mode it will
7# tell ticket owners about tickets which have exceeded a given priority
8# (see the %pri hash below). It also has the ability to tell ticket owners
9# about all their open or new tickets (-a), without or with stalled
10# tickets (-A).
11#
12# This is best used with another script to escalate priorities automatically.
13#
14# The intended use is to run with no flags daily, with the -a flag weekly
15# and with the -A flag monthly. Or similar.
16#
17# Written by T.D.Bishop@kent.ac.uk, July 2003.
18# Updated for multiple queues, March 2004.
19# (using patches sent by jtucker@cyberfuse.com, October 2003)
20# Updated for RT 3.2.x, November 2004.
21# (using patches sent by petter.reinholdtsen@usit.uio.no, August 2004)
22# Updated to only display tickets with a passed or near start date, March 2005.
23# (using patches sent by ray@sysdev.oucs.ox.ac.uk)
24#
25# Copied from <URL:http://www.cs.kent.ac.uk/people/staff/tdb/rtdev/> and
26# modified to fit local needs.
27# - Petter Reinholdtsen 2004-08-31, 2005-05-19
28
29
30### Configuration
31
32# Global mappings of users to addresses
33# This overrides addresses in the RT database
34# The most useful case is setting an address for Nobody
35#
36# Queue specific overrides can be done by using "queue,user"
37
38my(%map) = (
39 'Nobody' => 'RT Nobody <rt-drift@usit.uio.no>',
40 'root' => 'RT root <rt-drift@usit.uio.no>',
41);
42
43my(@queues); # do not delete this
44
45# Queues to operate on
46#@queues = qw( queue1 queue2 );
47
48# Whether to merge the tickets for all queues into one email per person
49# (as opposed to sending an email per person per queue)
50my($mergequeues) = 1;
51
52# Each state can have a priority associated with it.
53# When a ticket in that state passes the given priority it is added to
54# the list to be sent out.
55my(%pri) = (
56 'new' => 40,
57 'open' => 50,
58);
59
60# The length at which lines will be truncated. 80, or thereabouts looks
61# best for most people. Setting to 0 will stop lines being truncated.
62my($linelen) = 78;
63
64# How many days in the future a ticket's start date should be before
65# we ignore it (unless using -A).
66my($futuredays) = 7*24*60*60;
67
68# Location of RT's libs and scripts
69# Remember to change to correct path on current RT instance
3ffc5f4f
MKG
70use lib '/www/data/rt/perl/share/perl5';
71use lib '/www/data/rt/perl/lib/perl5';
72use lib '/www/data/rt/perl/lib64/perl5';
84fb5b46
MKG
73use lib ("/www/var/rt/local/lib", "/www/var/rt/lib");
74
75
76# Address emails should originate from
77my($from) = 'RT reminder <request-tracker@hjelp.uio.no>';
78
79### Code
80
81use strict;
82use Carp;
83use HTML::Entities;
84
85my($showall) = 0;
86my($showmost) = 0;
87
88my $debug = 0; # Enable debugging
89
90# Process command line arguments
91foreach(@ARGV) {
92 if (/^-a$/) {
93 $showmost = 1;
94 }
95 elsif (/^-A$/) {
96 $showall = 1;
97 }
98 elsif (/^-d$/) {
99 $debug = 1;
100 }
101 else {
102 print STDERR "Usage: rt-remind [-a] [-A] [-h]\n";
103 print STDERR " -a show all open or new tickets\n";
104 print STDERR " -A show all open, new, stalled or future tickets\n";
105 print STDERR " -d do not send any emails. debug mode only\n";
106 print STDERR " -h show this help\n";
107 exit 1;
108 }
109}
110
111# Pull in the RT stuff
112package RT;
113use RT::Interface::CLI qw(CleanEnv);
114
115# Clean our the environment
116CleanEnv();
117
118# Load the RT configuration
119RT::LoadConfig();
120
121# Initialise RT
122RT::Init();
123
124# Drop any setgid permissions we might have
125#RT::DropSetGIDPermissions();
126
127use RT::Date;
128use RT::Queue;
129use RT::Queues;
130use RT::Tickets;
131
132# Path to sendmail and flags. Need to set this after RT::Init()
133my($sendmail) = "$RT::SendmailPath $RT::SendmailArguments -t";
134
135my(%TicketStore) = ();
136
137if(!@queues) {
138 my $queues = new RT::Queues($RT::SystemUser);
139 $queues->LimitToEnabled();
140 foreach my $queue (@{$queues->ItemsArrayRef()}) {
141 push @queues, $queue->Name;
142 }
143}
144
145foreach my $queuename (@queues) {
146 # Load in the queue
147 my $queue = new RT::Queue($RT::SystemUser);
148 $queue->Load($queuename);
149
150 my $now = new RT::Date($RT::SystemUser);
151 $now->SetToNow();
152
153 # Get hold of the tickets we're after, sorted by priority
154 my $tickets = new RT::Tickets($RT::SystemUser);
155 $tickets->LimitStatus(VALUE => 'new');
156 $tickets->LimitStatus(VALUE => 'open');
157 $tickets->LimitStatus(VALUE => 'stalled') if $showall;
158 $tickets->LimitQueue(VALUE => $queue->Id);
159 $tickets->OrderBy(FIELD => 'Priority', ORDER => 'DESC');
160 my $user = RT::User->new($RT::SystemUser);
161
162 %TicketStore = () unless $mergequeues;
163
164 # Sort tickets in to lists for each owner
165 while (my $Ticket = $tickets->Next) {
166 $user->Load($Ticket->Owner);
167 my(@emails) = getContacts($Ticket, $user, $queuename);
168
169 # If ticket is over due and owned by someone other than
170 # Nobody, remind the queue owners as well.
171 if ("Nobody" ne $user->Name and
172 $Ticket->DueObj->Unix > 0 and
173 $Ticket->DueObj->Unix < time()) {
174
175 $user->Load("Nobody");
176 push(@emails, getContacts($Ticket, $user, $queuename))
177 }
178 my %gotit = ();
179 for my $address (@emails) {
180
181 push @{$TicketStore{$address}}, $Ticket
182 unless (exists $gotit{$address});
183 $gotit{$address} = 1;
184 }
185 }
186
187 &doMessages($queuename, %TicketStore) unless $mergequeues;
188}
189
190&doMessages("All", %TicketStore) if $mergequeues;
191
192# Disconnect before we finish off
193$RT::Handle->Disconnect();
194exit 0;
195
196sub getGroupMemberAddresses {
197 my ($Ticket, $owner, $groupname) = @_;
198
199 print "Looking up members of group '$groupname' for ".$Ticket->id."\n"
200 if ($debug);
201
202 use RT::Group;
203 my @emails = ();
204 my $Group = new RT::Group($RT::SystemUser);
205 if ($Group->LoadUserDefinedGroup($groupname) &&
206 $Group->MembersObj->Count != 0 ) {
207
208 # Expand recursively all users in the group
209 my $UserMembers = $Group->UserMembersObj;
210
211 while (my $member = $UserMembers->Next()) {
212 my $address = $member->EmailAddress;
213 if ($address) {
214 my $realname = $member->RealName;
215 my $a = "\"$realname\" <$address>";
216 print " Found '$a'\n" if ($debug);
217 push(@emails, $a);
218 }
219 }
220 return join(",", @emails) if @emails;
221 }
222 return undef;
223}
224
225#
226# Look up who to send reminder to for a given ticket. The mail
227# address is constructed based on ticket owner and queue name.
228#
229sub getContacts {
230 my ($Ticket, $owner, $queuename) = @_;
231
232 my($email) = $map{$queuename.",".$owner->Name};
233 print "Using contacts from \$map{$queuename,".$owner->Name."} for ".
234 $Ticket->Id."\n" if ($debug && $email);
235 if ((not defined $email) && ("Nobody" eq $owner->Name)) {
236 # check the <queuename>-owners group if the ticket have no owner
237 $email =
238 getGroupMemberAddresses($Ticket, $owner, "$queuename-owners");
239 print "Using contacts from group '$queuename-owners' for ".
240 $Ticket->Id."\n" if ($debug && $email);
241 }
242 $email = $map{$owner->Name} if not defined $email;
243 if (not defined $email) {
244 my $address = $owner->EmailAddress;
245 if ($address) {
246 my $realname = $owner->RealName;
247 $email = "\"$realname\" <$address>";
248 }
249 }
250 $email = $map{'root'} if not defined $email;
251
252 return split(/\s*,\s*/, $email);
253}
254
255# Subroutine to generate messages
256sub doMessages() {
257 my($queuename, %TicketStore) = @_;
258 foreach my $email (sort keys %TicketStore) {
259 my($t) = $TicketStore{$email};
260 &doOwner($email, $queuename, @$t);
261 }
262}
263
264# Subroutine to generate a messages for a given owner and set of tickets.
265sub doOwner() {
266 my($email, $queuename, @tickets) = @_;
267 my $user = RT::User->new($RT::SystemUser);
268 my $queue = RT::Queue->new($RT::SystemUser);
269 my $subject;
270 if ("All" eq $queuename) {
271 $subject = "Pending tickets in RT";
272 } else {
273 $subject = "Pending tickets for queue '$queuename' in RT";
274 }
275 my($body,$body_html);
276 $body_html .= "<p>";
277
278 if($showall) {
279 $body .= "This is a summary of all open, new or stalled tickets assigned to:\n";
280 $body_html .= "This is a summary of all open, new or stalled tickets assigned to:<br>\n";
281 }
282 elsif($showmost) {
283 $body .= "This is a summary of all open or new tickets assigned to:\n";
284 $body_html .= "This is a summary of all open or new tickets assigned to:<br>\n";
285 }
286 else {
287 $body .= "These tickets are now considered important and are assigned to:\n";
288 $body_html .= "These tickets are now considered important and are assigned to:<br>\n";
289 }
290 $body .= "\n $email\n\n";
291 my $email_html = $email;
292 $email_html =~ s/(\<|\>)//g;
293 $body_html .= "<br>&nbsp;&nbsp;".stripHTML($email)."<br><br>\n";
294 # Generate heading
295 $body .= sprintf "%6s %4s %3s %-8s %-8s %-10.10s %-30s\n", "Id", "Stat", "Pri", "Created", "Owner", "Queue", "Subject";
296 $body_html .= "<\p>";
297 $body_html .= "<table>\n";
298 $body_html .= "<tr style=\"background-color:lightgray\">\n";
299 $body_html .= "<td style=\"text-align:right\">Id</td><td>Stat</td><td>Pri</td><td>Created</td><td>Owner</td><td>Queue</td><td>Subject</td>\n";
300 $body_html .= "</tr>\n";
301
302 # We might not want to print the message if there are no tickets
303 my($printmsg) = 0;
304 # Look through tickets, sorted by priority, highest first
305 foreach my $Ticket (sort {$b->Priority <=> $a->Priority } @tickets) {
306 my($starts) = new RT::Date($RT::SystemUser);
307 $starts->Set(Format => 'sql', Value => $Ticket->Starts);
308 # Only add ticket if it's over the priority, or we're in -a or -A
309 # AND the ticket start date has passed or is in the next
310 # $futuredays days, or we're in -A
311 if(($Ticket->Priority > $pri{$Ticket->Status} || $showmost || $showall)
312 && ($starts->Diff(time) <= $futuredays || $showall)) {
313 $printmsg = 1;
314 # Use our own date formatting routine
315 my($date) = &formatDate($Ticket->CreatedObj->Unix);
316 $user->Load($Ticket->Owner);
317 $queue->Load($Ticket->Queue);
318 my($line) = sprintf "%6d %-4s %-3d %8s %-8s %-10.10s %-30s", $Ticket->Id, $Ticket->Status, $Ticket->Priority, $date, $user->Name, $queue->Name, $Ticket->Subject;
319 my($line_html) = sprintf "<td><a href=$RT::WebURL%d>%d</a></td><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>", $Ticket->Id, $Ticket->Id, $Ticket->Status, $Ticket->Priority, $date, $user->Name, $queue->Name, $Ticket->Subject;
320 # Truncate lines if required
321 if($linelen) {
322 $line = substr($line, 0, $linelen);
323 }
324 $body .= "$line\n";
325 $body_html .= "<tr>\n";
326 $body_html .= "$line_html\n";
327 $body_html .= "</tr>\n";
328 }
329 }
330 $body_html .= "</table>";
331
332 my $boundry = "===".time()."===";
333 my ($msg);
334 $msg .= "From: $from\n";
335 $msg .= "To: $email\n";
336 $msg .= "Subject: $subject\n";
337 $msg .= "MIME-Version: 1.0\n";
338 $msg .= "Content-Type: multipart/alternative; boundary=\"$boundry\"\n";
339 $msg .= "\n";
340 $msg .= "Dersom du kan lese dette taklar ikkje epostlesaren din rfc2046.\n\n";
341 $msg .= "--$boundry\n";
342 $msg .= "Content-Type: text/plain; charset=\"iso-8859-1\"\n\n";
343 $msg .= "$body\n";
344 $msg .= "$RT::WebURL\n";
345 $msg .= "--$boundry\n";
346 $msg .= "Content-Type: text/html; charset=\"iso-8859-1\"\n\n";
347 $msg .= "<html>\n";
348 $msg .= "$body_html\n";
349 $msg .= "<p><a href=\"$RT::WebURL\">$RT::WebURL</a></p>\n";
350 $msg .= "</html>\n";
351 $msg .= "--$boundry--\n";
352 # Send the message
353 if($printmsg) {
354 if ($debug) {
355 print "====== Would call '$sendmail' with this input:\n$msg\n\n";
356 } else {
357 open(SENDMAIL, "|$sendmail") || die "Error sending mail: $!";
358 print SENDMAIL $msg;
359 close(SENDMAIL);
360 }
361 }
362}
363
364# Formats a date like: Thu 10-07-03
365# Designed to be consice yet useful
366sub formatDate() {
367 my($unixtime) = @_;
368 # Return an empty string if we haven't been given a time
369 return "" if $unixtime <= 0;
370 my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($unixtime);
371 return sprintf "%02d-%02d-%02d", $mday, $mon+1, $year%100;
372}
373
374sub stripHTML() {
375my $s = shift;
376$s =~ s/\>/&gt;/g;
377$s =~ s/\</&lt;/g;
378return $s;
379}