]> git.uio.no Git - usit-rt.git/blob - local/lib/RT/Interface/Email/Filter/TakeAction.pm
Fixes to disable-ticketless + TakeAction
[usit-rt.git] / local / lib / RT / Interface / Email / Filter / TakeAction.pm
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
329             my $link_type = $ticket_as_user->LINKTYPEMAP->{ $type }->{'Type'};
330             my $link_mode = $ticket_as_user->LINKTYPEMAP->{ $type }->{'Mode'};
331
332             $tmp{'Default'} = [ do {
333                 my %h = ( Base => 'Target', Target => 'Base' );
334                 my $links = $args{'Ticket'}->_Links( $h{$link_mode}, $link_type );
335                 my @res;
336                 while ( my $link = $links->Next ) {
337                     my $method = $link_mode .'URI';
338                     my $uri = $link->$method();
339                     next unless $uri->IsLocal;
340                     push @res, $uri->Object->Id;
341                 }
342                 @res;
343             } ];
344             my ($add, $del) = _CompileAdditiveForUpdate( %tmp );
345             foreach ( @$del ) {
346                 my ($val, $msg) = $ticket_as_user->DeleteLink(
347                     Type => $link_type,
348                     $link_mode => $_,
349                 );
350                 $results{ 'Del'. $type } = {
351                     value => $_,
352                     result => $val,
353                     message => $msg,
354                 };
355             }
356             foreach ( @$add ) {
357                 my ($val, $msg) = $ticket_as_user->AddLink(
358                     Type => $link_type,
359                     $link_mode => $_,
360                 );
361                 $results{ 'Add'. $type } = {
362                     value => $_,
363                     result => $val,
364                     message => $msg,
365                 };
366             }
367         }
368
369         my $custom_fields = $queue->TicketCustomFields;
370         while ( my $cf = $custom_fields->Next ) {
371             my %tmp = _ParseAdditiveCommand( \%cmds, 0, "CustomField{". $cf->Name ."}" );
372             next unless keys %tmp;
373
374             $tmp{'Default'} = [ do {
375                 my $values = $args{'Ticket'}->CustomFieldValues( $cf->id );
376                 my @res;
377                 while ( my $value = $values->Next ) {
378                     push @res, $value->Content;
379                 }
380                 @res;
381             } ];
382             my ($add, $del) = _CompileAdditiveForUpdate( %tmp );
383             foreach ( @$del ) {
384                 my ( $val, $msg ) = $ticket_as_user->DeleteCustomFieldValue(
385                     Field => $cf->id,
386                     Value => $_
387                 );
388                 $results{ "DelCustomField{". $cf->Name ."}" } = {
389                     value => $_,
390                     result => $val,
391                     message => $msg,
392                 };
393             }
394             foreach ( @$add ) {
395                 my ( $val, $msg ) = $ticket_as_user->AddCustomFieldValue(
396                     Field => $cf->id,
397                     Value => $_
398                 );
399                 $results{ "DelCustomField{". $cf->Name ."}" } = {
400                     value => $_,
401                     result => $val,
402                     message => $msg,
403                 };
404             }
405         }
406
407         foreach my $attribute (grep $_ eq 'Status', @REGULAR_ATTRIBUTES) {
408             next unless defined $cmds{ lc $attribute };
409             next if $ticket_as_user->$attribute() eq $cmds{ lc $attribute };
410             next if $attribute =~ /deleted/i;
411
412             _SetAttribute(
413                 $ticket_as_user,        $attribute,
414                 lc $cmds{ lc $attribute }, \%results
415             );
416         }
417
418         _ReportResults(
419             Ticket => $args{'Ticket'},
420             Results => \%results,
421             Message => $args{'Message'}
422         );
423         return ( $args{'CurrentUser'}, -2 );
424
425     } else {
426
427         my %create_args = ();
428         foreach my $attribute (@REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES) {
429             next unless exists $cmds{ lc $attribute };
430
431             # canonicalize owner -- accept an e-mail address
432             if ( $attribute eq 'Owner' && $cmds{ lc $attribute } =~ /\@/ ) {
433                 my $user = RT::User->new($RT::SystemUser);
434                 $user->LoadByEmail( $cmds{ lc $attribute } );
435                 $cmds{ lc $attribute } = $user->Name if $user->id;
436             }
437
438             if ( $attribute eq 'TimeWorked' && ref $cmds{ lc $attribute } ) {
439                 my $time_taken = 0;
440                 map { $time_taken += ($_ || 0) }  @{ $cmds{'timeworked'} };
441                 $cmds{'timeworked'} = $time_taken;
442                 $RT::Logger->debug("Time taken on create: $time_taken");
443             }
444
445             if ( $attribute eq 'Status' && $cmds{ lc $attribute } ) {
446                 $cmds{ lc $attribute } = lc $cmds{ lc $attribute };
447             }
448
449             $create_args{$attribute} = $cmds{ lc $attribute };
450         }
451         foreach my $attribute (@DATE_ATTRIBUTES) {
452             next unless exists $cmds{ lc $attribute };
453             my $date = RT::Date->new( $args{'CurrentUser'} );
454             $date->Set(
455                 Format => 'unknown',
456                 Value  => $cmds{ lc $attribute }
457             );
458             $create_args{$attribute} = $date->ISO;
459         }
460
461         # Canonicalize links
462         foreach my $type ( @LINK_ATTRIBUTES ) {
463             $create_args{ $type } = [ _CompileAdditiveForCreate( 
464                 _ParseAdditiveCommand( \%cmds, 0, $type ),
465             ) ];
466         }
467
468         # Canonicalize custom fields
469         my $custom_fields = $queue->TicketCustomFields;
470         while ( my $cf = $custom_fields->Next ) {
471             my %tmp = _ParseAdditiveCommand( \%cmds, 0, "CustomField{". $cf->Name ."}" );
472             next unless keys %tmp;
473             $create_args{ 'CustomField-' . $cf->id } = [ _CompileAdditiveForCreate(%tmp) ];
474         }
475
476         # Canonicalize watchers
477         # First of all fetch default values
478         foreach my $type ( @WATCHER_ATTRIBUTES ) {
479             my %tmp = _ParseAdditiveCommand( \%cmds, 1, $type );
480             $tmp{'Default'} = [ $args{'CurrentUser'}->EmailAddress ] if $type eq 'Requestor';
481             $tmp{'Default'} = [
482                 ParseCcAddressesFromHead(
483                     Head        => $args{'Message'}->head,
484                     CurrentUser => $args{'CurrentUser'},
485                     QueueObj    => $args{'Queue'},
486                 )
487             ] if $type eq 'Cc' && $RT::ParseNewMessageForTicketCcs;
488
489             $create_args{ $type } = [ _CompileAdditiveForCreate( %tmp ) ];
490         }
491
492         # get queue unless mail contain it
493         $create_args{'Queue'} = $args{'Queue'}->Id unless exists $create_args{'Queue'};
494
495         # subject
496         unless ( $create_args{'Subject'} ) {
497             $create_args{'Subject'} = $args{'Message'}->head->get('Subject');
498             chomp $create_args{'Subject'};
499         }
500
501         # If we don't already have a ticket, we're going to create a new
502         # ticket
503
504         my ( $id, $txn_id, $msg ) = $ticket_as_user->Create(
505             %create_args,
506             MIMEObj => $args{'Message'}
507         );
508         unless ( $id ) {
509             if ($msg =~ /No permission to create/) {
510                  $msg.= "\n\nIf you feel that you should have permission to create ".
511                         "this ticket contact us \@ rt-nonrt\@usit.uio.no";     
512             };
513             $msg = "Couldn't create ticket from message with commands, ".
514                    "fallback to standard mailgate.\n\nError: $msg";
515             $RT::Logger->error( $msg );
516             $results{'Create'} = {
517                 result => $id,
518                 message => $msg,
519             };
520
521             _ReportResults( Results => \%results, Message => $args{'Message'} );
522
523             return ($args{'CurrentUser'}, $args{'AuthLevel'});
524         }
525
526         _ReportResults( Results => \%results, Message => $args{'Message'} );
527
528         # now that we've created a ticket, we abort so we don't create another.
529         $args{'Ticket'}->Load( $id );
530         return ( $args{'CurrentUser'}, -2 );
531     }
532 }
533
534 sub _ParseAdditiveCommand {
535     my ($cmds, $plural_forms, $base) = @_;
536     my (%res);
537
538     my @types = $base;
539     push @types, $base.'s' if $plural_forms;
540     push @types, 'Add'. $base;
541     push @types, 'Add'. $base .'s' if $plural_forms;
542     push @types, 'Del'. $base;
543     push @types, 'Del'. $base .'s' if $plural_forms;
544
545     foreach my $type ( @types ) {
546         next unless defined $cmds->{lc $type};
547
548         my @values = ref $cmds->{lc $type} eq 'ARRAY'?
549             @{ $cmds->{lc $type} }: $cmds->{lc $type};
550
551         if ( $type =~ /^\Q$base\Es?/ ) {
552             push @{ $res{'Set'} }, @values;
553         } elsif ( $type =~ /^Add/ ) {
554             push @{ $res{'Add'} }, @values;
555         } else {
556             push @{ $res{'Del'} }, @values;
557         }
558     }
559
560     return %res;
561 }
562
563 sub _CompileAdditiveForCreate {
564     my %cmd = @_;
565
566     unless ( exists $cmd{'Default'} && defined $cmd{'Default'} ) {
567         $cmd{'Default'} = [];
568     } elsif ( ref $cmd{'Default'} ne 'ARRAY' ) {
569         $cmd{'Default'} = [ $cmd{'Default'} ];
570     }
571
572     my @list;
573     @list = @{ $cmd{'Default'} } unless $cmd{'Set'};
574     @list = @{ $cmd{'Set'} } if $cmd{'Set'};
575     push @list, @{ $cmd{'Add'} } if $cmd{'Add'};
576     if ( $cmd{'Del'} ) {
577         my %seen;
578         $seen{$_} = 1 foreach @{ $cmd{'Del'} };
579         @list = grep !$seen{$_}, @list;
580     }
581     return @list;
582 }
583
584 sub _CompileAdditiveForUpdate {
585     my %cmd = @_;
586
587     my @new = _CompileAdditiveForCreate( %cmd );
588
589     unless ( exists $cmd{'Default'} && defined $cmd{'Default'} ) {
590         $cmd{'Default'} = [];
591     } elsif ( ref $cmd{'Default'} ne 'ARRAY' ) {
592         $cmd{'Default'} = [ $cmd{'Default'} ];
593     }
594
595     my ($add, $del);
596     unless ( @{ $cmd{'Default'} } ) {
597         $add = \@new;
598     } elsif ( !@new ) {
599         $del = $cmd{'Default'};
600     } else {
601         my (%cur, %new);
602         $cur{$_} = 1 foreach @{ $cmd{'Default'} };
603         $new{$_} = 1 foreach @new;
604
605         $add = [ grep !$cur{$_}, @new ];
606         $del = [ grep !$new{$_}, @{ $cmd{'Default'} } ];
607     }
608     $_ ||= [] foreach ($add, $del);
609     return ($add, $del);
610 }
611
612 sub _SetAttribute {
613     my $ticket    = shift;
614     my $attribute = shift;
615     my $value     = shift;
616     my $results   = shift;
617     my $setter    = "Set$attribute";
618     my ( $val, $msg ) = $ticket->$setter($value);
619     $results->{$attribute} = {
620         value   => $value,
621         result  => $val,
622         message => $msg
623     };
624 }
625
626 sub _CanonicalizeCommand {
627     my $key = shift;
628     # CustomField commands
629     $key =~ s/^(add|del|)c(?:ustom)?-?f(?:ield)?\.?[({\[](.*)[)}\]]$/$1customfield{$2}/i;
630     return $key;
631 }
632
633 sub _CheckCommand {
634     my ($cmd, $val) = (lc shift, shift);
635     return 1 if $cmd =~ /^(add|del|)customfield{.*}$/i;
636     if ( grep $cmd eq lc $_, @REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES, @DATE_ATTRIBUTES ) {
637         return 1 unless ref $val;
638         return (0, "Command '$cmd' doesn't support multiple values");
639     }
640     return 1 if grep $cmd eq lc $_, @LINK_ATTRIBUTES;
641     if ( $cmd =~ /^(?:add)(.*)$/i ) {
642         my $cmd = $1;
643         if ( grep $cmd eq lc $_, @REGULAR_ATTRIBUTES, @TIME_ATTRIBUTES, @DATE_ATTRIBUTES ) {
644             return (0, "Command '$cmd' doesn't support multiple values");
645         }
646         return 1 if grep $cmd eq lc $_, @LINK_ATTRIBUTES, @WATCHER_ATTRIBUTES;
647     }
648
649     return (0, "Command '$cmd' is unknown");
650 }
651
652 sub _ReportResults {
653     my %args = ( Ticket => undef, Message => undef, Results => {}, @_ );
654
655     my $msg = '';
656     unless ( $args{'Ticket'} ) {
657         $msg .= $args{'Results'}{'Create'}{'message'} || '';
658         $msg .= "\n" if $msg;
659         delete $args{'Results'}{'Create'};
660     }
661
662     foreach my $key ( keys %{ $args{'Results'} } ) {
663         my @records = ref $args{'Results'}->{ $key } eq 'ARRAY'?
664                          @{$args{'Results'}->{ $key }}: $args{'Results'}->{ $key };
665         foreach my $rec ( @records ) {
666             next if $rec->{'result'};
667             $msg .= "Your message has been delivered, but command by mail failed:\n\n";
668             $msg .= "Failed command '". $key .": ". $rec->{'value'} ."'\n";
669             $msg .= "Error message: ". ($rec->{'message'}||"(no message)") ."\n\n";
670             $msg .= "UiO has dissabeled some command by mail options.\n";
671             $msg .= "See: http://www.uio.no/tjenester/it/applikasjoner/rt/hjelp/cbm.html\n\n";
672         }
673     }
674     return unless $msg && $msg !~ /^\s*$/;
675
676     $RT::Logger->warning( $msg );
677     my $ErrorsTo = RT::Interface::Email::ParseErrorsToAddressFromHead( $args{'Message'}->head );
678     RT::Interface::Email::MailError(
679         To          => $ErrorsTo,
680         Subject     => "Command by mail error",
681         Explanation => $msg,
682         MIMEObj     => $args{'Message'},
683         Attach      => $args{'Message'}->as_string,
684     );
685     return;
686 }
687
688 1;