1 # Copyright (c) 2004 Ivan Kohler <ivan-rt@420.am>
2 # Copyright (c) 2008 Freeside Internet Services, Inc.
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
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.
16 RT::Interface::Web_Vendor
22 Freeside vendor overlay for RT::Interface::Web.
26 use_ok(RT::Interface::Web_Vendor);
32 #package RT::Interface::Web;
35 package HTML::Mason::Commands;
37 no warnings qw(redefine);
39 =head2 ProcessTicketCustomers
43 sub ProcessTicketCustomers {
52 my $Ticket = $args{'TicketObj'};
53 my $ARGSRef = $args{'ARGSRef'};
54 my $Debug = $args{'Debug'};
55 my $me = 'ProcessTicketCustomers';
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)-(.*)$/ ) {
66 "Trying to delete: Base: $base Target: $target Type $type";
67 my ( $val, $msg ) = $Ticket->DeleteLink( Base => $base,
82 my @svcnums = map { /^Ticket-AddService-(\d+)$/; $1 }
83 grep { /^Ticket-AddService-(\d+)$/ && $ARGSRef->{$_} }
87 foreach my $svcnum (@svcnums) {
88 my @link = ( 'Type' => 'MemberOf',
89 'Target' => "freeside://freeside/cust_svc/$svcnum",
92 my( $val, $msg ) = $Ticket->AddLink(@link);
102 push @custnums, map { /^Ticket-AddCustomer-(\d+)$/; $1 }
103 grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
106 #my @delete_custnums =
107 # map { /^Ticket-AddCustomer-(\d+)$/; $1 }
108 # grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
112 #figure out if we're going to auto-link requestors, and find them if so
115 my $num_cur_cust = $Ticket->Customers->Count;
116 my $num_new_cust = scalar(@custnums);
117 warn "$me: $num_cur_cust current customers / $num_new_cust new customers\n"
120 #if we're linking the first ticket to one customer
121 my $link_requestors = ( $num_cur_cust == 0 && $num_new_cust == 1 );
122 warn "$me: adding a single customer to a previously customerless".
123 " ticket, so linking customers to requestor too\n"
124 if $Debug && $link_requestors;
127 if ( $link_requestors ) {
129 #find any requestors without customers
131 grep { ! $_->Customers->Count }
132 @{ $Ticket->Requestors->UserMembersObj->ItemsArrayRef };
134 warn "$me: found ". scalar(@Requestors). " requestors without".
135 " customers; linking them\n"
141 #remove any declared non-customer addresses
144 my $exclude_regexp = RT->Config->Get('NonCustomerEmailRegexp');
145 @Requestors = grep { not $_->EmailAddress =~ $exclude_regexp } @Requestors
146 if defined $exclude_regexp;
149 #link ticket (and requestors) to customers
152 foreach my $custnum ( @custnums ) {
154 my @link = ( 'Type' => 'MemberOf',
155 'Target' => "freeside://freeside/cust_main/$custnum",
158 my( $val, $msg ) = $Ticket->AddLink(@link);
161 #add customer links to requestors
162 foreach my $Requestor ( @Requestors ) {
163 my( $val, $msg ) = $Requestor->AddLink(@link);
165 warn "$me: linking requestor to custnum $custnum: $msg\n"
175 #false laziness w/above... eventually it should go away in favor of this
176 sub ProcessObjectCustomers {
184 my $Object = $args{'Object'};
185 my $ARGSRef = $args{'ARGSRef'};
187 ### false laziness w/RT::Interface::Web::ProcessTicketLinks
188 # Delete links that are gone gone gone.
189 foreach my $arg ( keys %$ARGSRef ) {
190 if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
196 "Trying to delete: Base: $base Target: $target Type $type";
197 my ( $val, $msg ) = $Object->DeleteLink( Base => $base,
208 #my @delete_custnums =
209 # map { /^Object-AddCustomer-(\d+)$/; $1 }
210 # grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
213 my @custnums = map { /^Object-AddCustomer-(\d+)$/; $1 }
214 grep { /^Object-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
217 foreach my $custnum ( @custnums ) {
219 $Object->AddLink( 'Type' => 'MemberOf',
220 'Target' => "freeside://freeside/cust_main/$custnum",
229 =head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS );
231 Updates all core ticket fields except Status, and returns an array of results
236 sub ProcessTicketBasics {
244 my $TicketObj = $args{'TicketObj'};
245 my $ARGSRef = $args{'ARGSRef'};
247 # {{{ Set basic fields
260 # the UI for editing WillResolve through Ticket Basics should allow
262 if ( exists $ARGSRef->{'WillResolve_Date'} ) {
263 my $to_date = delete($ARGSRef->{'WillResolve_Date'});
264 my $DateObj = RT::Date->new($session{'CurrentUser'});
266 $DateObj->Set(Format => 'unknown', Value => $to_date);
268 $DateObj->Set(Value => 0);
270 $ARGSRef->{'WillResolve'} = $DateObj->ISO;
273 if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
274 my $tempqueue = RT::Queue->new($RT::SystemUser);
275 $tempqueue->Load( $ARGSRef->{'Queue'} );
276 if ( $tempqueue->id ) {
277 $ARGSRef->{'Queue'} = $tempqueue->id;
281 my @results = UpdateRecordObject(
282 AttributesRef => \@attribs,
283 Object => $TicketObj,
287 # We special case owner changing, so we can use ForceOwnerChange
288 if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
290 if ( $ARGSRef->{'ForceOwnerChange'} ) {
291 $ChownType = "Force";
296 my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
297 push( @results, $msg );
303 =head2 ProcessTicketDates (TicketObj => RT::Ticket, ARGSRef => {})
305 Process updates to the Starts, Started, Told, Resolved, and WillResolve
310 sub ProcessTicketDates {
317 my $Ticket = $args{'TicketObj'};
318 my $ARGSRef = $args{'ARGSRef'};
322 # {{{ Set date fields
323 my @date_fields = qw(
332 #Run through each field in this list. update the value if apropriate
333 foreach my $field (@date_fields) {
334 next unless exists $ARGSRef->{ $field . '_Date' };
335 next if $ARGSRef->{ $field . '_Date' } eq '';
339 my $DateObj = RT::Date->new( $session{'CurrentUser'} );
342 Value => $ARGSRef->{ $field . '_Date' }
345 my $obj = $field . "Obj";
346 if ( ( defined $DateObj->Unix )
347 and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )
349 my $method = "Set$field";
350 my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
351 push @results, "$msg";
359 =head2 ProcessTicketStatus (TicketObj => RT::Ticket, ARGSRef => {})
361 Process updates to the 'Status' field of the ticket. If the new value
362 of Status is 'resolved', this will check required custom fields before
367 sub ProcessTicketStatus {
374 my $TicketObj = $args{'TicketObj'};
375 my $ARGSRef = $args{'ARGSRef'};
378 return () if !$ARGSRef->{'Status'};
380 if ( lc( $ARGSRef->{'Status'} ) eq 'resolved' ) {
381 foreach my $field ( $TicketObj->MissingRequiredFields ) {
382 push @results, loc('Missing required field: [_1]', $field->Name);
386 $m->notes('RedirectToBasics' => 1);
390 return UpdateRecordObject(
391 AttributesRef => [ 'Status' ],
392 Object => $TicketObj,
397 =head2 ProcessUpdateMessage
399 Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly.
401 Don't write message if it only contains current user's signature and
402 SkipSignatureOnly argument is true. Function anyway adds attachments
403 and updates time worked field even if skips message. The default value
408 # change from stock: if txn custom fields are set but there's no content
409 # or attachment, create a Touch txn instead of doing nothing
411 sub ProcessUpdateMessage {
416 SkipSignatureOnly => 1,
420 if ( $args{ARGSRef}->{'UpdateAttachments'}
421 && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
423 delete $args{ARGSRef}->{'UpdateAttachments'};
426 # Strip the signature
427 $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent(
428 Content => $args{ARGSRef}->{UpdateContent},
429 ContentType => $args{ARGSRef}->{UpdateContentType},
430 StripSignature => $args{SkipSignatureOnly},
431 CurrentUser => $args{'TicketObj'}->CurrentUser,
434 my %txn_customfields;
436 foreach my $key ( keys %{ $args{ARGSRef} } ) {
437 if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
438 next if $key =~ /(TimeUnits|Magic)$/;
439 $txn_customfields{$key} = $args{ARGSRef}->{$key};
443 # If, after stripping the signature, we have no message, create a
444 # Touch transaction if necessary
445 if ( not $args{ARGSRef}->{'UpdateAttachments'}
446 and not length $args{ARGSRef}->{'UpdateContent'} )
448 #if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
449 # $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked +
450 # delete $args{ARGSRef}->{'UpdateTimeWorked'};
453 my $timetaken = $args{ARGSRef}->{'UpdateTimeWorked'};
454 if ( $timetaken or grep {length $_} values %txn_customfields ) {
455 my ( $Transaction, $Description, $Object ) =
456 $args{TicketObj}->Touch(
457 CustomFields => \%txn_customfields,
458 TimeTaken => $timetaken
466 if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject ) {
467 $args{ARGSRef}->{'UpdateSubject'} = undef;
470 my $Message = MakeMIMEEntity(
471 Subject => $args{ARGSRef}->{'UpdateSubject'},
472 Body => $args{ARGSRef}->{'UpdateContent'},
473 Type => $args{ARGSRef}->{'UpdateContentType'},
476 $Message->head->add( 'Message-ID' => Encode::encode_utf8(
477 RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} )
479 my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
480 if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
481 $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
483 $old_txn = $args{TicketObj}->Transactions->First();
486 if ( my $msg = $old_txn->Message->First ) {
487 RT::Interface::Email::SetInReplyTo(
493 if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
494 $Message->make_multipart;
495 $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
498 if ( $args{ARGSRef}->{'AttachTickets'} ) {
499 require RT::Action::SendEmail;
500 RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets,
501 ref $args{ARGSRef}->{'AttachTickets'}
502 ? @{ $args{ARGSRef}->{'AttachTickets'} }
503 : ( $args{ARGSRef}->{'AttachTickets'} ) );
506 my $bcc = $args{ARGSRef}->{'UpdateBcc'};
507 my $cc = $args{ARGSRef}->{'UpdateCc'};
511 BccMessageTo => $bcc,
512 Sign => $args{ARGSRef}->{'Sign'},
513 Encrypt => $args{ARGSRef}->{'Encrypt'},
515 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'},
516 CustomFields => \%txn_customfields,
520 foreach my $type (qw(Cc AdminCc)) {
521 if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
522 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} );
523 push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses;
524 push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses;
527 if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
528 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} );
529 push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses;
533 require RT::Action::SendEmail;
534 RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch );
537 unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) {
538 foreach my $key ( keys %{ $args{ARGSRef} } ) {
539 next unless $key =~ /^Update(Cc|Bcc)-(.*)$/;
541 my $var = ucfirst($1) . 'MessageTo';
543 if ( $message_args{$var} ) {
544 $message_args{$var} .= ", $value";
546 $message_args{$var} = $value;
552 # Do the update via the appropriate Ticket method
553 if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
554 my ( $Transaction, $Description, $Object ) =
555 $args{TicketObj}->Comment(%message_args);
556 push( @results, $Description );
557 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
558 } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
559 my ( $Transaction, $Description, $Object ) =
560 $args{TicketObj}->Correspond(%message_args);
561 push( @results, $Description );
562 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
565 loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
570 sub default_FormatDate { $_[0]->AsString }
572 sub ProcessColumnMapValue {
574 my %args = ( Arguments => [],
576 FormatDate => \&default_FormatDate,
580 if ( ref $value eq 'RT::Date' ) {
581 return $args{FormatDate}->($value);
582 } elsif ( UNIVERSAL::isa( $value, 'CODE' ) ) {
583 my @tmp = $value->( @{ $args{'Arguments'} } );
584 return ProcessColumnMapValue( ( @tmp > 1 ? \@tmp : $tmp[0] ), %args );
585 } elsif ( UNIVERSAL::isa( $value, 'ARRAY' ) ) {
586 return join '', map ProcessColumnMapValue( $_, %args ), @$value;
587 } elsif ( UNIVERSAL::isa( $value, 'SCALAR' ) ) {
592 return $m->interp->apply_escapes( $value, 'h' ) if $args{'Escape'};