1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
#!/usr/bin/perl
package RT::Action::SMSNotify;
use 5.10.1;
use strict;
use warnings;
no if $] >= 5.018, warnings => "experimental";
use Data::Dumper;
use SMS::Send;
use base qw(RT::Action);
=pod
=head1 NAME
RT::Action::SMSNotify - an action to send SMS notifications
=head1 DESCRIPTION
See L<RT::Extension::SMSNotify> for details on how to use this extension,
including how to customise error reporting.
This action may be invoked directly, from rt-crontool, or via a Scrip.
=head1 ARGUMENTS
C<RT::Action::SMSNotify> takes a single argument, like all other RT actions.
The argument is a comma-delimited string of codes indicating where the module
should get phone numbers to SMS from. Wherever a group appears in a category,
all the users from that group will be recursively added.
Recognised codes are:
=head2 TicketRequestors
The ticket requestor(s). May be groups.
=head2 TicketCc
All entries in the ticket Cc field
=head2 TicketAdminCc
All entries in the ticket AdminCc field
=head2 TicketOwner
The ticket Owner field
=head2 QueueCc
All queue watchers in the Cc category on the queue
=head2 QueueAdminCc
All queue watchers in the AdminCc category on the queue
=head2 g:name
The RT group with name 'name'. Ignored with a warning if it doesn't exist.
No mechanism for escaping commas in names is provided.
=head2 p:number
A phone number, specified in +0000000 form with no spaces, commas etc.
=head2 filtermodule:
You may override the phone number filter function on a per-call basis by
passing filtermodule: in the arguments. The argument must be the name of a
module that defines a GetPhoneForUser function.
This can appear anywhere in the argument string. For example, to force
the use of the OnShift filter for this action, you might write:
TicketAdminCc,TicketRequestors,TicketOwner,TicketCc,filtermodule:RT::Extension::SMSNotify::OnShift
=cut
sub _ArgToUsers {
# Convert one of the argument codes into an array of users.
# If it's one of the predefined codes, looks up the users object for it;
# otherwise looks for a u: or g: prefix for a user or group name or
# for a p: prefix for a phone number.
#
# returns a 2-tuple where one part is always undef. 1st part is
# arrayref of RT::User objects, 2nd part is a phone number from a p:
# code as a string.
#
my $ticket = shift;
my $name = shift;
my $queue = $ticket->QueueObj;
# To be set to an arrayref of members
my $m = undef;
# To be set to a scalar phone number from p:
my $p = undef;
RT::Logger->debug("SMSNotify: Examining $name for recipients");
for ($name) {
when (/^TicketRequestors?$/) {
$m = $ticket->Requestors->UserMembersObj->ItemsArrayRef;
}
when (/^TicketCc$/) {
$m = $ticket->Cc->UserMembersObj->ItemsArrayRef;
}
when (/^TicketAdminCc$/) {
$m = $ticket->AdminCc->UserMembersObj->ItemsArrayRef;
}
when (/^TicketOwner$/) {
$m = $ticket->OwnerGroup->UserMembersObj->ItemsArrayRef;
}
when (/^QueueCc$/) {
$m = $queue->Cc->UserMembersObj->ItemsArrayRef;
}
when (/^QueueAdminCc$/) {
$m = $queue->AdminCc->UserMembersObj->ItemsArrayRef;
}
when (/^g:/) {
my $g = RT::Group->new($RT::SystemUser);
$g->LoadUserDefinedGroup(substr($name,2));
$m = $g->UserMembersObj->ItemsArrayRef;
}
when (/^p:/) { $p = substr($name, 2); }
default {
RT::Logger->error("Unrecognised argument $name, ignoring");
}
}
die("Assertion that either \$m or \$p is undef violated") if (defined($m) == defined($p));
if (defined($m)) {
my @recips = map $_->Name, grep defined, @$m;
RT::Logger->debug("SMSNotify: Found " . scalar(@recips) . " recipient(s): " . join(', ', @recips) );
} else {
RT::Logger->debug("SMSNotify: Found phone number $p");
}
return $m, $p;
}
sub _AddPagersToRecipients {
# Takes hashref of { userid => userobject } form and an arrayref of
# RT::User objects to merge into it if the user ID isn't already
# present.
my $destusers = shift;
my $userstoadd = shift;
for my $u (@$userstoadd) {
$destusers->{$u->Id} = $u;
}
}
sub Prepare {
my $self = shift;
if (!$self->Argument) {
RT::Logger->error("Argument to RT::Action::SMSNotify required, see docs");
return 0;
}
if (! RT->Config->Get('SMSNotifyArguments') ) {
RT::Logger->error("\$SMSNotifyArguments is not set in RT_SiteConfig.pm");
return 0;
}
if (!defined(RT->Config->Get('SMSNotifyProvider'))) {
RT::Logger->error("\$SMSNotifyProvider is not set in RT_SiteConfig.pm");
return 0;
}
my $ticket = $self->TicketObj;
my $destusers = {};
my %numbers = ();
my $filter_arg = undef;
foreach my $argpart (split(',', $self->Argument)) {
if ($argpart =~ /filtermodule:/) {
$filter_arg = substr($argpart,length("filtermodule:"));
} else {
my ($userarray, $phoneno) = _ArgToUsers($ticket, $argpart);
_AddPagersToRecipients($destusers, $userarray) if defined($userarray);
$numbers{$phoneno} = undef if defined($phoneno);
}
}
if ($filter_arg) {
RT::Logger->debug("SMSNotify: Using phone filter argument " . $filter_arg);
}
# For each unique user to be notified, get their phone number(s) using
# the $SMSNotifyGetPhoneForUserFn mapping function and if it's defined,
# add that number as a key to the numbers hash with their user ID as the value.
# (If multiple users have the same number, the last user wins).
RT::Logger->debug("SMSNotify: Checking users for pager numbers: " . join(', ', map $_->Name, values %$destusers) );
my $getpagerfn = RT::Extension::SMSNotify::_GetPhoneLookupFunction($filter_arg);
foreach my $u (values %$destusers) {
foreach my $ph (&{$getpagerfn}($u, $ticket)) {
if (defined($ph)) {
RT::Logger->debug("SMSNotify: Adding $ph for user " . $u->Name);
} else {
RT::Logger->debug("SMSNotify: GetPhoneForUser function returned undef for " . $u->Name . ", skipping");
}
$numbers{$ph} = $u if ($ph);
}
}
if (%numbers) {
RT::Logger->info("SMSNotify: Preparing to send SMSes to: " . join(', ', keys %numbers) );
} else {
RT::Logger->info("SMSNotify: No recipients with pager numbers, not sending SMSes");
}
$self->{'PagerNumbersForUsers'} = \%numbers;
return scalar keys %numbers;
}
sub Commit {
my $self = shift;
my %memberlist = %{$self->{'PagerNumbersForUsers'}};
my $cfgargs = RT->Config->Get('SMSNotifyArguments');
my $smsprovider = RT->Config->Get('SMSNotifyProvider');
my $sender = SMS::Send->new( $smsprovider, %$cfgargs );
while ( my ($ph,$u) = each %memberlist ) {
my $uname = defined($u) ? $u->Name : 'none';
my ($result, $message) = $self->TemplateObj->Parse(
Argument => $self->Argument,
TicketObj => $self->TicketObj,
TransactionObj => $self->TransactionObj,
UserObj => $u,
PhoneNumber => $ph
);
if ( !$result ) {
eval {
RT::Extension::SMSNotify::_GetErrorNotifyFunction()->($result, $message, $ph, $u);
};
if ($@) { RT::Logger->crit("SMSNotify: Error notify function died: $@"); }
next;
}
my $MIMEObj = $self->TemplateObj->MIMEObj;
my $msgstring = $MIMEObj->bodyhandle->as_string;
eval {
$RT::Logger->debug("SMSNotify: Sending SMS to $ph");
$sender->send_sms(
text => $msgstring,
to => $ph
);
$RT::Logger->info("SMSNotify: Sent SMS to $ph (user: $uname)");
};
if ($@) {
my $msg = $@;
eval {
my $errfn = RT::Extension::SMSNotify::_GetErrorNotifyFunction();
$errfn->($result, $msg, $ph, $u);
};
if ($@) { RT::Logger->crit("SMSNotify: Error notify function died: $@"); }
}
}
return 1;
}
1;
|