#!/usr/local/bin/perl -w # RT3 reminder script # # This script will remind people about tickets they have not responded to # or dealt with in a reasonable length of time. In its normal mode it will # tell ticket owners about tickets which have exceeded a given priority # (see the %pri hash below). It also has the ability to tell ticket owners # about all their open or new tickets (-a), without or with stalled # tickets (-A). # # This is best used with another script to escalate priorities automatically. # # The intended use is to run with no flags daily, with the -a flag weekly # and with the -A flag monthly. Or similar. # # Written by T.D.Bishop@kent.ac.uk, July 2003. # Updated for multiple queues, March 2004. # (using patches sent by jtucker@cyberfuse.com, October 2003) # Updated for RT 3.2.x, November 2004. # (using patches sent by petter.reinholdtsen@usit.uio.no, August 2004) # Updated to only display tickets with a passed or near start date, March 2005. # (using patches sent by ray@sysdev.oucs.ox.ac.uk) # Updated to display string instead of empty subject, June 2005. # (using patches sent by pavel.ruzicka@i.cz) # Whole bunch of updates, June 2005. # (using patches sent by Petter Reinholdtsen ). # Updates to remove warnings with RT 3.8.0, July 2008. # # $Id: rt-remind,v 1.9 2009/09/21 14:19:21 tdb Exp $ ### Configuration # Global mappings of users to addresses # This overrides addresses in the RT database # The most useful case is setting an address for Nobody # # Queue specific overrides can be done by using "queue,user" my(%map) = ( 'Nobody' => 'YOU@YOURDOMAIN', 'root' => 'YOU@YOURDOMAIN', 'queue1,Nobody' => 'ANOTHER@YOURDOMAIN', ); my(@queues); # do not delete this # Queues to operate on (default is all) #@queues = qw( queue1 queue2 ); # Whether to merge the tickets for all queues into one email per person # (as opposed to sending an email per person per queue) my($mergequeues) = 0; # Each state can have a priority associated with it. # When a ticket in that state passes the given priority it is added to # the list to be sent out. my(%pri) = ( 'new' => 2, 'open' => 7, ); # The length at which lines will be truncated. 78, or thereabouts looks # best for most people. Setting to 0 will stop lines being truncated. my($linelen) = 78; # How many days in the future a ticket's start date should be before # we ignore it (unless using -A). my($futuredays) = 7*24*60*60; # Location of RT3's libs use lib ("/usr/local/packages/rt/lib", "/usr/local/packages/rt/local/lib"); # Address emails should originate from my($from) = 'RT Reminder '; # String to use when there is no subject my($nosubject) = "(No subject)"; # charset to use in emails my($charset) = "ISO-8859-15"; ### Code use strict; use Carp; my($showall) = 0; my($showmost) = 0; my($debug) = 0; # Enable debugging # Process command line arguments foreach(@ARGV) { if (/^-a$/) { $showmost = 1; } elsif (/^-A$/) { $showall = 1; } elsif (/^-d$/) { $debug = 1; } else { print STDERR "Usage: rt-remind [-a] [-A] [-h]\n"; print STDERR " -a show all open or new tickets\n"; print STDERR " -A show all open, new, stalled or future tickets\n"; print STDERR " -d debug mode - do not send any emails\n"; print STDERR " -h show this help\n"; exit 1; } } # Pull in the RT stuff package RT; use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc); # Clean our the environment CleanEnv(); # Load the RT configuration RT::LoadConfig(); # Initialise RT RT::Init(); # Drop any setgid permissions we might have #RT::DropSetGIDPermissions(); use RT::Date; use RT::Queue; use RT::Queues; use RT::Tickets; # Path to sendmail and flags. Need to set this after RT::Init() my($sendmail) = RT->Config->Get('SendmailPath') . " " . RT->Config->Get('SendmailArguments'); my(%TicketStore) = (); if(!@queues) { my $queues = new RT::Queues($RT::SystemUser); $queues->LimitToEnabled(); foreach my $queue (@{$queues->ItemsArrayRef()}) { push @queues, $queue->Name; } } foreach my $queuename (@queues) { # Load in the queue my $queue = new RT::Queue($RT::SystemUser); $queue->Load($queuename); my $now = new RT::Date($RT::SystemUser); $now->SetToNow(); # Get hold of the tickets we're after, sorted by priority my $tickets = new RT::Tickets($RT::SystemUser); $tickets->LimitStatus(VALUE => 'new'); $tickets->LimitStatus(VALUE => 'open'); $tickets->LimitStatus(VALUE => 'stalled') if $showall; $tickets->LimitQueue(VALUE => $queue->Id); $tickets->OrderBy(FIELD => 'Priority', ORDER => 'DESC'); my $user = RT::User->new($RT::SystemUser); %TicketStore = () unless $mergequeues; # Sort tickets in to lists for each owner while (my $Ticket = $tickets->Next) { $user->Load($Ticket->Owner); my(@emails) = &getContacts($Ticket, $user, $queuename); foreach my $email (@emails) { push @{$TicketStore{$email}}, $Ticket; } } &doMessages($queuename, %TicketStore) unless $mergequeues; } &doMessages("All", %TicketStore) if $mergequeues; # Disconnect before we finish off $RT::Handle->Disconnect(); exit 0; sub getGroupMemberAddresses { my ($groupname) = @_; print "Looking up members of group '$groupname'\n" if ($debug); use RT::Group; my @emails = (); my ($Group) = new RT::Group($RT::SystemUser); $Group->LoadUserDefinedGroup($groupname); if (defined $Group->Id && $Group->MembersObj->Count != 0) { my ($UserMembers) = $Group->MembersObj; # XXX Should we limit it to users, or recursively expand # groups as well? Only listing first order users for now. # $GroupMembers->LimitToGroups(); $UserMembers->LimitToUsers(); while (my $member = $UserMembers->Next()) { my ($address) = $member->MemberObj->Object->EmailAddress; if ($address) { my ($realname) = $member->MemberObj->Object->RealName; my ($a) = "\"$realname\" <$address>"; print " Found '$a'\n" if $debug; push(@emails, $a); } } return join(",", @emails) if @emails; } return undef; } # # Look up who to send reminder to for a given ticket. The mail # address is constructed based on ticket owner and queue name. # sub getContacts { my ($ticket, $owner, $queuename) = @_; # Try various methods to look up the contacts # Look for queue-specific mapping my($email) = $map{$queuename.",".$owner->Name}; # Check the -owners RT group if (!defined $email) { $email = &getGroupMemberAddresses("$queuename-owners"); } # Look for mapping (non queue-specific) if (!defined $email) { $email = $map{$owner->Name}; } # Look at ticket owner if (!defined $email) { my $address = $owner->EmailAddress; if (defined $address) { my $realname = $owner->RealName; $email = "\"$realname\" <$address>"; } } # If we still can't find anyone, try mapping for root if (!defined $email) { $email = $map{'root'}; } return split(/\s*,\s*/, $email); } # Subroutine to generate messages sub doMessages() { my($queuename, %TicketStore) = @_; foreach my $email (sort keys %TicketStore) { my($t) = $TicketStore{$email}; &doOwner($email, $queuename, @$t); } } # Subroutine to generate a messages for a given owner and set of tickets. sub doOwner() { my($email, $queuename, @tickets) = @_; my($subject); if($queuename eq "All") { $subject = "Outstanding RT tickets"; } else { $subject = "Outstanding RT tickets for queue '$queuename'"; } my($msg); $msg .= "Content-Type: text/plain; charset=\"$charset\"\n"; $msg .= "From: $from\n"; $msg .= "To: $email\n"; $msg .= "Subject: $subject\n"; $msg .= "\n"; if($showall) { $msg .= "This is a summary of all open, new, stalled or future tickets assigned to:\n"; } elsif($showmost) { $msg .= "This is a summary of all open or new tickets assigned to:\n"; } else { $msg .= "These tickets are now considered important and are assigned to:\n"; } $msg .= "\n"; $msg .= " $email\n"; $msg .= "\n"; # Generate heading $msg .= sprintf "%5s %-7s %3s %-13s %-30s\n", "Id", "Status", "Pri", "Created", "Subject"; # We might not want to print the message if there are no tickets my($printmsg) = 0; # Look through tickets, sorted by priority, highest first foreach my $Ticket (sort {$b->Priority <=> $a->Priority} @tickets) { my($starts) = new RT::Date($RT::SystemUser); $starts->Set(Format => 'sql', Value => $Ticket->Starts); # Only add ticket if it's over the priority, or we're in -a or -A # AND the ticket start date has passed or is in the next # $futuredays days, or we're in -A my($showfuture) = 1; if(defined $starts->Diff(time)) { $showfuture = $starts->Diff(time) <= $futuredays; } if(($Ticket->Priority > $pri{$Ticket->Status} || $showmost || $showall) && ($showfuture || $showall)) { $printmsg = 1; # Use our own date formatting routine my($date) = &formatDate($Ticket->CreatedObj->Unix); my($subject) = $Ticket->Subject ? $Ticket->Subject : $nosubject; my($line) = sprintf "%5d %-7s %3d %-13s %-30s", $Ticket->Id, $Ticket->Status, $Ticket->Priority, $date, $subject; # Truncate lines if required if($linelen) { $line = substr($line, 0, $linelen); } $msg .= "$line\n"; } } $msg .= "\n" . RT->Config->Get('WebURL') . "\n"; # Send the message if($printmsg) { if ($debug) { print "====== Would call '$sendmail' with this input:\n"; print "$msg\n\n"; } else { open(SENDMAIL, "|$sendmail") || die "Error sending mail: $!"; print SENDMAIL $msg; close(SENDMAIL); } } } # Formats a date like: Thu 10-07-03 # Designed to be consice yet useful sub formatDate() { my($unixtime) = @_; my(@days) = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ); # Return an empty string if we haven't been given a time return "" if $unixtime <= 0; my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($unixtime); return sprintf "%s %02d-%02d-%02d", $days[$wday], $mday, $mon+1, $year%100; }