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 # causes endless redirect loops and "WillResolve changed from Not set to Not set" on ticket view?
261 # # the UI for editing WillResolve through Ticket Basics should allow
262 # # setting it to null
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;
272 if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
273 my $tempqueue = RT::Queue->new($RT::SystemUser);
274 $tempqueue->Load( $ARGSRef->{'Queue'} );
275 if ( $tempqueue->id ) {
276 $ARGSRef->{'Queue'} = $tempqueue->id;
280 my @results = UpdateRecordObject(
281 AttributesRef => \@attribs,
282 Object => $TicketObj,
286 # We special case owner changing, so we can use ForceOwnerChange
287 if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner != $ARGSRef->{'Owner'} ) ) {
289 if ( $ARGSRef->{'ForceOwnerChange'} ) {
290 $ChownType = "Force";
295 my ( $val, $msg ) = $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
296 push( @results, $msg );
302 =head2 ProcessTicketDates (TicketObj => RT::Ticket, ARGSRef => {})
304 Process updates to the Starts, Started, Told, Resolved, and WillResolve
309 sub ProcessTicketDates {
316 my $Ticket = $args{'TicketObj'};
317 my $ARGSRef = $args{'ARGSRef'};
321 # {{{ Set date fields
322 my @date_fields = qw(
331 #Run through each field in this list. update the value if apropriate
332 foreach my $field (@date_fields) {
333 next unless exists $ARGSRef->{ $field . '_Date' };
334 next if $ARGSRef->{ $field . '_Date' } eq '';
338 my $DateObj = RT::Date->new( $session{'CurrentUser'} );
341 Value => $ARGSRef->{ $field . '_Date' }
344 my $obj = $field . "Obj";
345 if ( ( defined $DateObj->Unix )
346 and ( $DateObj->Unix != $Ticket->$obj()->Unix() ) )
348 my $method = "Set$field";
349 my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
350 push @results, "$msg";
358 =head2 ProcessTicketStatus (TicketObj => RT::Ticket, ARGSRef => {})
360 Process updates to the 'Status' field of the ticket. If the new value
361 of Status is 'resolved', this will check required custom fields before
366 sub ProcessTicketStatus {
373 my $TicketObj = $args{'TicketObj'};
374 my $ARGSRef = $args{'ARGSRef'};
377 return () if !$ARGSRef->{'Status'};
379 if ( lc( $ARGSRef->{'Status'} ) eq 'resolved' ) {
380 foreach my $field ( $TicketObj->MissingRequiredFields ) {
381 push @results, loc('Missing required field: [_1]', $field->Name);
385 $m->notes('RedirectToBasics' => 1);
389 return UpdateRecordObject(
390 AttributesRef => [ 'Status' ],
391 Object => $TicketObj,
396 =head2 ProcessUpdateMessage
398 Takes paramhash with fields ARGSRef, TicketObj and SkipSignatureOnly.
400 Don't write message if it only contains current user's signature and
401 SkipSignatureOnly argument is true. Function anyway adds attachments
402 and updates time worked field even if skips message. The default value
407 # change from stock: if txn custom fields are set but there's no content
408 # or attachment, create a Touch txn instead of doing nothing
410 sub ProcessUpdateMessage {
415 SkipSignatureOnly => 1,
419 if ( $args{ARGSRef}->{'UpdateAttachments'}
420 && !keys %{ $args{ARGSRef}->{'UpdateAttachments'} } )
422 delete $args{ARGSRef}->{'UpdateAttachments'};
425 # Strip the signature
426 $args{ARGSRef}->{UpdateContent} = RT::Interface::Web::StripContent(
427 Content => $args{ARGSRef}->{UpdateContent},
428 ContentType => $args{ARGSRef}->{UpdateContentType},
429 StripSignature => $args{SkipSignatureOnly},
430 CurrentUser => $args{'TicketObj'}->CurrentUser,
433 my %txn_customfields;
435 foreach my $key ( keys %{ $args{ARGSRef} } ) {
436 if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) {
437 next if $key =~ /(TimeUnits|Magic)$/;
438 $txn_customfields{$key} = $args{ARGSRef}->{$key};
442 # If, after stripping the signature, we have no message, create a
443 # Touch transaction if necessary
444 if ( not $args{ARGSRef}->{'UpdateAttachments'}
445 and not length $args{ARGSRef}->{'UpdateContent'} )
447 #if ( $args{ARGSRef}->{'UpdateTimeWorked'} ) {
448 # $args{ARGSRef}->{TimeWorked} = $args{TicketObj}->TimeWorked +
449 # delete $args{ARGSRef}->{'UpdateTimeWorked'};
452 my $timetaken = $args{ARGSRef}->{'UpdateTimeWorked'};
453 if ( $timetaken or grep {length $_} values %txn_customfields ) {
454 my ( $Transaction, $Description, $Object ) =
455 $args{TicketObj}->Touch(
456 CustomFields => \%txn_customfields,
457 TimeTaken => $timetaken
465 if ( $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject ) {
466 $args{ARGSRef}->{'UpdateSubject'} = undef;
469 my $Message = MakeMIMEEntity(
470 Subject => $args{ARGSRef}->{'UpdateSubject'},
471 Body => $args{ARGSRef}->{'UpdateContent'},
472 Type => $args{ARGSRef}->{'UpdateContentType'},
475 $Message->head->add( 'Message-ID' => Encode::encode_utf8(
476 RT::Interface::Email::GenMessageId( Ticket => $args{'TicketObj'} )
478 my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
479 if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
480 $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
482 $old_txn = $args{TicketObj}->Transactions->First();
485 if ( my $msg = $old_txn->Message->First ) {
486 RT::Interface::Email::SetInReplyTo(
492 if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
493 $Message->make_multipart;
494 $Message->add_part($_) foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
497 if ( $args{ARGSRef}->{'AttachTickets'} ) {
498 require RT::Action::SendEmail;
499 RT::Action::SendEmail->AttachTickets( RT::Action::SendEmail->AttachTickets,
500 ref $args{ARGSRef}->{'AttachTickets'}
501 ? @{ $args{ARGSRef}->{'AttachTickets'} }
502 : ( $args{ARGSRef}->{'AttachTickets'} ) );
505 my $bcc = $args{ARGSRef}->{'UpdateBcc'};
506 my $cc = $args{ARGSRef}->{'UpdateCc'};
510 BccMessageTo => $bcc,
511 Sign => $args{ARGSRef}->{'Sign'},
512 Encrypt => $args{ARGSRef}->{'Encrypt'},
514 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'},
515 CustomFields => \%txn_customfields,
519 foreach my $type (qw(Cc AdminCc)) {
520 if (grep $_ eq $type || $_ eq ( $type . 's' ), @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
521 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{$type} );
522 push @temp_squelch, $args{TicketObj}->$type->MemberEmailAddresses;
523 push @temp_squelch, $args{TicketObj}->QueueObj->$type->MemberEmailAddresses;
526 if (grep $_ eq 'Requestor' || $_ eq 'Requestors', @{ $args{ARGSRef}->{'SkipNotification'} || [] }) {
527 push @temp_squelch, map $_->address, Email::Address->parse( $message_args{Requestor} );
528 push @temp_squelch, $args{TicketObj}->Requestors->MemberEmailAddresses;
532 require RT::Action::SendEmail;
533 RT::Action::SendEmail->SquelchMailTo( RT::Action::SendEmail->SquelchMailTo, @temp_squelch );
536 unless ( $args{'ARGSRef'}->{'UpdateIgnoreAddressCheckboxes'} ) {
537 foreach my $key ( keys %{ $args{ARGSRef} } ) {
538 next unless $key =~ /^Update(Cc|Bcc)-(.*)$/;
540 my $var = ucfirst($1) . 'MessageTo';
542 if ( $message_args{$var} ) {
543 $message_args{$var} .= ", $value";
545 $message_args{$var} = $value;
551 # Do the update via the appropriate Ticket method
552 if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
553 my ( $Transaction, $Description, $Object ) =
554 $args{TicketObj}->Comment(%message_args);
555 push( @results, $Description );
556 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
557 } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
558 my ( $Transaction, $Description, $Object ) =
559 $args{TicketObj}->Correspond(%message_args);
560 push( @results, $Description );
561 #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
564 loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") );
569 sub default_FormatDate { $_[0]->AsString }
571 sub ProcessColumnMapValue {
573 my %args = ( Arguments => [],
575 FormatDate => \&default_FormatDate,
579 if ( ref $value eq 'RT::Date' ) {
580 return $args{FormatDate}->($value);
581 } elsif ( UNIVERSAL::isa( $value, 'CODE' ) ) {
582 my @tmp = $value->( @{ $args{'Arguments'} } );
583 return ProcessColumnMapValue( ( @tmp > 1 ? \@tmp : $tmp[0] ), %args );
584 } elsif ( UNIVERSAL::isa( $value, 'ARRAY' ) ) {
585 return join '', map ProcessColumnMapValue( $_, %args ), @$value;
586 } elsif ( UNIVERSAL::isa( $value, 'SCALAR' ) ) {
591 return $m->interp->apply_escapes( $value, 'h' ) if $args{'Escape'};