Fix for the link maps that are now in RT::Link.
[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 $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'};
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 ) {
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             };
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;