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 my $to_date = delete($ARGSRef->{'WillResolve_Date'});
263 my $DateObj = RT::Date->new($session{'CurrentUser'});
265 $DateObj->Set(Format => 'unknown', Value => $to_date);
267 $DateObj->Set(Value => 0);
269 $ARGSRef->{'WillResolve'} = $DateObj->ISO;
271 if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
272 my $tempqueue = RT::Queue->new($RT::SystemUser);
273 $tempqueue->Load( $ARGSRef->{'Queue'} );
274 if ( $tempqueue->id ) {
275 $ARGSRef->{'Queue'} = $tempqueue->id;
279 my @results = UpdateRecordObject(
280 AttributesRef => \@attribs,
281 Object => $TicketObj,
285 # We special case owner changing, so we can use ForceOwnerChange
286 if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
288 if ( $ARGSRef->{'ForceOwnerChange'} ) {
289 $ChownType = "Force";
294 my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
295 push( @results, $msg );
301 =head2 ProcessTicketDates (TicketObj => RT::Ticket, ARGSRef => {})
303 Process updates to the Starts, Started, Told, Resolved, and WillResolve
308 sub ProcessTicketDates {
315 my $Ticket = $args{'TicketObj'};
316 my $ARGSRef = $args{'ARGSRef'};
320 # {{{ Set date fields
321 my @date_fields = qw(
330 #Run through each field in this list. update the value if apropriate
331 foreach my $field (@date_fields) {
332 next unless exists $ARGSRef->{ $field . '_Date' };
333 next if $ARGSRef->{ $field . '_Date' } eq '';
337 my $DateObj = RT::Date->new( $session{'CurrentUser'} );
340 Value => $ARGSRef->{ $field . '_Date' }
343 my $obj = $field . "Obj";
344 if ( ( defined $DateObj->Unix )
345 and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )
347 my $method = "Set$field";
348 my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
349 push @results, "$msg";
357 =head2 ProcessTicketStatus (TicketObj => RT::Ticket, ARGSRef => {})
359 Process updates to the 'Status' field of the ticket. If the new value
360 of Status is 'resolved', this will check required custom fields before
365 sub ProcessTicketStatus {
372 my $TicketObj = $args{'TicketObj'};
373 my $ARGSRef = $args{'ARGSRef'};
376 return () if !$ARGSRef->{'Status'};
378 if ( lc( $ARGSRef->{'Status'} ) eq 'resolved' ) {
379 foreach my $field ( $TicketObj->MissingRequiredFields ) {
380 push @results, loc('Missing required field: [_1]', $field->Name);
384 $m->notes('RedirectToBasics' => 1);
388 return UpdateRecordObject(
389 AttributesRef => [ 'Status' ],
390 Object => $TicketObj,
395 =head2 ProcessUpdateMessage
397 Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly.
399 Don't write message if it only contains current user's signature and
400 SkipSignatureOnly argument is true. Function anyway adds attachments
401 and updates time worked field even if skips message. The default value
406 # change from stock: if txn custom fields are set but there's no content
407 # or attachment, create a Touch txn instead of doing nothing
409 sub ProcessUpdateMessage {
414 SkipSignatureOnly => 1,
418 if ( $args{ARGSRef}->{'UpdateAttachments'}
419 && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
421 delete $args{ARGSRef}->{'UpdateAttachments'};
424 # Strip the signature
425 $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent(
426 Content => $args{ARGSRef}->{UpdateContent},
427 ContentType => $args{ARGSRef}->{UpdateContentType},
428 StripSignature => $args{SkipSignatureOnly},
429 CurrentUser => $args{'TicketObj'}->CurrentUser,
432 my %txn_customfields;
434 foreach my $key ( keys %{ $args{ARGSRef} } ) {
435 if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
436 next if $key =~ /(TimeUnits|Magic)$/;
437 $txn_customfields{$key} = $args{ARGSRef}->{$key};
441 # If, after stripping the signature, we have no message, create a
442 # Touch transaction if necessary
443 if ( not $args{ARGSRef}->{'UpdateAttachments'}
444 and not length $args{ARGSRef}->{'UpdateContent'} )
446 #if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
447 # $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked +
448 # delete $args{ARGSRef}->{'UpdateTimeWorked'};
451 my $timetaken = $args{ARGSRef}->{'UpdateTimeWorked'};
452 if ( $timetaken or grep {length $_} values %txn_customfields ) {
453 my ( $Transaction, $Description, $Object ) =
454 $args{TicketObj}->Touch(
455 CustomFields => \%txn_customfields,
456 TimeTaken => $timetaken
464 if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject ) {
465 $args{ARGSRef}->{'UpdateSubject'} = undef;
468 my $Message = MakeMIMEEntity(
469 Subject => $args{ARGSRef}->{'UpdateSubject'},
470 Body => $args{ARGSRef}->{'UpdateContent'},
471 Type => $args{ARGSRef}->{'UpdateContentType'},
474 $Message->head->add( 'Message-ID' => Encode::encode_utf8(
475 RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} )
477 my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
478 if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
479 $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
481 $old_txn = $args{TicketObj}->Transactions->First();
484 if ( my $msg = $old_txn->Message->First ) {
485 RT::Interface::Email::SetInReplyTo(
491 if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
492 $Message->make_multipart;
493 $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
496 if ( $args{ARGSRef}->{'AttachTickets'} ) {
497 require RT::Action::SendEmail;
498 RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets,
499 ref $args{ARGSRef}->{'AttachTickets'}
500 ? @{ $args{ARGSRef}->{'AttachTickets'} }
501 : ( $args{ARGSRef}->{'AttachTickets'} ) );
504 my $bcc = $args{ARGSRef}->{'UpdateBcc'};
505 my $cc = $args{ARGSRef}->{'UpdateCc'};
509 BccMessageTo => $bcc,
510 Sign => $args{ARGSRef}->{'Sign'},
511 Encrypt => $args{ARGSRef}->{'Encrypt'},
513 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'},
514 CustomFields => \%txn_customfields,
518 foreach my $type (qw(Cc AdminCc)) {
519 if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
520 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} );
521 push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses;
522 push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses;
525 if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
526 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} );
527 push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses;
531 require RT::Action::SendEmail;
532 RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch );
535 unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) {
536 foreach my $key ( keys %{ $args{ARGSRef} } ) {
537 next unless $key =~ /^Update(Cc|Bcc)-(.*)$/;
539 my $var = ucfirst($1) . 'MessageTo';
541 if ( $message_args{$var} ) {
542 $message_args{$var} .= ", $value";
544 $message_args{$var} = $value;
550 # Do the update via the appropriate Ticket method
551 if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
552 my ( $Transaction, $Description, $Object ) =
553 $args{TicketObj}->Comment(%message_args);
554 push( @results, $Description );
555 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
556 } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
557 my ( $Transaction, $Description, $Object ) =
558 $args{TicketObj}->Correspond(%message_args);
559 push( @results, $Description );
560 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
563 loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
568 sub default_FormatDate { $_[0]->AsString }
570 sub ProcessColumnMapValue {
572 my %args = ( Arguments => [],
574 FormatDate => \&default_FormatDate,
578 if ( ref $value eq 'RT::Date' ) {
579 return $args{FormatDate}->($value);
580 } elsif ( UNIVERSAL::isa( $value, 'CODE' ) ) {
581 my @tmp = $value->( @{ $args{'Arguments'} } );
582 return ProcessColumnMapValue( ( @tmp > 1 ? \@tmp : $tmp[0] ), %args );
583 } elsif ( UNIVERSAL::isa( $value, 'ARRAY' ) ) {
584 return join '', map ProcessColumnMapValue( $_, %args ), @$value;
585 } elsif ( UNIVERSAL::isa( $value, 'SCALAR' ) ) {
590 return $m->interp->apply_escapes( $value, 'h' ) if $args{'Escape'};