summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Interface/Web.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Interface/Web.pm')
-rw-r--r--rt/lib/RT/Interface/Web.pm1287
1 files changed, 1287 insertions, 0 deletions
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
new file mode 100644
index 000000000..6b5272848
--- /dev/null
+++ b/rt/lib/RT/Interface/Web.pm
@@ -0,0 +1,1287 @@
+## $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Interface/Web.pm,v 1.1 2002-08-12 06:17:08 ivan Exp $
+
+## Portions Copyright 2000 Tobias Brox <tobix@fsck.com>
+## Copyright 1996-2002 Jesse Vincent <jesse@bestpractical.com>
+
+## This is a library of static subs to be used by the Mason web
+## interface to RT
+
+package RT::Interface::Web;
+
+# {{{ sub NewParser
+
+=head2 NewParser
+
+ Returns a new Mason::Parser object. Takes a param hash of things
+ that get passed to HTML::Mason::Parser. Currently hard coded to only
+ take the parameter 'allow_globals'.
+
+=cut
+
+sub NewParser {
+ my %args = (
+ allow_globals => undef,
+ @_
+ );
+
+ my $parser = new HTML::Mason::Parser(
+ default_escape_flags => 'h',
+ allow_globals => $args{'allow_globals'}
+ );
+ return ($parser);
+}
+
+# }}}
+
+# {{{ sub NewInterp
+
+=head2 NewInterp
+
+ Takes a paremeter hash. Needs a param called 'parser' which is a reference
+ to an HTML::Mason::Parser.
+ returns a new Mason::Interp object
+
+=cut
+
+sub NewInterp {
+ my %params = (
+ comp_root => [
+ [ local => $RT::MasonLocalComponentRoot ],
+ [ standard => $RT::MasonComponentRoot ]
+ ],
+ data_dir => "$RT::MasonDataDir",
+ @_
+ );
+
+ #We allow recursive autohandlers to allow for RT auth.
+
+ use HTML::Mason::Interp;
+ my $interp = new HTML::Mason::Interp(%params);
+
+}
+
+# }}}
+
+# {{{ sub NewApacheHandler
+
+=head2 NewApacheHandler
+
+ Takes a Mason::Interp object
+ Returns a new Mason::ApacheHandler object
+
+=cut
+
+sub NewApacheHandler {
+ my $interp = shift;
+ my $ah = new HTML::Mason::ApacheHandler( interp => $interp );
+ return ($ah);
+}
+
+# }}}
+
+
+# {{{ sub NewMason11ApacheHandler
+
+=head2 NewMason11ApacheHandler
+
+ Returns a new Mason::ApacheHandler object
+
+=cut
+
+sub NewMason11ApacheHandler {
+ my %args = ( default_escape_flags => 'h',
+ allow_globals => [%session],
+ comp_root => [
+ [ local => $RT::MasonLocalComponentRoot ],
+ [ standard => $RT::MasonComponentRoot ]
+ ],
+ data_dir => "$RT::MasonDataDir",
+ args_method => 'CGI'
+ );
+ my $ah = new HTML::Mason::ApacheHandler(%args);
+ return ($ah);
+}
+
+# }}}
+
+
+
+
+
+# }}}
+
+package HTML::Mason::Commands;
+
+# {{{ sub Abort
+# Error - calls Error and aborts
+sub Abort {
+
+ if ( $session{'ErrorDocument'} && $session{'ErrorDocumentType'} ) {
+ SetContentType( $session{'ErrorDocumentType'} );
+ $m->comp( $session{'ErrorDocument'}, Why => shift );
+ $m->abort;
+ }
+ else {
+ SetContentType('text/html');
+ $m->comp( "/Elements/Error", Why => shift );
+ $m->abort;
+ }
+}
+
+# }}}
+
+# {{{ sub CreateTicket
+
+=head2 CreateTicket ARGS
+
+Create a new ticket, using Mason's %ARGS. returns @results.
+=cut
+
+sub CreateTicket {
+ my %ARGS = (@_);
+
+ my (@Actions);
+
+ my $Ticket = new RT::Ticket( $session{'CurrentUser'} );
+
+ my $Queue = new RT::Queue( $session{'CurrentUser'} );
+ unless ( $Queue->Load( $ARGS{'Queue'} ) ) {
+ Abort('Queue not found');
+ }
+
+ unless ( $Queue->CurrentUserHasRight('CreateTicket') ) {
+ Abort('You have no permission to create tickets in that queue.');
+ }
+
+ my $due = new RT::Date( $session{'CurrentUser'} );
+ $due->Set( Format => 'unknown', Value => $ARGS{'Due'} );
+ my $starts = new RT::Date( $session{'CurrentUser'} );
+ $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} );
+
+ my @Requestors = split ( /,/, $ARGS{'Requestors'} );
+ my @Cc = split ( /,/, $ARGS{'Cc'} );
+ my @AdminCc = split ( /,/, $ARGS{'AdminCc'} );
+
+ my $MIMEObj = MakeMIMEEntity(
+ Subject => $ARGS{'Subject'},
+ From => $ARGS{'From'},
+ Cc => $ARGS{'Cc'},
+ Body => $ARGS{'Content'},
+ AttachmentFieldName => 'Attach'
+ );
+
+ my %create_args = (
+ Queue => $ARGS{Queue},
+ Owner => $ARGS{Owner},
+ InitialPriority => $ARGS{InitialPriority},
+ FinalPriority => $ARGS{FinalPriority},
+ TimeLeft => $ARGS{TimeLeft},
+ TimeWorked => $ARGS{TimeWorked},
+ Requestor => \@Requestors,
+ Cc => \@Cc,
+ AdminCc => \@AdminCc,
+ Subject => $ARGS{Subject},
+ Status => $ARGS{Status},
+ Due => $due->ISO,
+ Starts => $starts->ISO,
+ MIMEObj => $MIMEObj
+ );
+
+ # we need to get any KeywordSelect-<integer> fields into %create_args..
+ grep { $_ =~ /^KeywordSelect-/ &&{ $create_args{$_} = $ARGS{$_} } } %ARGS;
+
+ my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
+ unless ( $id && $Trans ) {
+ Abort($ErrMsg);
+ }
+ my @linktypes = qw( DependsOn MemberOf RefersTo );
+
+ foreach my $linktype (@linktypes) {
+ foreach my $luri ( split ( / /, $ARGS{"new-$linktype"} ) ) {
+ $luri =~ s/\s*$//; # Strip trailing whitespace
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Target => $luri,
+ Type => $linktype
+ );
+ push ( @Actions, $msg ) unless ($val);
+ }
+
+ foreach my $luri ( split ( / /, $ARGS{"$linktype-new"} ) ) {
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Base => $luri,
+ Type => $linktype
+ );
+
+ push ( @Actions, $msg ) unless ($val);
+ }
+ }
+
+ push ( @Actions, $ErrMsg );
+ unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) {
+ Abort( "No permission to view newly created ticket #"
+ . $Ticket->id . "." );
+ }
+ return ( $Ticket, @Actions );
+
+}
+
+# }}}
+
+# {{{ sub LoadTicket - loads a ticket
+
+=head2 LoadTicket id
+
+Takes a ticket id as its only variable. if it's handed an array, it takes
+the first value.
+
+Returns an RT::Ticket object as the current user.
+
+=cut
+
+sub LoadTicket {
+ my $id = shift;
+
+ if ( ref($id) eq "ARRAY" ) {
+ $id = $id->[0];
+ }
+
+ unless ($id) {
+ Abort("No ticket specified");
+ }
+
+ my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+ $Ticket->Load($id);
+ unless ( $Ticket->id ) {
+ Abort("Could not load ticket $id");
+ }
+ return $Ticket;
+}
+
+# }}}
+
+# {{{ sub ProcessUpdateMessage
+
+sub ProcessUpdateMessage {
+
+ #TODO document what else this takes.
+ my %args = (
+ ARGSRef => undef,
+ Actions => undef,
+ TicketObj => undef,
+ @_
+ );
+
+ #Make the update content have no 'weird' newlines in it
+ if ( $args{ARGSRef}->{'UpdateContent'} ) {
+
+ if (
+ $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() )
+ {
+ $args{ARGSRef}->{'UpdateSubject'} = undef;
+ }
+
+ my $Message = MakeMIMEEntity(
+ Subject => $args{ARGSRef}->{'UpdateSubject'},
+ Body => $args{ARGSRef}->{'UpdateContent'},
+ AttachmentFieldName => 'UpdateAttachment'
+ );
+
+ ## Check whether this was a refresh or not.
+
+ # Match Correspondence or Comments.
+ my $trans_flag = -2;
+ my $trans_type = undef;
+ my $orig_trans = $args{ARGSRef}->{'UpdateType'};
+ if ( $orig_trans =~ /^(private|public)$/ ) {
+ $trans_type = "Comment";
+ }elsif ( $orig_trans eq 'response' ) {
+ $trans_type = "Correspond";
+ }
+
+ # Do we have a transaction that we need to update on? session
+ if( defined( $trans_type ) ){
+ $trans_flag = 0;
+
+ # Prepare a checksum.
+ # See perldoc -f unpack for example of this.
+ my $this_checksum = unpack("%32C*", $Message->body_as_string ) % 65535;
+
+ # The above *could* generate duplicate checksums. Crosscheck with
+ # the length.
+ my $this_length = length( $Message->body_as_string );
+
+ # Don't forget the ticket id.
+ my $this_id = $args{TicketObj}->id;
+
+ # Check whether the previous transaction in the
+ # ticket is the same as the current transaction.
+ if( defined( $session{'prev_trans_type'} ) && defined( $session{'prev_trans_chksum'} ) && defined( $session{'prev_trans_length'} ) && defined( $session{'prev_trans_tickid'} ) ){
+ 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 ){
+ # Its the same as the previous transaction for this user.
+ $trans_flag = -1;
+ }
+ }
+
+ # Store them for next time.
+ $session{'prev_trans_type'} = $orig_trans;
+ $session{'prev_trans_chksum'} = $this_checksum;
+ $session{'prev_trans_length'} = $this_length;
+ $session{'prev_trans_tickid'} = $this_id;
+
+ if( $trans_flag == -1 ){
+ push ( @{ $args{'Actions'} },
+"This appears to be a duplicate of your previous update (please do not refresh this page)" );
+ }
+
+
+ if ( $trans_type eq 'Comment' && $trans_flag >= 0 ) {
+ my ( $Transaction, $Description ) = $args{TicketObj}->Comment(
+ CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
+ BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+ MIMEObj => $Message,
+ TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
+ );
+ push ( @{ $args{Actions} }, $Description );
+ }
+ elsif ( $trans_type eq 'Correspond' && $trans_flag >= 0 ) {
+ my ( $Transaction, $Description ) = $args{TicketObj}->Correspond(
+ CcMessageTo => $args{ARGSRef}->{'UpdateCc'},
+ BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+ MIMEObj => $Message,
+ TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}
+ );
+ push ( @{ $args{Actions} }, $Description );
+ }
+ }
+ else {
+ push ( @{ $args{'Actions'} },
+ "Update type was neither correspondence nor comment. Update not recorded"
+ );
+ }
+ }
+}
+
+# }}}
+
+# {{{ sub MakeMIMEEntity
+
+=head2 MakeMIMEEntity PARAMHASH
+
+Takes a paramhash Subject, Body and AttachmentFieldName.
+
+ Returns a MIME::Entity.
+
+=cut
+
+sub MakeMIMEEntity {
+
+ #TODO document what else this takes.
+ my %args = (
+ Subject => undef,
+ From => undef,
+ Cc => undef,
+ Body => undef,
+ AttachmentFieldName => undef,
+ @_
+ );
+
+ #Make the update content have no 'weird' newlines in it
+
+ $args{'Body'} =~ s/\r\n/\n/gs;
+ my $Message = MIME::Entity->build(
+ Subject => $args{'Subject'} || "",
+ From => $args{'From'},
+ Cc => $args{'Cc'},
+ Data => [ $args{'Body'} ]
+ );
+
+ my $cgi_object = CGIObject();
+ if ( $cgi_object->param( $args{'AttachmentFieldName'} ) ) {
+
+ my $cgi_filehandle =
+ $cgi_object->upload( $args{'AttachmentFieldName'} );
+
+ use File::Temp qw(tempfile tempdir);
+
+ #foreach my $filehandle (@filenames) {
+
+ # my ( $fh, $temp_file ) = tempfile();
+
+ #$binmode $fh; #thank you, windows
+
+ # We're having trouble with tempfiles not getting created. Let's try it with
+ # a scalar instead
+
+ my ( $buffer, @file );
+
+ while ( my $bytesread = read( $cgi_filehandle, $buffer, 4096 ) ) {
+ push ( @file, $buffer );
+ }
+
+ $RT::Logger->debug($file);
+ my $filename = "$cgi_filehandle";
+ $filename =~ s#^(.*)/##;
+ $filename =~ s#^(.*)\\##;
+ my $uploadinfo = $cgi_object->uploadInfo($cgi_filehandle);
+ $Message->attach(
+ Data => \@file,
+
+ #Path => $temp_file,
+ Filename => $filename,
+ Type => $uploadinfo->{'Content-Type'}
+ );
+
+ #close($fh);
+ #unlink($temp_file);
+
+ # }
+ }
+ $Message->make_singlepart();
+ return ($Message);
+
+}
+
+# }}}
+
+# {{{ sub ProcessSearchQuery
+
+=head2 ProcessSearchQuery
+
+ Takes a form such as the one filled out in webrt/Search/Elements/PickRestriction and turns it into something that RT::Tickets can understand.
+
+TODO Doc exactly what comes in the paramhash
+
+
+=cut
+
+sub ProcessSearchQuery {
+ my %args = @_;
+
+ ## TODO: The only parameter here is %ARGS. Maybe it would be
+ ## cleaner to load this parameter as $ARGS, and use $ARGS->{...}
+ ## instead of $args{ARGS}->{...} ? :)
+
+ #Searches are sticky.
+ if ( defined $session{'tickets'} ) {
+
+ # Reset the old search
+ $session{'tickets'}->GotoFirstItem;
+ }
+ else {
+
+ # Init a new search
+ $session{'tickets'} = RT::Tickets->new( $session{'CurrentUser'} );
+ }
+
+ #Import a bookmarked search if we have one
+ if ( defined $args{ARGS}->{'Bookmark'} ) {
+ $session{'tickets'}->ThawLimits( $args{ARGS}->{'Bookmark'} );
+ }
+
+ # {{{ Goto next/prev page
+ if ( $args{ARGS}->{'GotoPage'} eq 'Next' ) {
+ $session{'tickets'}->NextPage;
+ }
+ elsif ( $args{ARGS}->{'GotoPage'} eq 'Prev' ) {
+ $session{'tickets'}->PrevPage;
+ }
+
+ # }}}
+
+ # {{{ Deal with limiting the search
+
+ if ( $args{ARGS}->{'RefreshSearchInterval'} ) {
+ $session{'tickets_refresh_interval'} =
+ $args{ARGS}->{'RefreshSearchInterval'};
+ }
+
+ if ( $args{ARGS}->{'TicketsSortBy'} ) {
+ $session{'tickets_sort_by'} = $args{ARGS}->{'TicketsSortBy'};
+ $session{'tickets_sort_order'} = $args{ARGS}->{'TicketsSortOrder'};
+ $session{'tickets'}->OrderBy(
+ FIELD => $args{ARGS}->{'TicketsSortBy'},
+ ORDER => $args{ARGS}->{'TicketsSortOrder'}
+ );
+ }
+
+ # }}}
+
+ # {{{ Set the query limit
+ if ( defined $args{ARGS}->{'RowsPerPage'} ) {
+ $RT::Logger->debug(
+ "limiting to " . $args{ARGS}->{'RowsPerPage'} . " rows" );
+
+ $session{'tickets_rows_per_page'} = $args{ARGS}->{'RowsPerPage'};
+ $session{'tickets'}->RowsPerPage( $args{ARGS}->{'RowsPerPage'} );
+ }
+
+ # }}}
+ # {{{ Limit priority
+ if ( $args{ARGS}->{'ValueOfPriority'} ne '' ) {
+ $session{'tickets'}->LimitPriority(
+ VALUE => $args{ARGS}->{'ValueOfPriority'},
+ OPERATOR => $args{ARGS}->{'PriorityOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit owner
+ if ( $args{ARGS}->{'ValueOfOwner'} ne '' ) {
+ $session{'tickets'}->LimitOwner(
+ VALUE => $args{ARGS}->{'ValueOfOwner'},
+ OPERATOR => $args{ARGS}->{'OwnerOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit requestor email
+
+ if ( $args{ARGS}->{'ValueOfRequestor'} ne '' ) {
+ my $alias = $session{'tickets'}->LimitRequestor(
+ VALUE => $args{ARGS}->{'ValueOfRequestor'},
+ OPERATOR => $args{ARGS}->{'RequestorOp'},
+ );
+
+ }
+
+ # }}}
+ # {{{ Limit Queue
+ if ( $args{ARGS}->{'ValueOfQueue'} ne '' ) {
+ $session{'tickets'}->LimitQueue(
+ VALUE => $args{ARGS}->{'ValueOfQueue'},
+ OPERATOR => $args{ARGS}->{'QueueOp'}
+ );
+ }
+
+ # }}}
+ # {{{ Limit Status
+ if ( $args{ARGS}->{'ValueOfStatus'} ne '' ) {
+ if ( ref( $args{ARGS}->{'ValueOfStatus'} ) ) {
+ foreach my $value ( @{ $args{ARGS}->{'ValueOfStatus'} } ) {
+ $session{'tickets'}->LimitStatus(
+ VALUE => $value,
+ OPERATOR => $args{ARGS}->{'StatusOp'},
+ );
+ }
+ }
+ else {
+ $session{'tickets'}->LimitStatus(
+ VALUE => $args{ARGS}->{'ValueOfStatus'},
+ OPERATOR => $args{ARGS}->{'StatusOp'},
+ );
+ }
+
+ }
+
+ # }}}
+ # {{{ Limit Subject
+ if ( $args{ARGS}->{'ValueOfSubject'} ne '' ) {
+ $session{'tickets'}->LimitSubject(
+ VALUE => $args{ARGS}->{'ValueOfSubject'},
+ OPERATOR => $args{ARGS}->{'SubjectOp'},
+ );
+ }
+
+ # }}}
+ # {{{ Limit Dates
+ if ( $args{ARGS}->{'ValueOfDate'} ne '' ) {
+
+ my $date = ParseDateToISO( $args{ARGS}->{'ValueOfDate'} );
+ $args{ARGS}->{'DateType'} =~ s/_Date$//;
+
+ $session{'tickets'}->LimitDate(
+ FIELD => $args{ARGS}->{'DateType'},
+ VALUE => $date,
+ OPERATOR => $args{ARGS}->{'DateOp'},
+ );
+ }
+
+ # }}}
+ # {{{ Limit Content
+ if ( $args{ARGS}->{'ValueOfContent'} ne '' ) {
+ $session{'tickets'}->LimitContent(
+ VALUE => $args{ARGS}->{'ValueOfContent'},
+ OPERATOR => $args{ARGS}->{'ContentOp'},
+ );
+ }
+
+ # }}}
+ # {{{ Limit KeywordSelects
+
+ foreach my $KeywordSelectId (
+ map { /^KeywordSelect(\d+)$/; $1 }
+ grep { /^KeywordSelect(\d+)$/; } keys %{ $args{ARGS} }
+ )
+ {
+ my $form = $args{ARGS}->{"KeywordSelect$KeywordSelectId"};
+ my $oper = $args{ARGS}->{"KeywordSelectOp$KeywordSelectId"};
+ foreach my $KeywordId ( ref($form) ? @{$form} : ($form) ) {
+ next unless ($KeywordId);
+ my $quote = 1;
+ if ( $KeywordId =~ /^null$/i ) {
+
+ #Don't quote the string 'null'
+ $quote = 0;
+
+ # Convert the operator to something apropriate for nulls
+ $oper = 'IS' if ( $oper eq '=' );
+ $oper = 'IS NOT' if ( $oper eq '!=' );
+ }
+ $session{'tickets'}->LimitKeyword(
+ KEYWORDSELECT => $KeywordSelectId,
+ OPERATOR => $oper,
+ QUOTEVALUE => $quote,
+ KEYWORD => $KeywordId
+ );
+ }
+ }
+
+ # }}}
+
+}
+
+# }}}
+
+# {{{ sub ParseDateToISO
+
+=head2 ParseDateToISO
+
+Takes a date in an arbitrary format.
+Returns an ISO date and time in GMT
+
+=cut
+
+sub ParseDateToISO {
+ my $date = shift;
+
+ my $date_obj = new RT::Date($CurrentUser);
+ $date_obj->Set(
+ Format => 'unknown',
+ Value => $date
+ );
+ return ( $date_obj->ISO );
+}
+
+# }}}
+
+# {{{ sub Config
+# TODO: This might eventually read the cookies, user configuration
+# information from the DB, queue configuration information from the
+# DB, etc.
+
+sub Config {
+ my $args = shift;
+ my $key = shift;
+ return $args->{$key} || $RT::WebOptions{$key};
+}
+
+# }}}
+
+# {{{ sub ProcessACLChanges
+
+sub ProcessACLChanges {
+ my $ACLref = shift;
+ my $ARGSref = shift;
+
+ my @CheckACL = @$ACLref;
+ my %ARGS = %$ARGSref;
+
+ my ( $ACL, @results );
+
+ # {{{ Add rights
+ foreach $ACL (@CheckACL) {
+ my ($Principal);
+
+ next unless ($ACL);
+
+ # Parse out what we're really talking about.
+ if ( $ACL =~ /^(.*?)-(\d+)-(.*?)-(\d+)/ ) {
+ my $PrincipalType = $1;
+ my $PrincipalId = $2;
+ my $Scope = $3;
+ my $AppliesTo = $4;
+
+ # {{{ Create an object called Principal
+ # so we can do rights operations
+
+ if ( $PrincipalType eq 'User' ) {
+ $Principal = new RT::User( $session{'CurrentUser'} );
+ }
+ elsif ( $PrincipalType eq 'Group' ) {
+ $Principal = new RT::Group( $session{'CurrentUser'} );
+ }
+ else {
+ Abort("$PrincipalType unknown principal type");
+ }
+
+ $Principal->Load($PrincipalId)
+ || Abort("$PrincipalType $PrincipalId couldn't be loaded");
+
+ # }}}
+
+ # {{{ load up an RT::ACL object with the same current vals of this ACL
+
+ my $CurrentACL = new RT::ACL( $session{'CurrentUser'} );
+ if ( $Scope eq 'Queue' ) {
+ $CurrentACL->LimitToQueue($AppliesTo);
+ }
+ elsif ( $Scope eq 'System' ) {
+ $CurrentACL->LimitToSystem();
+ }
+
+ $CurrentACL->LimitPrincipalToType($PrincipalType);
+ $CurrentACL->LimitPrincipalToId($PrincipalId);
+
+ # }}}
+
+ # {{{ Get the values of the select we're working with
+ # into an array. it will contain all the new rights that have
+ # been granted
+ #Hack to turn the ACL returned into an array
+ my @rights =
+ ref( $ARGS{"GrantACE-$ACL"} ) eq 'ARRAY'
+ ? @{ $ARGS{"GrantACE-$ACL"} }
+ : ( $ARGS{"GrantACE-$ACL"} );
+
+ # }}}
+
+ # {{{ Add any rights we need.
+
+ foreach my $right (@rights) {
+ next unless ($right);
+
+ #if the right that's been selected wasn't there before, add it.
+ unless (
+ $CurrentACL->HasEntry(
+ RightScope => "$Scope",
+ RightName => "$right",
+ RightAppliesTo => "$AppliesTo",
+ PrincipalType => $PrincipalType,
+ PrincipalId => $Principal->Id
+ )
+ )
+ {
+
+ #Add new entry to list of rights.
+ if ( $Scope eq 'Queue' ) {
+ my $Queue = new RT::Queue( $session{'CurrentUser'} );
+ $Queue->Load($AppliesTo);
+ unless ( $Queue->id ) {
+ Abort("Couldn't find a queue called $AppliesTo");
+ }
+
+ my ( $val, $msg ) = $Principal->GrantQueueRight(
+ RightAppliesTo => $Queue->id,
+ RightName => "$right"
+ );
+
+ if ($val) {
+ push ( @results,
+ "Granted right $right to "
+ . $Principal->Name
+ . " for queue "
+ . $Queue->Name );
+ }
+ else {
+ push ( @results, $msg );
+ }
+ }
+ elsif ( $Scope eq 'System' ) {
+ my ( $val, $msg ) = $Principal->GrantSystemRight(
+ RightAppliesTo => $AppliesTo,
+ RightName => "$right"
+ );
+ if ($val) {
+ push ( @results, "Granted system right '$right' to "
+ . $Principal->Name );
+ }
+ else {
+ push ( @results, $msg );
+ }
+ }
+ }
+ }
+
+ # }}}
+ }
+ }
+
+ # }}} Add rights
+
+ # {{{ remove any rights that have been deleted
+
+ my @RevokeACE =
+ ref( $ARGS{"RevokeACE"} ) eq 'ARRAY'
+ ? @{ $ARGS{"RevokeACE"} }
+ : ( $ARGS{"RevokeACE"} );
+
+ foreach my $aceid (@RevokeACE) {
+
+ my $right = new RT::ACE( $session{'CurrentUser'} );
+ $right->Load($aceid);
+ next unless ( $right->id );
+
+ my $phrase = "Revoked "
+ . $right->PrincipalType . " "
+ . $right->PrincipalObj->Name
+ . "'s right to "
+ . $right->RightName;
+
+ if ( $right->RightScope eq 'System' ) {
+ $phrase .= ' across all queues.';
+ }
+ else {
+ $phrase .= ' for the queue ' . $right->AppliesToObj->Name . '.';
+ }
+ my ( $val, $msg ) = $right->Delete();
+ if ($val) {
+ push ( @results, $phrase );
+ }
+ else {
+ push ( @results, $msg );
+ }
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub UpdateRecordObj
+
+=head2 UpdateRecordObj ( ARGSRef => \%ARGS, Object => RT::Record, AttributesRef => \@attribs)
+
+@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.
+
+Returns an array of success/failure messages
+
+=cut
+
+sub UpdateRecordObject {
+ my %args = (
+ ARGSRef => undef,
+ AttributesRef => undef,
+ Object => undef,
+ @_
+ );
+
+ my (@results);
+
+ my $object = $args{'Object'};
+ my $attributes = $args{'AttributesRef'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ foreach $attribute (@$attributes) {
+ if ( ( defined $ARGSRef->{"$attribute"} )
+ and ( $ARGSRef->{"$attribute"} ne $object->$attribute() ) )
+ {
+ $ARGSRef->{"$attribute"} =~ s/\r\n/\n/gs;
+
+ my $method = "Set$attribute";
+ my ( $code, $msg ) = $object->$method( $ARGSRef->{"$attribute"} );
+ push @results, "$attribute: $msg";
+ }
+ }
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketBasics
+
+=head2 ProcessTicketBasics ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketBasics {
+
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $TicketObj = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ # {{{ Set basic fields
+ my @attribs = qw(
+ Subject
+ FinalPriority
+ Priority
+ TimeWorked
+ TimeLeft
+ Status
+ Queue
+ );
+
+ if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
+ my $tempqueue = RT::Queue->new($RT::SystemUser);
+ $tempqueue->Load( $ARGSRef->{'Queue'} );
+ if ( $tempqueue->id ) {
+ $ARGSRef->{'Queue'} = $tempqueue->Id();
+ }
+ }
+
+ my @results = UpdateRecordObject(
+ AttributesRef => \@attribs,
+ Object => $TicketObj,
+ ARGSRef => $ARGSRef
+ );
+
+ # We special case owner changing, so we can use ForceOwnerChange
+ if ( $ARGSRef->{'Owner'} && ( $TicketObj->Owner ne $ARGSRef->{'Owner'} ) ) {
+ my ($ChownType);
+ if ( $ARGSRef->{'ForceOwnerChange'} ) {
+ $ChownType = "Force";
+ }
+ else {
+ $ChownType = "Give";
+ }
+
+ my ( $val, $msg ) =
+ $TicketObj->SetOwner( $ARGSRef->{'Owner'}, $ChownType );
+ push ( @results, "$msg" );
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketWatchers
+
+=head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketWatchers {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+ my (@results);
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ # {{{ Munge watchers
+
+ foreach my $key ( keys %$ARGSRef ) {
+
+ # Delete deletable watchers
+ if ( ( $key =~ /^DelWatcher(\d*)$/ ) and ( $ARGSRef->{$key} ) ) {
+ my ( $code, $msg ) = $Ticket->DeleteWatcher($1);
+ push @results, $msg;
+ }
+
+ # Delete watchers in the simple style demanded by the bulk manipulator
+ elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {
+ my ( $code, $msg ) = $Ticket->DeleteWatcher( $ARGSRef->{$key}, $1 );
+ push @results, $msg;
+ }
+
+ # Add new wathchers by email address
+ elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
+ and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) )
+ {
+
+ #They're in this order because otherwise $1 gets clobbered :/
+ my ( $code, $msg ) = $Ticket->AddWatcher(
+ Type => $ARGSRef->{$key},
+ Email => $ARGSRef->{ "WatcherAddressEmail" . $1 }
+ );
+ push @results, $msg;
+ }
+
+ #Add requestors in the simple style demanded by the bulk manipulator
+ elsif ( $key =~ /^Add(Requestor|Cc|AdminCc)$/ ) {
+ my ( $code, $msg ) = $Ticket->AddWatcher(
+ Type => $1,
+ Email => $ARGSRef->{$key}
+ );
+ push @results, $msg;
+ }
+
+ # Add new watchers by owner
+ elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
+ and ( $key =~ /^WatcherTypeUser(\d*)$/ ) )
+ {
+
+ #They're in this order because otherwise $1 gets clobbered :/
+ my ( $code, $msg ) =
+ $Ticket->AddWatcher( Type => $ARGSRef->{$key}, Owner => $1 );
+ push @results, $msg;
+ }
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketDates
+
+=head2 ProcessTicketDates ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketDates {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my (@results);
+
+ # {{{ Set date fields
+ my @date_fields = qw(
+ Told
+ Resolved
+ Starts
+ Started
+ Due
+ );
+
+ #Run through each field in this list. update the value if apropriate
+ foreach $field (@date_fields) {
+ my ( $code, $msg );
+
+ my $DateObj = RT::Date->new( $session{'CurrentUser'} );
+
+ #If it's something other than just whitespace
+ if ( $ARGSRef->{ $field . '_Date' } ne '' ) {
+ $DateObj->Set(
+ Format => 'unknown',
+ Value => $ARGSRef->{ $field . '_Date' }
+ );
+ my $obj = $field . "Obj";
+ if ( ( defined $DateObj->Unix )
+ and ( $DateObj->Unix ne $Ticket->$obj()->Unix() ) )
+ {
+ my $method = "Set$field";
+ my ( $code, $msg ) = $Ticket->$method( $DateObj->ISO );
+ push @results, "$msg";
+ }
+ }
+ }
+
+ # }}}
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketLinks
+
+=head2 ProcessTicketLinks ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketLinks {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $Ticket = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my (@results);
+
+ # Delete links that are gone gone gone.
+ foreach my $arg ( keys %$ARGSRef ) {
+ if ( $arg =~ /DeleteLink-(.*?)-(DependsOn|MemberOf|RefersTo)-(.*)$/ ) {
+ my $base = $1;
+ my $type = $2;
+ my $target = $3;
+
+ push @results,
+ "Trying to delete: Base: $base Target: $target Type $type";
+ my ( $val, $msg ) = $Ticket->DeleteLink(
+ Base => $base,
+ Type => $type,
+ Target => $target
+ );
+
+ push @results, $msg;
+
+ }
+
+ }
+
+ my @linktypes = qw( DependsOn MemberOf RefersTo );
+
+ foreach my $linktype (@linktypes) {
+
+ for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) )
+ {
+ $luri =~ s/\s*$//; # Strip trailing whitespace
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Target => $luri,
+ Type => $linktype
+ );
+ push @results, $msg;
+ }
+
+ for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) )
+ {
+ my ( $val, $msg ) = $Ticket->AddLink(
+ Base => $luri,
+ Type => $linktype
+ );
+
+ push @results, $msg;
+ }
+ }
+
+ #Merge if we need to
+ if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
+ my ( $val, $msg ) =
+ $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
+ push @results, $msg;
+ }
+
+ return (@results);
+}
+
+# }}}
+
+# {{{ sub ProcessTicketObjectKeywords
+
+=head2 ProcessTicketObjectKeywords ( TicketObj => $Ticket, ARGSRef => \%ARGS );
+
+Returns an array of results messages.
+
+=cut
+
+sub ProcessTicketObjectKeywords {
+ my %args = (
+ TicketObj => undef,
+ ARGSRef => undef,
+ @_
+ );
+
+ my $TicketObj = $args{'TicketObj'};
+ my $ARGSRef = $args{'ARGSRef'};
+
+ my (@results);
+
+ # {{{ set ObjectKeywords.
+
+ my $KeywordSelects = $TicketObj->QueueObj->KeywordSelects;
+
+ # iterate through all the keyword selects for this queue
+ while ( my $KeywordSelect = $KeywordSelects->Next ) {
+
+ # {{{ do some setup
+
+ # if we have KeywordSelectMagic for this keywordselect:
+ next
+ unless
+ defined $ARGSRef->{ 'KeywordSelectMagic' . $KeywordSelect->id };
+
+ # Lets get a hash of the possible values to work with
+ my $value = $ARGSRef->{ 'KeywordSelect' . $KeywordSelect->id } || [];
+
+ #lets get all those values in a hash. regardless of # of entries
+ #we'll use this for adding and deleting keywords from this object.
+ my %values = map { $_ => 1 } ref($value) ? @{$value} : ($value);
+
+ # Load up the ObjectKeywords for this KeywordSelect for this ticket
+ my $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id );
+
+ # }}}
+ # {{{ add new keywords
+
+ foreach my $key ( keys %values ) {
+
+ #unless the ticket has that keyword for that keyword select,
+ unless ( $ObjectKeys->HasEntry($key) ) {
+
+ #Add the keyword
+ my ( $result, $msg ) = $TicketObj->AddKeyword(
+ Keyword => $key,
+ KeywordSelect => $KeywordSelect->id
+ );
+ push ( @results, $msg );
+ }
+ }
+
+ # }}}
+ # {{{ Delete unused keywords
+
+ #redo this search, so we don't ask it to delete things that are already gone
+ # such as when a single keyword select gets its value changed.
+ $ObjectKeys = $TicketObj->KeywordsObj( $KeywordSelect->id );
+
+ while ( my $TicketKey = $ObjectKeys->Next ) {
+
+ # if the hash defined above doesn\'t contain the keyword mentioned,
+ unless ( $values{ $TicketKey->Keyword } ) {
+
+ #I'd really love to just call $keyword->Delete, but then
+ # we wouldn't get a transaction recorded
+ my ( $result, $msg ) = $TicketObj->DeleteKeyword(
+ Keyword => $TicketKey->Keyword,
+ KeywordSelect => $KeywordSelect->id
+ );
+ push ( @results, $msg );
+ }
+ }
+
+ # }}}
+ }
+
+ #Iterate through the keyword selects for BulkManipulator style access
+ while ( my $KeywordSelect = $KeywordSelects->Next ) {
+ if ( $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id } ) {
+
+ #Add the keyword
+ my ( $result, $msg ) = $TicketObj->AddKeyword(
+ Keyword =>
+ $ARGSRef->{ "AddToKeywordSelect" . $KeywordSelect->Id },
+ KeywordSelect => $KeywordSelect->id
+ );
+ push ( @results, $msg );
+ }
+ if ( $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id } ) {
+
+ #Delete the keyword
+ my ( $result, $msg ) = $TicketObj->DeleteKeyword(
+ Keyword =>
+ $ARGSRef->{ "DeleteFromKeywordSelect" . $KeywordSelect->Id },
+ KeywordSelect => $KeywordSelect->id
+ );
+ push ( @results, $msg );
+ }
+ }
+
+ # }}}
+
+ return (@results);
+}
+
+# }}}
+
+1;