1 ## $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Interface/Web.pm,v 1.1 2002-08-12 06:17:08 ivan Exp $
3 ## Portions Copyright 2000 Tobias Brox <tobix@fsck.com>
4 ## Copyright 1996-2002 Jesse Vincent <jesse@bestpractical.com>
6 ## This is a library of static subs to be used by the Mason web
9 package RT::Interface::Web;
15 Returns a new Mason::Parser object. Takes a param hash of things
16 that get passed to HTML::Mason::Parser. Currently hard coded to only
17 take the parameter 'allow_globals'.
23 allow_globals => undef,
27 my $parser = new HTML::Mason::Parser(
28 default_escape_flags => 'h',
29 allow_globals => $args{'allow_globals'}
40 Takes a paremeter hash. Needs a param called 'parser' which is a reference
41 to an HTML::Mason::Parser.
42 returns a new Mason::Interp object
49 [ local => $RT::MasonLocalComponentRoot ],
50 [ standard => $RT::MasonComponentRoot ]
52 data_dir => "$RT::MasonDataDir",
56 #We allow recursive autohandlers to allow for RT auth.
58 use HTML::Mason::Interp;
59 my $interp = new HTML::Mason::Interp(%params);
65 # {{{ sub NewApacheHandler
67 =head2 NewApacheHandler
69 Takes a Mason::Interp object
70 Returns a new Mason::ApacheHandler object
74 sub NewApacheHandler {
76 my $ah = new HTML::Mason::ApacheHandler( interp => $interp );
83 # {{{ sub NewMason11ApacheHandler
85 =head2 NewMason11ApacheHandler
87 Returns a new Mason::ApacheHandler object
91 sub NewMason11ApacheHandler {
92 my %args = ( default_escape_flags => 'h',
93 allow_globals => [%session],
95 [ local => $RT::MasonLocalComponentRoot ],
96 [ standard => $RT::MasonComponentRoot ]
98 data_dir => "$RT::MasonDataDir",
101 my $ah = new HTML::Mason::ApacheHandler(%args);
113 package HTML::Mason::Commands;
116 # Error - calls Error and aborts
119 if ( $session{'ErrorDocument'} && $session{'ErrorDocumentType'} ) {
120 SetContentType( $session{'ErrorDocumentType'} );
121 $m->comp( $session{'ErrorDocument'}, Why => shift );
125 SetContentType('text/html');
126 $m->comp( "/Elements/Error", Why => shift );
133 # {{{ sub CreateTicket
135 =head2 CreateTicket ARGS
137 Create a new ticket, using Mason's %ARGS. returns @results.
145 my $Ticket = new RT::Ticket( $session{'CurrentUser'} );
147 my $Queue = new RT::Queue( $session{'CurrentUser'} );
148 unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
149 Abort('Queue not found');
152 unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
153 Abort('You have no permission to create tickets in that queue.');
156 my $due = new RT::Date( $session{'CurrentUser'} );
157 $due->Set( Format => 'unknown', Value => $ARGS{'Due'} );
158 my $starts = new RT::Date( $session{'CurrentUser'} );
159 $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} );
161 my @Requestors = split ( /,/, $ARGS{'Requestors'} );
162 my @Cc = split ( /,/, $ARGS{'Cc'} );
163 my @AdminCc = split ( /,/, $ARGS{'AdminCc'} );
165 my $MIMEObj = MakeMIMEEntity(
166 Subject => $ARGS{'Subject'},
167 From => $ARGS{'From'},
169 Body => $ARGS{'Content'},
170 AttachmentFieldName => 'Attach'
174 Queue => $ARGS{Queue},
175 Owner => $ARGS{Owner},
176 InitialPriority => $ARGS{InitialPriority},
177 FinalPriority => $ARGS{FinalPriority},
178 TimeLeft => $ARGS{TimeLeft},
179 TimeWorked => $ARGS{TimeWorked},
180 Requestor => \@Requestors,
182 AdminCc => \@AdminCc,
183 Subject => $ARGS{Subject},
184 Status => $ARGS{Status},
186 Starts => $starts->ISO,
190 # we need to get any KeywordSelect-<integer> fields into %create_args..
191 grep { $_ =~ /^KeywordSelect-/ &&{ $create_args{$_} = $ARGS{$_} } } %ARGS;
193 my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
194 unless ( $id && $Trans ) {
197 my @linktypes = qw( DependsOn MemberOf RefersTo );
199 foreach my $linktype (@linktypes) {
200 foreach my $luri ( split ( / /, $ARGS{"new-$linktype"} ) ) {
201 $luri =~ s/\s*$//; # Strip trailing whitespace
202 my ( $val, $msg ) = $Ticket->AddLink(
206 push ( @Actions, $msg ) unless ($val);
209 foreach my $luri ( split ( / /, $ARGS{"$linktype-new"} ) ) {
210 my ( $val, $msg ) = $Ticket->AddLink(
215 push ( @Actions, $msg ) unless ($val);
219 push ( @Actions, $ErrMsg );
220 unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
221 Abort( "No permission to view newly created ticket #"
222 . $Ticket->id . "." );
224 return ( $Ticket, @Actions );
230 # {{{ sub LoadTicket - loads a ticket
234 Takes a ticket id as its only variable. if it's handed an array, it takes
237 Returns an RT::Ticket object as the current user.
244 if ( ref($id) eq "ARRAY" ) {
249 Abort("No ticket specified");
252 my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
254 unless ( $Ticket->id ) {
255 Abort("Could not load ticket $id");
262 # {{{ sub ProcessUpdateMessage
264 sub ProcessUpdateMessage {
266 #TODO document what else this takes.
274 #Make the update content have no 'weird' newlines in it
275 if ( $args{ARGSRef}->{'UpdateContent'} ) {
278 $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() )
280 $args{ARGSRef}->{'UpdateSubject'} = undef;
283 my $Message = MakeMIMEEntity(
284 Subject => $args{ARGSRef}->{'UpdateSubject'},
285 Body => $args{ARGSRef}->{'UpdateContent'},
286 AttachmentFieldName => 'UpdateAttachment'
289 ## Check whether this was a refresh or not.
291 # Match Correspondence or Comments.
293 my $trans_type = undef;
294 my $orig_trans = $args{ARGSRef}->{'UpdateType'};
295 if ( $orig_trans =~ /^(private|public)$/ ) {
296 $trans_type = "Comment";
297 }elsif ( $orig_trans eq 'response' ) {
298 $trans_type = "Correspond";
301 # Do we have a transaction that we need to update on? session
302 if( defined( $trans_type ) ){
305 # Prepare a checksum.
306 # See perldoc -f unpack for example of this.
307 my $this_checksum = unpack("%32C*", $Message->body_as_string ) % 65535;
309 # The above *could* generate duplicate checksums. Crosscheck with
311 my $this_length = length( $Message->body_as_string );
313 # Don't forget the ticket id.
314 my $this_id = $args{TicketObj}->id;
316 # Check whether the previous transaction in the
317 # ticket is the same as the current transaction.
318 if( defined( $session{'prev_trans_type'} ) && defined( $session{'prev_trans_chksum'} ) && defined( $session{'prev_trans_length'} ) && defined( $session{'prev_trans_tickid'} ) ){
319 if( $session{'prev_trans_type'} eq $orig_trans && $session{'prev_trans_chksum'} == $this_checksum && $session{'prev_trans_length'} == $this_length && $session{'prev_trans_tickid'} == $this_id ){
320 # Its the same as the previous transaction for this user.
325 # Store them for next time.
326 $session{'prev_trans_type'} = $orig_trans;
327 $session{'prev_trans_chksum'} = $this_checksum;
328 $session{'prev_trans_length'} = $this_length;
329 $session{'prev_trans_tickid'} = $this_id;
331 if( $trans_flag == -1 ){
332 push ( @{ $args{'Actions'} },
333 "This appears to be a duplicate of your previous update (please do not refresh this page)" );
337 if ( $trans_type eq 'Comment' && $trans_flag >= 0 ) {
338 my ( $Transaction, $Description ) = $args{TicketObj}->Comment(
339 CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
340 BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
342 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
344 push ( @{ $args{Actions} }, $Description );
346 elsif ( $trans_type eq 'Correspond' && $trans_flag >= 0 ) {
347 my ( $Transaction, $Description ) = $args{TicketObj}->Correspond(
348 CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
349 BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
351 TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
353 push ( @{ $args{Actions} }, $Description );
357 push ( @{ $args{'Actions'} },
358 "Update type was neither correspondence nor comment. Update not recorded"
366 # {{{ sub MakeMIMEEntity
368 =head2 MakeMIMEEntity PARAMHASH
370 Takes a paramhash Subject, Body and AttachmentFieldName.
372 Returns a MIME::Entity.
378 #TODO document what else this takes.
384 AttachmentFieldName => undef,
388 #Make the update content have no 'weird' newlines in it
390 $args{'Body'} =~ s/\r\n/\n/gs;
391 my $Message = MIME::Entity->build(
392 Subject => $args{'Subject'} || "",
393 From => $args{'From'},
395 Data => [ $args{'Body'} ]
398 my $cgi_object = CGIObject();
399 if ( $cgi_object->param( $args{'AttachmentFieldName'} ) ) {
402 $cgi_object->upload( $args{'AttachmentFieldName'} );
404 use File::Temp qw(tempfile tempdir);
406 #foreach my $filehandle (@filenames) {
408 # my ( $fh, $temp_file ) = tempfile();
410 #$binmode $fh; #thank you, windows
412 # We're having trouble with tempfiles not getting created. Let's try it with
415 my ( $buffer, @file );
417 while ( my $bytesread = read( $cgi_filehandle, $buffer, 4096 ) ) {
418 push ( @file, $buffer );
421 $RT::Logger->debug($file);
422 my $filename = "$cgi_filehandle";
423 $filename =~ s#^(.*)/##;
424 $filename =~ s#^(.*)\\##;
425 my $uploadinfo = $cgi_object->uploadInfo($cgi_filehandle);
430 Filename => $filename,
431 Type => $uploadinfo->{'Content-Type'}
439 $Message->make_singlepart();
446 # {{{ sub ProcessSearchQuery
448 =head2 ProcessSearchQuery
450 Takes a form such as the one filled out in webrt/Search/Elements/PickRestriction and turns it into something that RT::Tickets can understand.
452 TODO Doc exactly what comes in the paramhash
457 sub ProcessSearchQuery {
460 ## TODO: The only parameter here is %ARGS. Maybe it would be
461 ## cleaner to load this parameter as $ARGS, and use $ARGS->{...}
462 ## instead of $args{ARGS}->{...} ? :)
464 #Searches are sticky.
465 if ( defined $session{'tickets'} ) {
467 # Reset the old search
468 $session{'tickets'}->GotoFirstItem;
473 $session{'tickets'} = RT::Tickets->new( $session{'CurrentUser'} );
476 #Import a bookmarked search if we have one
477 if ( defined $args{ARGS}->{'Bookmark'} ) {
478 $session{'tickets'}->ThawLimits( $args{ARGS}->{'Bookmark'} );
481 # {{{ Goto next/prev page
482 if ( $args{ARGS}->{'GotoPage'} eq 'Next' ) {
483 $session{'tickets'}->NextPage;
485 elsif ( $args{ARGS}->{'GotoPage'} eq 'Prev' ) {
486 $session{'tickets'}->PrevPage;
491 # {{{ Deal with limiting the search
493 if ( $args{ARGS}->{'RefreshSearchInterval'} ) {
494 $session{'tickets_refresh_interval'} =
495 $args{ARGS}->{'RefreshSearchInterval'};
498 if ( $args{ARGS}->{'TicketsSortBy'} ) {
499 $session{'tickets_sort_by'} = $args{ARGS}->{'TicketsSortBy'};
500 $session{'tickets_sort_order'} = $args{ARGS}->{'TicketsSortOrder'};
501 $session{'tickets'}->OrderBy(
502 FIELD => $args{ARGS}->{'TicketsSortBy'},
503 ORDER => $args{ARGS}->{'TicketsSortOrder'}
509 # {{{ Set the query limit
510 if ( defined $args{ARGS}->{'RowsPerPage'} ) {
512 "limiting to " . $args{ARGS}->{'RowsPerPage'} . " rows" );
514 $session{'tickets_rows_per_page'} = $args{ARGS}->{'RowsPerPage'};
515 $session{'tickets'}->RowsPerPage( $args{ARGS}->{'RowsPerPage'} );
520 if ( $args{ARGS}->{'ValueOfPriority'} ne '' ) {
521 $session{'tickets'}->LimitPriority(
522 VALUE => $args{ARGS}->{'ValueOfPriority'},
523 OPERATOR => $args{ARGS}->{'PriorityOp'}
529 if ( $args{ARGS}->{'ValueOfOwner'} ne '' ) {
530 $session{'tickets'}->LimitOwner(
531 VALUE => $args{ARGS}->{'ValueOfOwner'},
532 OPERATOR => $args{ARGS}->{'OwnerOp'}
537 # {{{ Limit requestor email
539 if ( $args{ARGS}->{'ValueOfRequestor'} ne '' ) {
540 my $alias = $session{'tickets'}->LimitRequestor(
541 VALUE => $args{ARGS}->{'ValueOfRequestor'},
542 OPERATOR => $args{ARGS}->{'RequestorOp'},
549 if ( $args{ARGS}->{'ValueOfQueue'} ne '' ) {
550 $session{'tickets'}->LimitQueue(
551 VALUE => $args{ARGS}->{'ValueOfQueue'},
552 OPERATOR => $args{ARGS}->{'QueueOp'}
558 if ( $args{ARGS}->{'ValueOfStatus'} ne '' ) {
559 if ( ref( $args{ARGS}->{'ValueOfStatus'} ) ) {
560 foreach my $value ( @{ $args{ARGS}->{'ValueOfStatus'} } ) {
561 $session{'tickets'}->LimitStatus(
563 OPERATOR => $args{ARGS}->{'StatusOp'},
568 $session{'tickets'}->LimitStatus(
569 VALUE => $args{ARGS}->{'ValueOfStatus'},
570 OPERATOR => $args{ARGS}->{'StatusOp'},
578 if ( $args{ARGS}->{'ValueOfSubject'} ne '' ) {
579 $session{'tickets'}->LimitSubject(
580 VALUE => $args{ARGS}->{'ValueOfSubject'},
581 OPERATOR => $args{ARGS}->{'SubjectOp'},
587 if ( $args{ARGS}->{'ValueOfDate'} ne '' ) {
589 my $date = ParseDateToISO( $args{ARGS}->{'ValueOfDate'} );
590 $args{ARGS}->{'DateType'} =~ s/_Date$//;
592 $session{'tickets'}->LimitDate(
593 FIELD => $args{ARGS}->{'DateType'},
595 OPERATOR => $args{ARGS}->{'DateOp'},
601 if ( $args{ARGS}->{'ValueOfContent'} ne '' ) {
602 $session{'tickets'}->LimitContent(
603 VALUE => $args{ARGS}->{'ValueOfContent'},
604 OPERATOR => $args{ARGS}->{'ContentOp'},
609 # {{{ Limit KeywordSelects
611 foreach my $KeywordSelectId (
612 map { /^KeywordSelect(\d+)$/; $1 }
613 grep { /^KeywordSelect(\d+)$/; } keys %{ $args{ARGS} }
616 my $form = $args{ARGS}->{"KeywordSelect$KeywordSelectId"};
617 my $oper = $args{ARGS}->{"KeywordSelectOp$KeywordSelectId"};
618 foreach my $KeywordId ( ref($form) ? @{$form} : ($form) ) {
619 next unless ($KeywordId);
621 if ( $KeywordId =~ /^null$/i ) {
623 #Don't quote the string 'null'
626 # Convert the operator to something apropriate for nulls
627 $oper = 'IS' if ( $oper eq '=' );
628 $oper = 'IS NOT' if ( $oper eq '!=' );
630 $session{'tickets'}->LimitKeyword(
631 KEYWORDSELECT => $KeywordSelectId,
633 QUOTEVALUE => $quote,
634 KEYWORD => $KeywordId
645 # {{{ sub ParseDateToISO
647 =head2 ParseDateToISO
649 Takes a date in an arbitrary format.
650 Returns an ISO date and time in GMT
657 my $date_obj = new RT::Date($CurrentUser);
662 return ( $date_obj->ISO );
668 # TODO: This might eventually read the cookies, user configuration
669 # information from the DB, queue configuration information from the
675 return $args->{$key} || $RT::WebOptions{$key};
680 # {{{ sub ProcessACLChanges
682 sub ProcessACLChanges {
686 my @CheckACL = @$ACLref;
687 my %ARGS = %$ARGSref;
689 my ( $ACL, @results );
692 foreach $ACL (@CheckACL) {
697 # Parse out what we're really talking about.
698 if ( $ACL =~ /^(.*?)-(\d+)-(.*?)-(\d+)/ ) {
699 my $PrincipalType = $1;
700 my $PrincipalId = $2;
704 # {{{ Create an object called Principal
705 # so we can do rights operations
707 if ( $PrincipalType eq 'User' ) {
708 $Principal = new RT::User( $session{'CurrentUser'} );
710 elsif ( $PrincipalType eq 'Group' ) {
711 $Principal = new RT::Group( $session{'CurrentUser'} );
714 Abort("$PrincipalType unknown principal type");
717 $Principal->Load($PrincipalId)
718 || Abort("$PrincipalType $PrincipalId couldn't be loaded");
722 # {{{ load up an RT::ACL object with the same current vals of this ACL
724 my $CurrentACL = new RT::ACL( $session{'CurrentUser'} );
725 if ( $Scope eq 'Queue' ) {
726 $CurrentACL->LimitToQueue($AppliesTo);
728 elsif ( $Scope eq 'System' ) {
729 $CurrentACL->LimitToSystem();
732 $CurrentACL->LimitPrincipalToType($PrincipalType);
733 $CurrentACL->LimitPrincipalToId($PrincipalId);
737 # {{{ Get the values of the select we're working with
738 # into an array. it will contain all the new rights that have
740 #Hack to turn the ACL returned into an array
742 ref( $ARGS{"GrantACE-$ACL"} ) eq 'ARRAY'
743 ? @{ $ARGS{"GrantACE-$ACL"} }
744 : ( $ARGS{"GrantACE-$ACL"} );
748 # {{{ Add any rights we need.
750 foreach my $right (@rights) {
751 next unless ($right);
753 #if the right that's been selected wasn't there before, add it.
755 $CurrentACL->HasEntry(
756 RightScope => "$Scope",
757 RightName => "$right",
758 RightAppliesTo => "$AppliesTo",
759 PrincipalType => $PrincipalType,
760 PrincipalId => $Principal->Id
765 #Add new entry to list of rights.
766 if ( $Scope eq 'Queue' ) {
767 my $Queue = new RT::Queue( $session{'CurrentUser'} );
768 $Queue->Load($AppliesTo);
769 unless ( $Queue->id ) {
770 Abort("Couldn't find a queue called $AppliesTo");
773 my ( $val, $msg ) = $Principal->GrantQueueRight(
774 RightAppliesTo => $Queue->id,
775 RightName => "$right"
780 "Granted right $right to "
786 push ( @results, $msg );
789 elsif ( $Scope eq 'System' ) {
790 my ( $val, $msg ) = $Principal->GrantSystemRight(
791 RightAppliesTo => $AppliesTo,
792 RightName => "$right"
795 push ( @results, "Granted system right '$right' to "
796 . $Principal->Name );
799 push ( @results, $msg );
811 # {{{ remove any rights that have been deleted
814 ref( $ARGS{"RevokeACE"} ) eq 'ARRAY'
815 ? @{ $ARGS{"RevokeACE"} }
816 : ( $ARGS{"RevokeACE"} );
818 foreach my $aceid (@RevokeACE) {
820 my $right = new RT::ACE( $session{'CurrentUser'} );
821 $right->Load($aceid);
822 next unless ( $right->id );
824 my $phrase = "Revoked "
825 . $right->PrincipalType . " "
826 . $right->PrincipalObj->Name
830 if ( $right->RightScope eq 'System' ) {
831 $phrase .= ' across all queues.';
834 $phrase .= ' for the queue ' . $right->AppliesToObj->Name . '.';
836 my ( $val, $msg ) = $right->Delete();
838 push ( @results, $phrase );
841 push ( @results, $msg );
852 # {{{ sub UpdateRecordObj
854 =head2 UpdateRecordObj ( ARGSRef => \%ARGS, Object => RT::Record, AttributesRef => \@attribs)
856 @attribs is a list of ticket fields to check and update if they differ from the B<Object>'s current values. ARGSRef is a ref to HTML::Mason's %ARGS.
858 Returns an array of success/failure messages
862 sub UpdateRecordObject {
865 AttributesRef => undef,
872 my $object = $args{'Object'};
873 my $attributes = $args{'AttributesRef'};
874 my $ARGSRef = $args{'ARGSRef'};
876 foreach $attribute (@$attributes) {
877 if ( ( defined $ARGSRef->{"$attribute"} )
878 and ( $ARGSRef->{"$attribute"} ne $object->$attribute() ) )
880 $ARGSRef->{"$attribute"} =~ s/\r\n/\n/gs;
882 my $method = "Set$attribute";
883 my ( $code, $msg ) = $object->$method( $ARGSRef->{"$attribute"} );
884 push @results, "$attribute: $msg";
892 # {{{ sub ProcessTicketBasics
894 =head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS );
896 Returns an array of results messages.
900 sub ProcessTicketBasics {
908 my $TicketObj = $args{'TicketObj'};
909 my $ARGSRef = $args{'ARGSRef'};
911 # {{{ Set basic fields
922 if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
923 my $tempqueue = RT::Queue->new($RT::SystemUser);
924 $tempqueue->Load( $ARGSRef->{'Queue'} );
925 if ( $tempqueue->id ) {
926 $ARGSRef->{'Queue'} = $tempqueue->Id();
930 my @results = UpdateRecordObject(
931 AttributesRef => \@attribs,
932 Object => $TicketObj,
936 # We special case owner changing, so we can use ForceOwnerChange
937 if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner ne $ARGSRef->{'Owner'} ) ) {
939 if ( $ARGSRef->{'ForceOwnerChange'} ) {
940 $ChownType = "Force";
947 $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
948 push ( @results, "$msg" );
958 # {{{ sub ProcessTicketWatchers
960 =head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );
962 Returns an array of results messages.
966 sub ProcessTicketWatchers {
974 my $Ticket = $args{'TicketObj'};
975 my $ARGSRef = $args{'ARGSRef'};
979 foreach my $key ( keys %$ARGSRef ) {
981 # Delete deletable watchers
982 if ( ( $key =~ /^DelWatcher(\d*)$/ ) and ( $ARGSRef->{$key} ) ) {
983 my ( $code, $msg ) = $Ticket->DeleteWatcher($1);
987 # Delete watchers in the simple style demanded by the bulk manipulator
988 elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {
989 my ( $code, $msg ) = $Ticket->DeleteWatcher( $ARGSRef->{$key}, $1 );
993 # Add new wathchers by email address
994 elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
995 and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) )
998 #They're in this order because otherwise $1 gets clobbered :/
999 my ( $code, $msg ) = $Ticket->AddWatcher(
1000 Type => $ARGSRef->{$key},
1001 Email => $ARGSRef->{ "WatcherAddressEmail" . $1 }
1003 push @results, $msg;
1006 #Add requestors in the simple style demanded by the bulk manipulator
1007 elsif ( $key =~ /^Add(Requestor|Cc|AdminCc)$/ ) {
1008 my ( $code, $msg ) = $Ticket->AddWatcher(
1010 Email => $ARGSRef->{$key}
1012 push @results, $msg;
1015 # Add new watchers by owner
1016 elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
1017 and ( $key =~ /^WatcherTypeUser(\d*)$/ ) )
1020 #They're in this order because otherwise $1 gets clobbered :/
1021 my ( $code, $msg ) =
1022 $Ticket->AddWatcher( Type => $ARGSRef->{$key}, Owner => $1 );
1023 push @results, $msg;
1034 # {{{ sub ProcessTicketDates
1036 =head2 ProcessTicketDates ( TicketObj => $Ticket, ARGSRef => \%ARGS );
1038 Returns an array of results messages.
1042 sub ProcessTicketDates {
1049 my $Ticket = $args{'TicketObj'};
1050 my $ARGSRef = $args{'ARGSRef'};
1054 # {{{ Set date fields
1055 my @date_fields = qw(
1063 #Run through each field in this list. update the value if apropriate
1064 foreach $field (@date_fields) {
1067 my $DateObj = RT::Date->new( $session{'CurrentUser'} );
1069 #If it's something other than just whitespace
1070 if ( $ARGSRef->{ $field . '_Date' } ne '' ) {
1072 Format => 'unknown',
1073 Value => $ARGSRef->{ $field . '_Date' }
1075 my $obj = $field . "Obj";
1076 if ( ( defined $DateObj->Unix )
1077 and ( $DateObj->Unix ne $Ticket->$obj()->Unix() ) )
1079 my $method = "Set$field";
1080 my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
1081 push @results, "$msg";
1092 # {{{ sub ProcessTicketLinks
1094 =head2 ProcessTicketLinks ( TicketObj => $Ticket, ARGSRef => \%ARGS );
1096 Returns an array of results messages.
1100 sub ProcessTicketLinks {
1107 my $Ticket = $args{'TicketObj'};
1108 my $ARGSRef = $args{'ARGSRef'};
1112 # Delete links that are gone gone gone.
1113 foreach my $arg ( keys %$ARGSRef ) {
1114 if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
1120 "Trying to delete: Base: $base Target: $target Type $type";
1121 my ( $val, $msg ) = $Ticket->DeleteLink(
1127 push @results, $msg;
1133 my @linktypes = qw( DependsOn MemberOf RefersTo );
1135 foreach my $linktype (@linktypes) {
1137 for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) )
1139 $luri =~ s/\s*$//; # Strip trailing whitespace
1140 my ( $val, $msg ) = $Ticket->AddLink(
1144 push @results, $msg;
1147 for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) )
1149 my ( $val, $msg ) = $Ticket->AddLink(
1154 push @results, $msg;
1158 #Merge if we need to
1159 if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
1161 $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
1162 push @results, $msg;
1170 # {{{ sub ProcessTicketObjectKeywords
1172 =head2 ProcessTicketObjectKeywords ( TicketObj => $Ticket, ARGSRef => \%ARGS );
1174 Returns an array of results messages.
1178 sub ProcessTicketObjectKeywords {
1185 my $TicketObj = $args{'TicketObj'};
1186 my $ARGSRef = $args{'ARGSRef'};
1190 # {{{ set ObjectKeywords.
1192 my $KeywordSelects = $TicketObj->QueueObj->KeywordSelects;
1194 # iterate through all the keyword selects for this queue
1195 while ( my $KeywordSelect = $KeywordSelects->Next ) {
1199 # if we have KeywordSelectMagic for this keywordselect:
1202 defined $ARGSRef->{ 'KeywordSelectMagic' . $KeywordSelect->id };
1204 # Lets get a hash of the possible values to work with
1205 my $value = $ARGSRef->{ 'KeywordSelect' . $KeywordSelect->id } || [];
1207 #lets get all those values in a hash. regardless of # of entries
1208 #we'll use this for adding and deleting keywords from this object.
1209 my %values = map { $_ => 1 } ref($value) ? @{$value} : ($value);
1211 # Load up the ObjectKeywords for this KeywordSelect for this ticket
1212 my $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id );
1215 # {{{ add new keywords
1217 foreach my $key ( keys %values ) {
1219 #unless the ticket has that keyword for that keyword select,
1220 unless ( $ObjectKeys->HasEntry($key) ) {
1223 my ( $result, $msg ) = $TicketObj->AddKeyword(
1225 KeywordSelect => $KeywordSelect->id
1227 push ( @results, $msg );
1232 # {{{ Delete unused keywords
1234 #redo this search, so we don't ask it to delete things that are already gone
1235 # such as when a single keyword select gets its value changed.
1236 $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id );
1238 while ( my $TicketKey = $ObjectKeys->Next ) {
1240 # if the hash defined above doesn\'t contain the keyword mentioned,
1241 unless ( $values{ $TicketKey->Keyword } ) {
1243 #I'd really love to just call $keyword->Delete, but then
1244 # we wouldn't get a transaction recorded
1245 my ( $result, $msg ) = $TicketObj->DeleteKeyword(
1246 Keyword => $TicketKey->Keyword,
1247 KeywordSelect => $KeywordSelect->id
1249 push ( @results, $msg );
1256 #Iterate through the keyword selects for BulkManipulator style access
1257 while ( my $KeywordSelect = $KeywordSelects->Next ) {
1258 if ( $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id } ) {
1261 my ( $result, $msg ) = $TicketObj->AddKeyword(
1263 $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id },
1264 KeywordSelect => $KeywordSelect->id
1266 push ( @results, $msg );
1268 if ( $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id } ) {
1271 my ( $result, $msg ) = $TicketObj->DeleteKeyword(
1273 $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id },
1274 KeywordSelect => $KeywordSelect->id
1276 push ( @results, $msg );