]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | package RT::Interface::Email::Filter::TakeAction; |
2 | ||
3 | use warnings; | |
4 | use strict; | |
5 | ||
6 | use RT::Interface::Email qw(ParseCcAddressesFromHead); | |
7 | ||
8 | our @REGULAR_ATTRIBUTES = qw(Owner Subject Status Priority FinalPriority); | |
9 | our @TIME_ATTRIBUTES = qw(TimeWorked TimeLeft TimeEstimated); | |
10 | our @DATE_ATTRIBUTES = qw(Due Starts Started Resolved Told); | |
11 | our @LINK_ATTRIBUTES = qw(MemberOf Parents Members Children | |
12 | HasMember RefersTo ReferredToBy DependsOn DependedOnBy); | |
13 | our @WATCHER_ATTRIBUTES = qw(Requestor Cc AdminCc); | |
14 | ||
15 | =head1 NAME | |
16 | ||
17 | RT::Interface::Email::Filter::TakeAction - Change metadata of ticket via email | |
18 | ||
19 | =head1 DESCRIPTION | |
20 | ||
21 | This extension parse content of incomming messages for list commands. Format | |
22 | of commands is: | |
23 | ||
24 | Command: value | |
25 | Command: value | |
26 | ... | |
27 | ||
28 | You can find list of L</COMMANDS commands below>. | |
29 | ||
30 | Some commands (like Status, Queue and other) can be used only once. Commands | |
31 | that manage lists can be used multiple times, for example link, custom fields | |
32 | and watchers commands. Also, the latter can be used with C<Add> and C<Del> | |
33 | prefixes to add/delete values from the current list of the ticket you reply to | |
34 | or comment on. | |
35 | ||
36 | =head2 COMMANDS | |
37 | ||
38 | =head3 Basic | |
39 | ||
40 | =over 4 | |
41 | ||
42 | =item Queue: <name> | |
43 | ||
44 | Set new queue for the ticket | |
45 | ||
46 | =item Subject: <string> | |
47 | ||
48 | Set new subject to the given string | |
49 | ||
50 | =item Status: <status> | |
51 | ||
52 | Set new status, one of new, open, stalled, resolved, rejected or deleted | |
53 | ||
54 | =item Owner: <username> | |
55 | ||
56 | Set new owner using the given username | |
57 | ||
58 | =item Priority: <#> | |
59 | ||
60 | Set new priority to the given value | |
61 | ||
62 | =item FinalPriority: <#> | |
63 | ||
64 | Set new final priority to the given value | |
65 | ||
66 | =back | |
67 | ||
68 | =head3 Dates | |
69 | ||
70 | Set new date/timestamp, or 0 to unset: | |
71 | ||
72 | Due: <new timestamp> | |
73 | Starts: <new timestamp> | |
74 | Started: <new timestamp> | |
75 | ||
76 | =head3 Time | |
77 | ||
78 | Set new times to the given value in minutes. Note that | |
79 | on correspond/comment B<< C<TimeWorked> add time >> to the current | |
80 | value. | |
81 | ||
82 | TimeWorked: <minutes> | |
83 | TimeEstimated: <minutes> | |
84 | TimeLeft: <minutes> | |
85 | ||
86 | =head3 Watchers | |
87 | ||
88 | Manage watchers: requestors, ccs and admin ccs. This commands | |
89 | can be used several times and/or with C<Add> and C<Del> prefixes, | |
90 | for example C<Requestor> comand set requestor(s) and the current | |
91 | requestors would be deleted, but C<AddRequestor> command adds | |
92 | to the current list. | |
93 | ||
94 | Requestor: <address> Set requestor(s) using the email address | |
95 | AddRequestor: <address> Add new requestor using the email address | |
96 | DelRequestor: <address> Remove email address as requestor | |
97 | Cc: <address> Set Cc watcher(s) using the email address | |
98 | AddCc: <address> Add new Cc watcher using the email address | |
99 | DelCc: <address> Remove email address as Cc watcher | |
100 | AdminCc: <address> Set AdminCc watcher(s) using the email address | |
101 | AddAdminCc: <address> Add new AdminCc watcher using the email address | |
102 | DelAdminCc: <address> Remove email address as AdminCc watcher | |
103 | ||
104 | =head3 Links | |
105 | ||
106 | Manage links. These commands are also could be used several times in one | |
107 | message. | |
108 | ||
109 | DependsOn: <ticket id> | |
110 | DependedOnBy: <ticket id> | |
111 | RefersTo: <ticket id> | |
112 | ReferredToBy: <ticket id> | |
113 | Members: <ticket id> | |
114 | MemberOf: <ticket id> | |
115 | ||
116 | =head3 Custom field values | |
117 | ||
118 | Manage custom field values. Could be used multiple times. (The curly braces | |
119 | are literal.) | |
120 | ||
121 | CustomField.{<CFName>}: <custom field value> | |
122 | AddCustomField.{<CFName>}: <custom field value> | |
123 | DelCustomField.{<CFName>}: <custom field value> | |
124 | ||
125 | Short forms: | |
126 | ||
127 | CF.{<CFName>}: <custom field value> | |
128 | AddCF.{<CFName>}: <custom field value> | |
129 | DelCF.{<CFName>}: <custom field value> | |
130 | ||
131 | =cut | |
132 | ||
133 | =head2 GetCurrentUser | |
134 | ||
135 | Returns a CurrentUser object. Also performs all the commands. | |
136 | ||
137 | =cut | |
138 | ||
139 | sub GetCurrentUser { | |
140 | my %args = ( | |
141 | Message => undef, | |
142 | RawMessageRef => undef, | |
143 | CurrentUser => undef, | |
144 | AuthLevel => undef, | |
145 | Action => undef, | |
146 | Ticket => undef, | |
147 | Queue => undef, | |
148 | @_ | |
149 | ); | |
150 | ||
151 | unless ( $args{'CurrentUser'} ) { | |
152 | $RT::Logger->error( | |
153 | "Filter::TakeAction executed when " | |
154 | ."CurrentUser (actor) is not authorized. " | |
155 | ."Most probably you want to add Auth::MailFrom plugin before." | |
156 | ); | |
157 | return ( $args{'CurrentUser'}, $args{'AuthLevel'} ); | |
158 | } | |
159 | ||
160 | # If the user isn't asking for a comment or a correspond, | |
161 | # bail out | |
162 | unless ( $args{'Action'} =~ /^(?:comment|correspond)$/i ) { | |
163 | return ( $args{'CurrentUser'}, $args{'AuthLevel'} ); | |
164 | } | |
165 | ||
166 | my @content; | |
167 | my @parts = $args{'Message'}->parts_DFS; | |
168 | foreach my $part (@parts) { | |
169 | my $body = $part->bodyhandle or next; | |
170 | ||
171 | #if it looks like it has pseudoheaders, that's our content | |
172 | if ( $body->as_string =~ /^(?:\S+):/m ) { | |
173 | @content = $body->as_lines; | |
174 | last; | |
175 | } | |
176 | } | |
177 | ||
178 | my @items; | |
179 | my $found_pseudoheaders = 0; | |
180 | my $separator = ":"; | |
181 | if (defined $RT::CBMSeparator) {$separator = quotemeta($RT::CBMSeparator)} | |
182 | foreach my $line (@content) { | |
183 | next if $line =~ /^\s*$/ && ! $found_pseudoheaders; | |
184 | last if $line !~ /^(?:(\S+)\s*?$separator\s*?(.*)\s*?|)$/; | |
185 | $found_pseudoheaders = 1; | |
186 | push( @items, $1 => $2 ); | |
187 | } | |
188 | my %cmds; | |
189 | while ( my $key = _CanonicalizeCommand( lc shift @items ) ) { | |
190 | my $val = shift @items; | |
191 | # strip leading and trailing spaces | |
192 | $val =~ s/^\s+|\s+$//g; | |
193 | ||
194 | if ( exists $cmds{$key} ) { | |
195 | $cmds{$key} = [ $cmds{$key} ] unless ref $cmds{$key}; | |
196 | push @{ $cmds{$key} }, $val; | |
197 | } else { | |
198 | $cmds{$key} = $val; | |
199 | } | |
200 | } | |
201 | ||
202 | my %results; | |
203 | ||
204 | foreach my $cmd ( keys %cmds ) { | |
205 | my ($val, $msg) = _CheckCommand( $cmd ); | |
206 | unless ( $val ) { | |
207 | $results{ $cmd } = { | |
208 | value => delete $cmds{ $cmd }, | |
209 | result => $val, | |
210 | message => $msg, | |
211 | }; | |
212 | } | |
213 | } | |
214 | ||
215 | my $ticket_as_user = RT::Ticket->new( $args{'CurrentUser'} ); | |
216 | my $queue = RT::Queue->new( $args{'CurrentUser'} ); | |
217 | if ( $cmds{'queue'} ) { | |
218 | $queue->Load( $cmds{'queue'} ); | |
219 | } | |
220 | ||
221 | if ( !$queue->id ) { | |
222 | $queue->Load( $args{'Queue'}->id ); | |
223 | } | |
224 | ||
225 | # If we're updating. | |
226 | if ( $args{'Ticket'}->id ) { | |
227 | $ticket_as_user->Load( $args{'Ticket'}->id ); | |
228 | ||
229 | # we set status later as correspond can reopen ticket | |
230 | foreach my $attribute (grep !/^(Status|TimeWorked)/, @REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES) { | |
231 | next unless defined $cmds{ lc $attribute }; | |
232 | next if $ticket_as_user->$attribute() eq $cmds{ lc $attribute }; | |
233 | ||
234 | # canonicalize owner -- accept an e-mail address | |
235 | if ( $attribute eq 'Owner' && $cmds{ lc $attribute } =~ /\@/ ) { | |
236 | my $user = RT::User->new($RT::SystemUser); | |
237 | $user->LoadByEmail( $cmds{ lc $attribute } ); | |
238 | $cmds{ lc $attribute } = $user->Name if $user->id; | |
239 | } | |
240 | ||
241 | _SetAttribute( | |
242 | $ticket_as_user, $attribute, | |
243 | $cmds{ lc $attribute }, \%results | |
244 | ); | |
245 | } | |
246 | ||
247 | foreach my $attribute (@DATE_ATTRIBUTES) { | |
248 | next unless ( $cmds{ lc $attribute } ); | |
249 | ||
250 | my $date = RT::Date->new( $args{'CurrentUser'} ); | |
251 | $date->Set( | |
252 | Format => 'unknown', | |
253 | Value => $cmds{ lc $attribute }, | |
254 | ); | |
255 | _SetAttribute( $ticket_as_user, $attribute, $date->ISO, | |
256 | \%results ); | |
257 | $results{ lc $attribute }->{value} = $cmds{ lc $attribute }; | |
258 | } | |
259 | ||
260 | foreach my $type ( @WATCHER_ATTRIBUTES ) { | |
261 | my %tmp = _ParseAdditiveCommand( \%cmds, 1, $type ); | |
262 | next unless keys %tmp; | |
263 | ||
264 | $tmp{'Default'} = [ do { | |
265 | my $method = $type; | |
266 | $method .= 's' if $type eq 'Requestor'; | |
267 | $args{'Ticket'}->$method->MemberEmailAddresses; | |
268 | } ]; | |
269 | my ($add, $del) = _CompileAdditiveForUpdate( %tmp ); | |
270 | foreach my $text ( @$del ) { | |
271 | my $user = RT::User->new($RT::SystemUser); | |
272 | $user->LoadByEmail($text) if $text =~ /\@/; | |
273 | $user->Load($text) unless $user->id; | |
274 | my ( $val, $msg ) = $ticket_as_user->DeleteWatcher( | |
275 | Type => $type, | |
276 | PrincipalId => $user->PrincipalId, | |
277 | ); | |
278 | push @{ $results{ 'Del'. $type } }, { | |
279 | value => $text, | |
280 | result => $val, | |
281 | message => $msg | |
282 | }; | |
283 | } | |
284 | foreach my $text ( @$add ) { | |
285 | my $user = RT::User->new($RT::SystemUser); | |
286 | $user->LoadByEmail($text) if $text =~ /\@/; | |
287 | $user->Load($text) unless $user->id; | |
288 | my ( $val, $msg ) = $ticket_as_user->AddWatcher( | |
289 | Type => $type, | |
290 | PrincipalId => $user->PrincipalId, | |
291 | ); | |
292 | push @{ $results{ 'Add'. $type } }, { | |
293 | value => $text, | |
294 | result => $val, | |
295 | message => $msg | |
296 | }; | |
297 | } | |
298 | } | |
299 | ||
300 | { | |
301 | my $time_taken = 0; | |
302 | if (grep $_ eq 'TimeWorked', @TIME_ATTRIBUTES) { | |
303 | if (ref $cmds{'timeworked'}) { | |
304 | map { $time_taken += ($_ || 0) } @{ $cmds{'timeworked'} }; | |
305 | $RT::Logger->debug("Time taken: $time_taken"); | |
306 | } | |
307 | else { | |
308 | $time_taken = $cmds{'timeworked'} || 0; | |
309 | } | |
310 | } | |
311 | ||
312 | my $method = ucfirst $args{'Action'}; | |
313 | my ($status, $msg) = $ticket_as_user->$method( | |
314 | TimeTaken => $time_taken, | |
315 | MIMEObj => $args{'Message'}, | |
316 | ); | |
317 | unless ( $status ) { | |
318 | $RT::Logger->warning( | |
319 | "Couldn't write $args{'Action'}." | |
320 | ." Fallback to standard mailgate. Error: $msg"); | |
321 | return ( $args{'CurrentUser'}, $args{'AuthLevel'} ); | |
322 | } | |
323 | } | |
324 | ||
325 | foreach my $type ( @LINK_ATTRIBUTES ) { | |
326 | my %tmp = _ParseAdditiveCommand( \%cmds, 1, $type ); | |
327 | next unless keys %tmp; | |
328 | ||
2d967a12 MKG |
329 | my $typemap = keys %RT::Link::TYPEMAP ? \%RT::Link::TYPEMAP : $ticket_as_user->LINKTYPEMAP; |
330 | my $link_type = $typemap->{$type}->{'Type'}; | |
331 | my $link_mode = $typemap->{$type}->{'Mode'}; | |
84fb5b46 MKG |
332 | |
333 | $tmp{'Default'} = [ do { | |
334 | my %h = ( Base => 'Target', Target => 'Base' ); | |
335 | my $links = $args{'Ticket'}->_Links( $h{$link_mode}, $link_type ); | |
336 | my @res; | |
337 | while ( my $link = $links->Next ) { | |
338 | my $method = $link_mode .'URI'; | |
339 | my $uri = $link->$method(); | |
340 | next unless $uri->IsLocal; | |
341 | push @res, $uri->Object->Id; | |
342 | } | |
343 | @res; | |
344 | } ]; | |
345 | my ($add, $del) = _CompileAdditiveForUpdate( %tmp ); | |
346 | foreach ( @$del ) { | |
347 | my ($val, $msg) = $ticket_as_user->DeleteLink( | |
348 | Type => $link_type, | |
349 | $link_mode => $_, | |
350 | ); | |
351 | $results{ 'Del'. $type } = { | |
352 | value => $_, | |
353 | result => $val, | |
354 | message => $msg, | |
355 | }; | |
356 | } | |
357 | foreach ( @$add ) { | |
358 | my ($val, $msg) = $ticket_as_user->AddLink( | |
359 | Type => $link_type, | |
360 | $link_mode => $_, | |
361 | ); | |
362 | $results{ 'Add'. $type } = { | |
363 | value => $_, | |
364 | result => $val, | |
365 | message => $msg, | |
366 | }; | |
367 | } | |
368 | } | |
369 | ||
370 | my $custom_fields = $queue->TicketCustomFields; | |
371 | while ( my $cf = $custom_fields->Next ) { | |
372 | my %tmp = _ParseAdditiveCommand( \%cmds, 0, "CustomField{". $cf->Name ."}" ); | |
373 | next unless keys %tmp; | |
374 | ||
375 | $tmp{'Default'} = [ do { | |
376 | my $values = $args{'Ticket'}->CustomFieldValues( $cf->id ); | |
377 | my @res; | |
378 | while ( my $value = $values->Next ) { | |
379 | push @res, $value->Content; | |
380 | } | |
381 | @res; | |
382 | } ]; | |
383 | my ($add, $del) = _CompileAdditiveForUpdate( %tmp ); | |
384 | foreach ( @$del ) { | |
385 | my ( $val, $msg ) = $ticket_as_user->DeleteCustomFieldValue( | |
386 | Field => $cf->id, | |
387 | Value => $_ | |
388 | ); | |
389 | $results{ "DelCustomField{". $cf->Name ."}" } = { | |
390 | value => $_, | |
391 | result => $val, | |
392 | message => $msg, | |
393 | }; | |
394 | } | |
395 | foreach ( @$add ) { | |
396 | my ( $val, $msg ) = $ticket_as_user->AddCustomFieldValue( | |
397 | Field => $cf->id, | |
398 | Value => $_ | |
399 | ); | |
400 | $results{ "DelCustomField{". $cf->Name ."}" } = { | |
401 | value => $_, | |
402 | result => $val, | |
403 | message => $msg, | |
404 | }; | |
405 | } | |
406 | } | |
407 | ||
408 | foreach my $attribute (grep $_ eq 'Status', @REGULAR_ATTRIBUTES) { | |
409 | next unless defined $cmds{ lc $attribute }; | |
410 | next if $ticket_as_user->$attribute() eq $cmds{ lc $attribute }; | |
411 | next if $attribute =~ /deleted/i; | |
412 | ||
413 | _SetAttribute( | |
414 | $ticket_as_user, $attribute, | |
415 | lc $cmds{ lc $attribute }, \%results | |
416 | ); | |
417 | } | |
418 | ||
419 | _ReportResults( | |
420 | Ticket => $args{'Ticket'}, | |
421 | Results => \%results, | |
422 | Message => $args{'Message'} | |
423 | ); | |
424 | return ( $args{'CurrentUser'}, -2 ); | |
425 | ||
426 | } else { | |
427 | ||
428 | my %create_args = (); | |
429 | foreach my $attribute (@REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES) { | |
430 | next unless exists $cmds{ lc $attribute }; | |
431 | ||
432 | # canonicalize owner -- accept an e-mail address | |
433 | if ( $attribute eq 'Owner' && $cmds{ lc $attribute } =~ /\@/ ) { | |
434 | my $user = RT::User->new($RT::SystemUser); | |
435 | $user->LoadByEmail( $cmds{ lc $attribute } ); | |
436 | $cmds{ lc $attribute } = $user->Name if $user->id; | |
437 | } | |
438 | ||
439 | if ( $attribute eq 'TimeWorked' && ref $cmds{ lc $attribute } ) { | |
440 | my $time_taken = 0; | |
441 | map { $time_taken += ($_ || 0) } @{ $cmds{'timeworked'} }; | |
442 | $cmds{'timeworked'} = $time_taken; | |
443 | $RT::Logger->debug("Time taken on create: $time_taken"); | |
444 | } | |
445 | ||
446 | if ( $attribute eq 'Status' && $cmds{ lc $attribute } ) { | |
447 | $cmds{ lc $attribute } = lc $cmds{ lc $attribute }; | |
448 | } | |
449 | ||
450 | $create_args{$attribute} = $cmds{ lc $attribute }; | |
451 | } | |
452 | foreach my $attribute (@DATE_ATTRIBUTES) { | |
453 | next unless exists $cmds{ lc $attribute }; | |
454 | my $date = RT::Date->new( $args{'CurrentUser'} ); | |
455 | $date->Set( | |
456 | Format => 'unknown', | |
457 | Value => $cmds{ lc $attribute } | |
458 | ); | |
459 | $create_args{$attribute} = $date->ISO; | |
460 | } | |
461 | ||
462 | # Canonicalize links | |
463 | foreach my $type ( @LINK_ATTRIBUTES ) { | |
464 | $create_args{ $type } = [ _CompileAdditiveForCreate( | |
465 | _ParseAdditiveCommand( \%cmds, 0, $type ), | |
466 | ) ]; | |
467 | } | |
468 | ||
469 | # Canonicalize custom fields | |
470 | my $custom_fields = $queue->TicketCustomFields; | |
471 | while ( my $cf = $custom_fields->Next ) { | |
472 | my %tmp = _ParseAdditiveCommand( \%cmds, 0, "CustomField{". $cf->Name ."}" ); | |
473 | next unless keys %tmp; | |
474 | $create_args{ 'CustomField-' . $cf->id } = [ _CompileAdditiveForCreate(%tmp) ]; | |
475 | } | |
476 | ||
477 | # Canonicalize watchers | |
478 | # First of all fetch default values | |
479 | foreach my $type ( @WATCHER_ATTRIBUTES ) { | |
480 | my %tmp = _ParseAdditiveCommand( \%cmds, 1, $type ); | |
481 | $tmp{'Default'} = [ $args{'CurrentUser'}->EmailAddress ] if $type eq 'Requestor'; | |
482 | $tmp{'Default'} = [ | |
483 | ParseCcAddressesFromHead( | |
484 | Head => $args{'Message'}->head, | |
485 | CurrentUser => $args{'CurrentUser'}, | |
486 | QueueObj => $args{'Queue'}, | |
487 | ) | |
488 | ] if $type eq 'Cc' && $RT::ParseNewMessageForTicketCcs; | |
489 | ||
490 | $create_args{ $type } = [ _CompileAdditiveForCreate( %tmp ) ]; | |
491 | } | |
492 | ||
493 | # get queue unless mail contain it | |
494 | $create_args{'Queue'} = $args{'Queue'}->Id unless exists $create_args{'Queue'}; | |
495 | ||
496 | # subject | |
497 | unless ( $create_args{'Subject'} ) { | |
498 | $create_args{'Subject'} = $args{'Message'}->head->get('Subject'); | |
499 | chomp $create_args{'Subject'}; | |
500 | } | |
501 | ||
502 | # If we don't already have a ticket, we're going to create a new | |
503 | # ticket | |
504 | ||
505 | my ( $id, $txn_id, $msg ) = $ticket_as_user->Create( | |
506 | %create_args, | |
507 | MIMEObj => $args{'Message'} | |
508 | ); | |
509 | unless ( $id ) { | |
fe26c0cc MKG |
510 | if ($msg =~ /No permission to create/) { |
511 | $msg.= "\n\nIf you feel that you should have permission to create ". | |
512 | "this ticket contact us \@ rt-nonrt\@usit.uio.no"; | |
513 | }; | |
84fb5b46 MKG |
514 | $msg = "Couldn't create ticket from message with commands, ". |
515 | "fallback to standard mailgate.\n\nError: $msg"; | |
516 | $RT::Logger->error( $msg ); | |
517 | $results{'Create'} = { | |
518 | result => $id, | |
519 | message => $msg, | |
520 | }; | |
521 | ||
522 | _ReportResults( Results => \%results, Message => $args{'Message'} ); | |
523 | ||
524 | return ($args{'CurrentUser'}, $args{'AuthLevel'}); | |
525 | } | |
526 | ||
527 | _ReportResults( Results => \%results, Message => $args{'Message'} ); | |
528 | ||
529 | # now that we've created a ticket, we abort so we don't create another. | |
530 | $args{'Ticket'}->Load( $id ); | |
531 | return ( $args{'CurrentUser'}, -2 ); | |
532 | } | |
533 | } | |
534 | ||
535 | sub _ParseAdditiveCommand { | |
536 | my ($cmds, $plural_forms, $base) = @_; | |
537 | my (%res); | |
538 | ||
539 | my @types = $base; | |
540 | push @types, $base.'s' if $plural_forms; | |
541 | push @types, 'Add'. $base; | |
542 | push @types, 'Add'. $base .'s' if $plural_forms; | |
543 | push @types, 'Del'. $base; | |
544 | push @types, 'Del'. $base .'s' if $plural_forms; | |
545 | ||
546 | foreach my $type ( @types ) { | |
547 | next unless defined $cmds->{lc $type}; | |
548 | ||
549 | my @values = ref $cmds->{lc $type} eq 'ARRAY'? | |
550 | @{ $cmds->{lc $type} }: $cmds->{lc $type}; | |
551 | ||
552 | if ( $type =~ /^\Q$base\Es?/ ) { | |
553 | push @{ $res{'Set'} }, @values; | |
554 | } elsif ( $type =~ /^Add/ ) { | |
555 | push @{ $res{'Add'} }, @values; | |
556 | } else { | |
557 | push @{ $res{'Del'} }, @values; | |
558 | } | |
559 | } | |
560 | ||
561 | return %res; | |
562 | } | |
563 | ||
564 | sub _CompileAdditiveForCreate { | |
565 | my %cmd = @_; | |
566 | ||
567 | unless ( exists $cmd{'Default'} && defined $cmd{'Default'} ) { | |
568 | $cmd{'Default'} = []; | |
569 | } elsif ( ref $cmd{'Default'} ne 'ARRAY' ) { | |
570 | $cmd{'Default'} = [ $cmd{'Default'} ]; | |
571 | } | |
572 | ||
573 | my @list; | |
574 | @list = @{ $cmd{'Default'} } unless $cmd{'Set'}; | |
575 | @list = @{ $cmd{'Set'} } if $cmd{'Set'}; | |
576 | push @list, @{ $cmd{'Add'} } if $cmd{'Add'}; | |
577 | if ( $cmd{'Del'} ) { | |
578 | my %seen; | |
579 | $seen{$_} = 1 foreach @{ $cmd{'Del'} }; | |
580 | @list = grep !$seen{$_}, @list; | |
581 | } | |
582 | return @list; | |
583 | } | |
584 | ||
585 | sub _CompileAdditiveForUpdate { | |
586 | my %cmd = @_; | |
587 | ||
588 | my @new = _CompileAdditiveForCreate( %cmd ); | |
589 | ||
590 | unless ( exists $cmd{'Default'} && defined $cmd{'Default'} ) { | |
591 | $cmd{'Default'} = []; | |
592 | } elsif ( ref $cmd{'Default'} ne 'ARRAY' ) { | |
593 | $cmd{'Default'} = [ $cmd{'Default'} ]; | |
594 | } | |
595 | ||
596 | my ($add, $del); | |
597 | unless ( @{ $cmd{'Default'} } ) { | |
598 | $add = \@new; | |
599 | } elsif ( !@new ) { | |
600 | $del = $cmd{'Default'}; | |
601 | } else { | |
602 | my (%cur, %new); | |
603 | $cur{$_} = 1 foreach @{ $cmd{'Default'} }; | |
604 | $new{$_} = 1 foreach @new; | |
605 | ||
606 | $add = [ grep !$cur{$_}, @new ]; | |
607 | $del = [ grep !$new{$_}, @{ $cmd{'Default'} } ]; | |
608 | } | |
609 | $_ ||= [] foreach ($add, $del); | |
610 | return ($add, $del); | |
611 | } | |
612 | ||
613 | sub _SetAttribute { | |
614 | my $ticket = shift; | |
615 | my $attribute = shift; | |
616 | my $value = shift; | |
617 | my $results = shift; | |
618 | my $setter = "Set$attribute"; | |
619 | my ( $val, $msg ) = $ticket->$setter($value); | |
620 | $results->{$attribute} = { | |
621 | value => $value, | |
622 | result => $val, | |
623 | message => $msg | |
624 | }; | |
625 | } | |
626 | ||
627 | sub _CanonicalizeCommand { | |
628 | my $key = shift; | |
629 | # CustomField commands | |
630 | $key =~ s/^(add|del|)c(?:ustom)?-?f(?:ield)?\.?[({\[](.*)[)}\]]$/$1customfield{$2}/i; | |
631 | return $key; | |
632 | } | |
633 | ||
634 | sub _CheckCommand { | |
635 | my ($cmd, $val) = (lc shift, shift); | |
636 | return 1 if $cmd =~ /^(add|del|)customfield{.*}$/i; | |
637 | if ( grep $cmd eq lc $_, @REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES, @DATE_ATTRIBUTES ) { | |
638 | return 1 unless ref $val; | |
639 | return (0, "Command '$cmd' doesn't support multiple values"); | |
640 | } | |
641 | return 1 if grep $cmd eq lc $_, @LINK_ATTRIBUTES; | |
642 | if ( $cmd =~ /^(?:add)(.*)$/i ) { | |
643 | my $cmd = $1; | |
644 | if ( grep $cmd eq lc $_, @REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES, @DATE_ATTRIBUTES ) { | |
645 | return (0, "Command '$cmd' doesn't support multiple values"); | |
646 | } | |
647 | return 1 if grep $cmd eq lc $_, @LINK_ATTRIBUTES, @WATCHER_ATTRIBUTES; | |
648 | } | |
649 | ||
650 | return (0, "Command '$cmd' is unknown"); | |
651 | } | |
652 | ||
653 | sub _ReportResults { | |
654 | my %args = ( Ticket => undef, Message => undef, Results => {}, @_ ); | |
655 | ||
656 | my $msg = ''; | |
657 | unless ( $args{'Ticket'} ) { | |
658 | $msg .= $args{'Results'}{'Create'}{'message'} || ''; | |
659 | $msg .= "\n" if $msg; | |
660 | delete $args{'Results'}{'Create'}; | |
661 | } | |
662 | ||
663 | foreach my $key ( keys %{ $args{'Results'} } ) { | |
664 | my @records = ref $args{'Results'}->{ $key } eq 'ARRAY'? | |
665 | @{$args{'Results'}->{ $key }}: $args{'Results'}->{ $key }; | |
666 | foreach my $rec ( @records ) { | |
667 | next if $rec->{'result'}; | |
668 | $msg .= "Your message has been delivered, but command by mail failed:\n\n"; | |
669 | $msg .= "Failed command '". $key .": ". $rec->{'value'} ."'\n"; | |
670 | $msg .= "Error message: ". ($rec->{'message'}||"(no message)") ."\n\n"; | |
671 | $msg .= "UiO has dissabeled some command by mail options.\n"; | |
672 | $msg .= "See: http://www.uio.no/tjenester/it/applikasjoner/rt/hjelp/cbm.html\n\n"; | |
673 | } | |
674 | } | |
675 | return unless $msg && $msg !~ /^\s*$/; | |
676 | ||
677 | $RT::Logger->warning( $msg ); | |
678 | my $ErrorsTo = RT::Interface::Email::ParseErrorsToAddressFromHead( $args{'Message'}->head ); | |
679 | RT::Interface::Email::MailError( | |
680 | To => $ErrorsTo, | |
681 | Subject => "Command by mail error", | |
682 | Explanation => $msg, | |
683 | MIMEObj => $args{'Message'}, | |
684 | Attach => $args{'Message'}->as_string, | |
685 | ); | |
686 | return; | |
687 | } | |
688 | ||
689 | 1; |