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);
267 $ARGSRef->{'WillResolve'} = $DateObj->ISO;
268 } elsif ( $TicketObj and $TicketObj->WillResolveObj->Unix > 0 ) {
269 $DateObj->Set(Value => 0);
270 $ARGSRef->{'WillResolve'} = $DateObj->ISO;
274 if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
275 my $tempqueue = RT::Queue->new($RT::SystemUser);
276 $tempqueue->Load( $ARGSRef->{'Queue'} );
277 if ( $tempqueue->id ) {
278 $ARGSRef->{'Queue'} = $tempqueue->id;
282 my @results = UpdateRecordObject(
283 AttributesRef => \@attribs,
284 Object => $TicketObj,
288 # We special case owner changing, so we can use ForceOwnerChange
289 if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
291 if ( $ARGSRef->{'ForceOwnerChange'} ) {
292 $ChownType = "Force";
297 my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
298 push( @results, $msg );
304 =head2 ProcessTicketDates (TicketObj => RT::Ticket, ARGSRef => {})
306 Process updates to the Starts, Started, Told, Resolved, and WillResolve
311 sub ProcessTicketDates {
318 my $Ticket = $args{'TicketObj'};
319 my $ARGSRef = $args{'ARGSRef'};
323 # {{{ Set date fields
324 my @date_fields = qw(
333 #Run through each field in this list. update the value if apropriate
334 foreach my $field (@date_fields) {
335 next unless exists $ARGSRef->{ $field . '_Date' };
336 next if $ARGSRef->{ $field . '_Date' } eq '';
340 my $DateObj = RT::Date->new( $session{'CurrentUser'} );
343 Value => $ARGSRef->{ $field . '_Date' }
346 my $obj = $field . "Obj";
347 if ( ( defined $DateObj->Unix )
348 and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )
350 my $method = "Set$field";
351 my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
352 push @results, "$msg";
360 =head2 ProcessTicketStatus (TicketObj => RT::Ticket, ARGSRef => {})
362 Process updates to the 'Status' field of the ticket. If the new value
363 of Status is 'resolved', this will check required custom fields before
368 sub ProcessTicketStatus {
375 my $TicketObj = $args{'TicketObj'};
376 my $ARGSRef = $args{'ARGSRef'};
379 return () if !$ARGSRef->{'Status'};
381 if ( lc( $ARGSRef->{'Status'} ) eq 'resolved' ) {
382 foreach my $field ( $TicketObj->MissingRequiredFields ) {
383 push @results, loc('Missing required field: [_1]', $field->Name);
387 $m->notes('RedirectToBasics' => 1);
391 return UpdateRecordObject(
392 AttributesRef => [ 'Status' ],
393 Object => $TicketObj,
398 =head2 ProcessUpdateMessage
400 Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly.
402 Don't write message if it only contains current user's signature and
403 SkipSignatureOnly argument is true. Function anyway adds attachments
404 and updates time worked field even if skips message. The default value
409 # change from stock: if txn custom fields are set but there's no content
410 # or attachment, create a Touch txn instead of doing nothing
412 sub ProcessUpdateMessage {
417 SkipSignatureOnly => 1,
421 if ( $args{ARGSRef}->{'UpdateAttachments'}
422 && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
424 delete $args{ARGSRef}->{'UpdateAttachments'};
427 # Strip the signature
428 $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent(
429 Content => $args{ARGSRef}->{UpdateContent},
430 ContentType => $args{ARGSRef}->{UpdateContentType},
431 StripSignature => $args{SkipSignatureOnly},
432 CurrentUser => $args{'TicketObj'}->CurrentUser,
435 my %txn_customfields;
437 foreach my $key ( keys %{ $args{ARGSRef} } ) {
438 if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
439 next if $key =~ /(TimeUnits|Magic)$/;
440 $txn_customfields{$key} = $args{ARGSRef}->{$key};
444 # If, after stripping the signature, we have no message, create a
445 # Touch transaction if necessary
446 if ( not $args{ARGSRef}->{'UpdateAttachments'}
447 and not length $args{ARGSRef}->{'UpdateContent'} )
449 #if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
450 # $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked +
451 # delete $args{ARGSRef}->{'UpdateTimeWorked'};
454 my $timetaken = $args{ARGSRef}->{'UpdateTimeWorked'};
455 if ( $timetaken or grep {length $_} values %txn_customfields ) {
456 my ( $Transaction, $Description, $Object ) =
457 $args{TicketObj}->Touch(
458 CustomFields => \%txn_customfields,
459 TimeTaken => $timetaken
467 if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject ) {
468 $args{ARGSRef}->{'UpdateSubject'} = undef;
471 my $Message = MakeMIMEEntity(
472 Subject => $args{ARGSRef}->{'UpdateSubject'},
473 Body => $args{ARGSRef}->{'UpdateContent'},
474 Type => $args{ARGSRef}->{'UpdateContentType'},
477 $Message->head->add( 'Message-ID' => Encode::encode_utf8(
478 RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} )
480 my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
481 if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
482 $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
484 $old_txn = $args{TicketObj}->Transactions->First();
487 if ( my $msg = $old_txn->Message->First ) {
488 RT::Interface::Email::SetInReplyTo(
494 if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
495 $Message->make_multipart;
496 $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
499 if ( $args{ARGSRef}->{'AttachTickets'} ) {
500 require RT::Action::SendEmail;
501 RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets,
502 ref $args{ARGSRef}->{'AttachTickets'}
503 ? @{ $args{ARGSRef}->{'AttachTickets'} }
504 : ( $args{ARGSRef}->{'AttachTickets'} ) );
507 my $bcc = $args{ARGSRef}->{'UpdateBcc'};
508 my $cc = $args{ARGSRef}->{'UpdateCc'};
512 BccMessageTo => $bcc,
513 Sign => $args{ARGSRef}->{'Sign'},
514 Encrypt => $args{ARGSRef}->{'Encrypt'},
516 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'},
517 CustomFields => \%txn_customfields,
521 foreach my $type (qw(Cc AdminCc)) {
522 if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
523 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} );
524 push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses;
525 push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses;
528 if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
529 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} );
530 push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses;
534 require RT::Action::SendEmail;
535 RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch );
538 unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) {
539 foreach my $key ( keys %{ $args{ARGSRef} } ) {
540 next unless $key =~ /^Update(Cc|Bcc)-(.*)$/;
542 my $var = ucfirst($1) . 'MessageTo';
544 if ( $message_args{$var} ) {
545 $message_args{$var} .= ", $value";
547 $message_args{$var} = $value;
553 # Do the update via the appropriate Ticket method
554 if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
555 my ( $Transaction, $Description, $Object ) =
556 $args{TicketObj}->Comment(%message_args);
557 push( @results, $Description );
558 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
559 } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
560 my ( $Transaction, $Description, $Object ) =
561 $args{TicketObj}->Correspond(%message_args);
562 push( @results, $Description );
563 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
566 loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
571 sub default_FormatDate { $_[0]->AsString }
573 sub ProcessColumnMapValue {
575 my %args = ( Arguments => [],
577 FormatDate => \&default_FormatDate,
581 if ( ref $value eq 'RT::Date' ) {
582 return $args{FormatDate}->($value);
583 } elsif ( UNIVERSAL::isa( $value, 'CODE' ) ) {
584 my @tmp = $value->( @{ $args{'Arguments'} } );
585 return ProcessColumnMapValue( ( @tmp > 1 ? \@tmp : $tmp[0] ), %args );
586 } elsif ( UNIVERSAL::isa( $value, 'ARRAY' ) ) {
587 return join '', map ProcessColumnMapValue( $_, %args ), @$value;
588 } elsif ( UNIVERSAL::isa( $value, 'SCALAR' ) ) {
593 return $m->interp->apply_escapes( $value, 'h' ) if $args{'Escape'};