129f69fabb6498ea11538d07030c4e41a56c0354
[freeside.git] / rt / lib / RT / Interface / Web_Vendor.pm
1 # Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
2 # Copyright (c) 2008 Freeside Internet Services, Inc.
3 #
4 # This work is made available to you under the terms of Version 2 of
5 # the GNU General Public License. A copy of that license should have
6 # been provided with this software, but in any event can be snarfed
7 # from www.gnu.org.
8
9 # This work is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # General Public License for more details.
13
14 =head1 NAME
15
16 RT::Interface::Web_Vendor
17
18 =head1 SYNOPSIS
19
20 =head1 DESCRIPTION
21
22 Freeside vendor overlay for RT::Interface::Web.
23
24 =begin testing
25
26 use_ok(RT::Interface::Web_Vendor);
27
28 =end testing
29
30 =cut
31
32 #package RT::Interface::Web;
33 #use strict;
34
35 package HTML::Mason::Commands;
36 use strict;
37 no warnings qw(redefine);
38
39 =head2 ProcessTicketCustomers 
40
41 =cut
42
43 sub ProcessTicketCustomers {
44     my %args = (
45         TicketObj => undef,
46         ARGSRef   => undef,
47         Debug     => 0,
48         @_
49     );
50     my @results = ();
51
52     my $Ticket  = $args{'TicketObj'};
53     my $ARGSRef = $args{'ARGSRef'};
54     my $Debug   = $args{'Debug'};
55     my $me = 'ProcessTicketCustomers';
56
57     ### false laziness w/RT::Interface::Web::ProcessTicketLinks
58     # Delete links that are gone gone gone.
59     foreach my $arg ( keys %$ARGSRef ) {
60         if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
61             my $base   = $1;
62             my $type   = $2;
63             my $target = $3;
64
65             push @results,
66               "Trying to delete: Base: $base Target: $target  Type $type";
67             my ( $val, $msg ) = $Ticket->DeleteLink( Base   => $base,
68                                                      Type   => $type,
69                                                      Target => $target );
70
71             push @results, $msg;
72
73         }
74
75     }
76     ###
77
78     ###
79     #find new customers
80     ###
81
82     my @custnums = map  { /^Ticket-AddCustomer-(\d+)$/; $1 }
83                    grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
84                    keys %$ARGSRef;
85
86     #my @delete_custnums =
87     #  map  { /^Ticket-AddCustomer-(\d+)$/; $1 }
88     #  grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
89     #  keys %$ARGSRef;
90
91     ###
92     #figure out if we're going to auto-link requestors, and find them if so
93     ###
94
95     my $num_cur_cust = $Ticket->Customers->Count;
96     my $num_new_cust = scalar(@custnums);
97     warn "$me: $num_cur_cust current customers / $num_new_cust new customers\n"
98       if $Debug;
99
100     #if we're linking the first ticket to one customer
101     my $link_requestors = ( $num_cur_cust == 0 && $num_new_cust == 1 );
102     warn "$me: adding a single customer to a previously customerless".
103          " ticket, so linking customers to requestor too\n"
104       if $Debug && $link_requestors;
105
106     my @Requestors = ();
107     if ( $link_requestors ) {
108
109       #find any requestors without customers
110       @Requestors =
111         grep { ! $_->Customers->Count }
112              @{ $Ticket->Requestors->UserMembersObj->ItemsArrayRef };
113
114       warn "$me: found ". scalar(@Requestors). " requestors without".
115            " customers; linking them\n"
116         if $Debug;
117
118     }
119
120     ###
121     #link ticket (and requestors) to customers
122     ###
123
124     foreach my $custnum ( @custnums ) {
125
126       my @link = ( 'Type'   => 'MemberOf',
127                    'Target' => "freeside://freeside/cust_main/$custnum",
128                  );
129
130       my( $val, $msg ) = $Ticket->AddLink(@link);
131       push @results, $msg;
132
133       #add customer links to requestors
134       foreach my $Requestor ( @Requestors ) {
135         my( $val, $msg ) = $Requestor->AddLink(@link);
136         push @results, $msg;
137         warn "$me: linking requestor to custnum $custnum: $msg\n"
138           if $Debug > 1;
139       }
140
141     }
142
143     return @results;
144
145 }
146
147 #false laziness w/above... eventually it should go away in favor of this
148 sub ProcessObjectCustomers {
149     my %args = (
150         Object => undef,
151         ARGSRef   => undef,
152         @_
153     );
154     my @results = ();
155
156     my $Object  = $args{'Object'};
157     my $ARGSRef = $args{'ARGSRef'};
158
159     ### false laziness w/RT::Interface::Web::ProcessTicketLinks
160     # Delete links that are gone gone gone.
161     foreach my $arg ( keys %$ARGSRef ) {
162         if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
163             my $base   = $1;
164             my $type   = $2;
165             my $target = $3;
166
167             push @results,
168               "Trying to delete: Base: $base Target: $target  Type $type";
169             my ( $val, $msg ) = $Object->DeleteLink( Base   => $base,
170                                                      Type   => $type,
171                                                      Target => $target );
172
173             push @results, $msg;
174
175         }
176
177     }
178     ###
179
180     #my @delete_custnums =
181     #  map  { /^Object-AddCustomer-(\d+)$/; $1 }
182     #  grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
183     #  keys %$ARGSRef;
184
185     my @custnums = map  { /^Object-AddCustomer-(\d+)$/; $1 }
186                    grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
187                    keys %$ARGSRef;
188
189     foreach my $custnum ( @custnums ) {
190       my( $val, $msg ) =
191         $Object->AddLink( 'Type'   => 'MemberOf',
192                           'Target' => "freeside://freeside/cust_main/$custnum",
193                         );
194       push @results, $msg;
195     }
196
197     return @results;
198
199 }
200
201 =head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS );
202
203 Updates all core ticket fields except Status, and returns an array of results
204 messages.
205
206 =cut
207
208 sub ProcessTicketBasics {
209
210     my %args = (
211         TicketObj => undef,
212         ARGSRef   => undef,
213         @_
214     );
215
216     my $TicketObj = $args{'TicketObj'};
217     my $ARGSRef   = $args{'ARGSRef'};
218
219     # {{{ Set basic fields
220     my @attribs = qw(
221         Subject
222         FinalPriority
223         Priority
224         TimeEstimated
225         TimeWorked
226         TimeLeft
227         Type
228         Queue
229     );
230
231     if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
232         my $tempqueue = RT::Queue->new($RT::SystemUser);
233         $tempqueue->Load( $ARGSRef->{'Queue'} );
234         if ( $tempqueue->id ) {
235             $ARGSRef->{'Queue'} = $tempqueue->id;
236         }
237     }
238
239     my @results = UpdateRecordObject(
240         AttributesRef => \@attribs,
241         Object        => $TicketObj,
242         ARGSRef       => $ARGSRef,
243     );
244
245     # We special case owner changing, so we can use ForceOwnerChange
246     if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
247         my ($ChownType);
248         if ( $ARGSRef->{'ForceOwnerChange'} ) {
249             $ChownType = "Force";
250         } else {
251             $ChownType = "Give";
252         }
253
254         my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
255         push( @results, $msg );
256     }
257
258     return (@results);
259 }
260
261 =head2 ProcessTicketDates (TicketObj => RT::Ticket, ARGSRef => {}) 
262
263 Process updates to the Starts, Started, Told, Resolved, and WillResolve 
264 fields.
265
266 =cut
267
268 sub ProcessTicketDates {
269     my %args = (
270         TicketObj => undef,
271         ARGSRef   => undef,
272         @_
273     );
274
275     my $Ticket  = $args{'TicketObj'};
276     my $ARGSRef = $args{'ARGSRef'};
277
278     my (@results);
279
280     # {{{ Set date fields
281     my @date_fields = qw(
282         Told
283         Resolved
284         Starts
285         Started
286         Due
287         WillResolve
288     );
289
290     #Run through each field in this list. update the value if apropriate
291     foreach my $field (@date_fields) {
292         next unless exists $ARGSRef->{ $field . '_Date' };
293         next if $ARGSRef->{ $field . '_Date' } eq '';
294
295         my ( $code, $msg );
296
297         my $DateObj = RT::Date->new( $session{'CurrentUser'} );
298         $DateObj->Set(
299             Format => 'unknown',
300             Value  => $ARGSRef->{ $field . '_Date' }
301         );
302
303         my $obj = $field . "Obj";
304         if (    ( defined $DateObj->Unix )
305             and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )
306         {
307             my $method = "Set$field";
308             my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
309             push @results, "$msg";
310         }
311     }
312
313     # }}}
314     return (@results);
315 }
316
317 =head2 ProcessTicketStatus (TicketObj => RT::Ticket, ARGSRef => {})
318
319 Process updates to the 'Status' field of the ticket.  If the new value 
320 of Status is 'resolved', this will check required custom fields before 
321 allowing the update.
322
323 =cut
324
325 sub ProcessTicketStatus {
326     my %args = (
327         TicketObj => undef,
328         ARGSRef   => undef,
329         @_
330     );
331
332     my $TicketObj = $args{'TicketObj'};
333     my $ARGSRef   = $args{'ARGSRef'};
334     my @results;
335
336     return () if !$ARGSRef->{'Status'};
337
338     if ( lc( $ARGSRef->{'Status'} ) eq 'resolved' ) {
339         foreach my $field ( $TicketObj->MissingRequiredFields ) {
340             push @results, loc('Missing required field: [_1]', $field->Name);
341         }
342     }
343     if ( @results ) {
344         $m->notes('RedirectToBasics' => 1);
345         return @results;
346     }
347
348     return UpdateRecordObject(
349         AttributesRef => [ 'Status' ],
350         Object        => $TicketObj,
351         ARGSRef       => $ARGSRef,
352     );
353 }
354
355 =head2 ProcessUpdateMessage
356
357 Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly.
358
359 Don't write message if it only contains current user's signature and
360 SkipSignatureOnly argument is true. Function anyway adds attachments
361 and updates time worked field even if skips message. The default value
362 is true.
363
364 =cut
365
366 # change from stock: if txn custom fields are set but there's no content
367 # or attachment, create a Touch txn instead of doing nothing
368
369 sub ProcessUpdateMessage {
370
371     my %args = (
372         ARGSRef           => undef,
373         TicketObj         => undef,
374         SkipSignatureOnly => 1,
375         @_
376     );
377
378     if ( $args{ARGSRef}->{'UpdateAttachments'}
379         && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
380     {
381         delete $args{ARGSRef}->{'UpdateAttachments'};
382     }
383
384     # Strip the signature
385     $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent(
386         Content        => $args{ARGSRef}->{UpdateContent},
387         ContentType    => $args{ARGSRef}->{UpdateContentType},
388         StripSignature => $args{SkipSignatureOnly},
389         CurrentUser    => $args{'TicketObj'}->CurrentUser,
390     );
391
392     my %txn_customfields;
393
394     foreach my $key ( keys %{ $args{ARGSRef} } ) {
395       if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
396         next if $key =~ /(TimeUnits|Magic)$/;
397         $txn_customfields{$key} = $args{ARGSRef}->{$key};
398       }
399     }
400
401     # If, after stripping the signature, we have no message, create a 
402     # Touch transaction if necessary
403     if (    not $args{ARGSRef}->{'UpdateAttachments'}
404         and not length $args{ARGSRef}->{'UpdateContent'} )
405     {
406         #if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
407         #      $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked +
408         #          delete $args{ARGSRef}->{'UpdateTimeWorked'};
409         #  }
410
411         my $timetaken = $args{ARGSRef}->{'UpdateTimeWorked'};
412         if ( $timetaken or grep {length $_} values %txn_customfields ) {
413             my ( $Transaction, $Description, $Object ) =
414                 $args{TicketObj}->Touch( 
415                   CustomFields => \%txn_customfields,
416                   TimeTaken => $timetaken
417                 );
418             return $Description;
419         }
420
421         return;
422     }
423
424     if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject ) {
425         $args{ARGSRef}->{'UpdateSubject'} = undef;
426     }
427
428     my $Message = MakeMIMEEntity(
429         Subject => $args{ARGSRef}->{'UpdateSubject'},
430         Body    => $args{ARGSRef}->{'UpdateContent'},
431         Type    => $args{ARGSRef}->{'UpdateContentType'},
432     );
433
434     $Message->head->add( 'Message-ID' => Encode::encode_utf8(
435         RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} )
436     ) );
437     my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
438     if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
439         $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
440     } else {
441         $old_txn = $args{TicketObj}->Transactions->First();
442     }
443
444     if ( my $msg = $old_txn->Message->First ) {
445         RT::Interface::Email::SetInReplyTo(
446             Message   => $Message,
447             InReplyTo => $msg
448         );
449     }
450
451     if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
452         $Message->make_multipart;
453         $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
454     }
455
456     if ( $args{ARGSRef}->{'AttachTickets'} ) {
457         require RT::Action::SendEmail;
458         RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets,
459             ref $args{ARGSRef}->{'AttachTickets'}
460             ? @{ $args{ARGSRef}->{'AttachTickets'} }
461             : ( $args{ARGSRef}->{'AttachTickets'} ) );
462     }
463
464     my $bcc = $args{ARGSRef}->{'UpdateBcc'};
465     my $cc  = $args{ARGSRef}->{'UpdateCc'};
466
467     my %message_args = (
468         CcMessageTo  => $cc,
469         BccMessageTo => $bcc,
470         Sign         => $args{ARGSRef}->{'Sign'},
471         Encrypt      => $args{ARGSRef}->{'Encrypt'},
472         MIMEObj      => $Message,
473         TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'},
474         CustomFields => \%txn_customfields,
475     );
476
477     my @temp_squelch;
478     foreach my $type (qw(Cc AdminCc)) {
479         if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
480             push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} );
481             push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses;
482             push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses;
483         }
484     }
485     if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
486             push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} );
487             push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses;
488     }
489
490     if (@temp_squelch) {
491         require RT::Action::SendEmail;
492         RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch );
493     }
494
495     unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) {
496         foreach my $key ( keys %{ $args{ARGSRef} } ) {
497             next unless $key =~ /^Update(Cc|Bcc)-(.*)$/;
498
499             my $var   = ucfirst($1) . 'MessageTo';
500             my $value = $2;
501             if ( $message_args{$var} ) {
502                 $message_args{$var} .= ", $value";
503             } else {
504                 $message_args{$var} = $value;
505             }
506         }
507     }
508
509     my @results;
510     # Do the update via the appropriate Ticket method
511     if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
512         my ( $Transaction, $Description, $Object ) =
513             $args{TicketObj}->Comment(%message_args);
514         push( @results, $Description );
515         #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
516     } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
517         my ( $Transaction, $Description, $Object ) =
518             $args{TicketObj}->Correspond(%message_args);
519         push( @results, $Description );
520         #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
521     } else {
522         push( @results,
523             loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
524     }
525     return @results;
526 }
527
528 1;
529