X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FTicket.pm;h=068eec09da1232245e6a14c16f5e43cc09dbbcd3;hp=5f76e055f71ff640e79d81e47e1b0db123e6cda7;hb=919e930aa9279b3c5cd12b593889cd6de79d67bf;hpb=06fb1346ff8076a84f743fa07de31852942e144f diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm index 5f76e055f..068eec09d 100755 --- a/rt/lib/RT/Ticket.pm +++ b/rt/lib/RT/Ticket.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -54,7 +54,7 @@ =head1 DESCRIPTION -This module lets you manipulate RT\'s ticket object. +This module lets you manipulate RT's ticket object. =head1 METHODS @@ -197,18 +197,18 @@ Arguments: ARGS is a hash of named parameters. Valid parameters are: AdminCc - A reference to a list of email addresses or Names SquelchMailTo - A reference to a list of email addresses - who should this ticket not mail - Type -- The ticket\'s type. ignore this for now - Owner -- This ticket\'s owner. either an RT::User object or this user\'s id + Type -- The ticket's type. ignore this for now + Owner -- This ticket's owner. either an RT::User object or this user's id Subject -- A string describing the subject of the ticket Priority -- an integer from 0 to 99 InitialPriority -- an integer from 0 to 99 FinalPriority -- an integer from 0 to 99 - Status -- any valid status (Defined in RT::Queue) + Status -- any valid status for Queue's Lifecycle, otherwises uses on_create from Lifecycle default TimeEstimated -- an integer. estimated time for this task in minutes TimeWorked -- an integer. time worked so far in minutes TimeLeft -- an integer. time remaining in minutes - Starts -- an ISO date describing the ticket\'s start date and time in GMT - Due -- an ISO date describing the ticket\'s due date and time in GMT + Starts -- an ISO date describing the ticket's start date and time in GMT + Due -- an ISO date describing the ticket's due date and time in GMT MIMEObj -- a MIME::Entity object with the content of the initial ticket request. CustomField- -- a scalar or array of values for the customfield with the id @@ -255,6 +255,7 @@ sub Create { Starts => undef, Started => undef, Resolved => undef, + WillResolve => undef, MIMEObj => undef, _RecordTransaction => 1, DryRun => 0, @@ -299,6 +300,7 @@ sub Create { $args{'Status'} = $cycle->DefaultOnCreate; } + $args{'Status'} = lc $args{'Status'}; unless ( $cycle->IsValid( $args{'Status'} ) ) { return ( 0, 0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", @@ -356,6 +358,11 @@ sub Create { $Started->Set( Format => 'ISO', Value => $args{'Started'} ); } + my $WillResolve = RT::Date->new($self->CurrentUser ); + if ( defined $args{'WillResolve'} ) { + $WillResolve->Set( Format => 'ISO', Value => $args{'WillResolve'} ); + } + # If the status is not an initial status, set the started date elsif ( !$cycle->IsInitial($args{'Status'}) ) { $Started->SetToNow; @@ -460,6 +467,11 @@ sub Create { } } + $args{'Type'} = lc $args{'Type'} + if $args{'Type'} =~ /^(ticket|approval|reminder)$/i; + + $args{'Subject'} =~ s/\n//g; + $RT::Handle->BeginTransaction(); my %params = ( @@ -477,6 +489,7 @@ sub Create { Starts => $Starts->ISO, Started => $Started->ISO, Resolved => $Resolved->ISO, + WillResolve => $WillResolve->ISO, Due => $Due->ISO ); @@ -629,7 +642,7 @@ sub Create { } } - if ( $obj && $obj->Status eq 'deleted' ) { + if ( $obj && lc $obj->Status eq 'deleted' ) { push @non_fatal_errors, $self->loc("Linking. Can't link to a deleted ticket"); next; @@ -783,6 +796,15 @@ sub Create { } } +sub SetType { + my $self = shift; + my $value = shift; + + # Force lowercase on internal RT types + $value = lc $value + if $value =~ /^(ticket|approval|reminder)$/i; + return $self->_Set(Field => 'Type', Value => $value, @_); +} @@ -836,10 +858,10 @@ sub _Parse822HeadersForAttributes { } $args{$date} = $dateobj->ISO; } - $args{'mimeobj'} = MIME::Entity->new(); - $args{'mimeobj'}->build( - Type => ( $args{'contenttype'} || 'text/plain' ), - Data => ($args{'content'} || '') + $args{'mimeobj'} = MIME::Entity->build( + Type => ( $args{'contenttype'} || 'text/plain' ), + Charset => "UTF-8", + Data => Encode::encode("UTF-8", ($args{'content'} || '')) ); return (%args); @@ -850,8 +872,8 @@ sub _Parse822HeadersForAttributes { =head2 Import PARAMHASH Import a ticket. -Doesn\'t create a transaction. -Doesn\'t supply queue defaults, etc. +Doesn't create a transaction. +Doesn't supply queue defaults, etc. Returns: TICKETID @@ -885,7 +907,7 @@ sub Import { $QueueObj = RT::Queue->new(RT->SystemUser); $QueueObj->Load( $args{'Queue'} ); - #TODO error check this and return 0 if it\'s not loading properly +++ + #TODO error check this and return 0 if it's not loading properly +++ } elsif ( ref( $args{'Queue'} ) eq 'RT::Queue' ) { $QueueObj = RT::Queue->new(RT->SystemUser); @@ -1103,7 +1125,7 @@ PrincipalId The RT::Principal id of the user or group that's being added as a wa Email The email address of the new watcher. If a user with this email address can't be found, a new nonprivileged user will be created. -If the watcher you\'re trying to set has an RT account, set the PrincipalId paremeter to their User Id. Otherwise, set the Email parameter to their Email address. +If the watcher you're trying to set has an RT account, set the PrincipalId paremeter to their User Id. Otherwise, set the Email parameter to their Email address. =cut @@ -1206,7 +1228,8 @@ sub _AddWatcher { if ( $group->HasMember( $principal)) { - return ( 0, $self->loc('That principal is already a [_1] for this ticket', $self->loc($args{'Type'})) ); + return ( 0, $self->loc('[_1] is already a [_2] for this ticket', + $principal->Object->Name, $self->loc($args{'Type'})) ); } @@ -1215,7 +1238,8 @@ sub _AddWatcher { unless ($m_id) { $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg); - return ( 0, $self->loc('Could not make that principal a [_1] for this ticket', $self->loc($args{'Type'})) ); + return ( 0, $self->loc('Could not make [_1] a [_2] for this ticket', + $principal->Object->Name, $self->loc($args{'Type'})) ); } unless ( $args{'Silent'} ) { @@ -1226,7 +1250,8 @@ sub _AddWatcher { ); } - return ( 1, $self->loc('Added principal as a [_1] for this ticket', $self->loc($args{'Type'})) ); + return ( 1, $self->loc('Added [_1] as a [_2] for this ticket', + $principal->Object->Name, $self->loc($args{'Type'})) ); } @@ -1325,8 +1350,8 @@ sub DeleteWatcher { unless ( $group->HasMember($principal) ) { return ( 0, - $self->loc( 'That principal is not a [_1] for this ticket', - $args{'Type'} ) ); + $self->loc( '[_1] is not a [_2] for this ticket', + $principal->Object->Name, $args{'Type'} ) ); } my ( $m_id, $m_msg ) = $group->_DeleteMember( $principal->Id ); @@ -1339,8 +1364,8 @@ sub DeleteWatcher { return (0, $self->loc( - 'Could not remove that principal as a [_1] for this ticket', - $args{'Type'} ) ); + 'Could not remove [_1] as a [_2] for this ticket', + $principal->Object->Name, $args{'Type'} ) ); } unless ( $args{'Silent'} ) { @@ -1421,7 +1446,7 @@ sub UnsquelchMailTo { =head2 RequestorAddresses - B String: All Ticket Requestor email addresses as a string. +B String: All Ticket Requestor email addresses as a string. =cut @@ -1794,7 +1819,7 @@ sub SetQueue { unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) { return ( 0, $self->loc("There is no mapping for statuses between these queues. Contact your system administrator.") ); } - $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ $self->Status }; + $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ lc $self->Status }; return ( 0, $self->loc("Mapping between queues' lifecycles is incomplete. Contact your system administrator.") ) unless $new_status; } @@ -1891,6 +1916,13 @@ sub QueueObj { return ($self->{_queue_obj}); } +sub SetSubject { + my $self = shift; + my $value = shift; + $value =~ s/\n//g; + return $self->_Set( Field => 'Subject', Value => $value ); +} + =head2 SubjectTag Takes nothing. Returns SubjectTag for this ticket. Includes @@ -2256,6 +2288,11 @@ sub Correspond { my @results = $self->_RecordNote(%args); + unless ( $results[0] ) { + $RT::Handle->Rollback(); + return @results; + } + #Set the last told date to now if this isn't mail from the requestor. #TODO: Note that this will wrongly ack mail from any non-requestor as a "told" unless ( $self->IsRequestor($self->CurrentUser->id) ) { @@ -2307,11 +2344,17 @@ sub _RecordNote { } unless ( $args{'MIMEObj'} ) { + my $data = ref $args{'Content'}? $args{'Content'} : [ $args{'Content'} ]; $args{'MIMEObj'} = MIME::Entity->build( - Data => ( ref $args{'Content'}? $args{'Content'}: [ $args{'Content'} ] ) + Type => "text/plain", + Charset => "UTF-8", + Data => [ map {Encode::encode("UTF-8", $_)} @{$data} ], ); } + $args{'MIMEObj'}->head->replace('X-RT-Interface' => 'API') + unless $args{'MIMEObj'}->head->get('X-RT-Interface'); + # convert text parts into utf-8 RT::I18N::SetMIMEEntityToUTF8( $args{'MIMEObj'} ); @@ -2327,13 +2370,13 @@ sub _RecordNote { my $addresses = join ', ', ( map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse( $args{ $type . 'MessageTo' } ) ); - $args{'MIMEObj'}->head->replace( 'RT-Send-' . $type, Encode::encode_utf8( $addresses ) ); + $args{'MIMEObj'}->head->replace( 'RT-Send-' . $type, Encode::encode( "UTF-8", $addresses ) ); } } foreach my $argument (qw(Encrypt Sign)) { $args{'MIMEObj'}->head->replace( - "X-RT-$argument" => Encode::encode_utf8( $args{ $argument } ) + "X-RT-$argument" => Encode::encode( "UTF-8", $args{ $argument } ) ) if defined $args{ $argument }; } @@ -2341,10 +2384,10 @@ sub _RecordNote { # internal Message-ID now, so all emails sent because of this # message have a common Message-ID my $org = RT->Config->Get('Organization'); - my $msgid = $args{'MIMEObj'}->head->get('Message-ID'); + my $msgid = Encode::decode( "UTF-8", $args{'MIMEObj'}->head->get('Message-ID') ); unless (defined $msgid && $msgid =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>/) { $args{'MIMEObj'}->head->set( - 'RT-Message-ID' => Encode::encode_utf8( + 'RT-Message-ID' => Encode::encode( "UTF-8", RT::Interface::Email::GenMessageId( Ticket => $self ) ) ); @@ -2353,7 +2396,7 @@ sub _RecordNote { #Record the correspondence (write the transaction) my ( $Trans, $msg, $TransObj ) = $self->_NewTransaction( Type => $args{'NoteType'}, - Data => ( $args{'MIMEObj'}->head->get('subject') || 'No Subject' ), + Data => ( Encode::decode( "UTF-8", $args{'MIMEObj'}->head->get('Subject') ) || 'No Subject' ), TimeTaken => $args{'TimeTaken'}, MIMEObj => $args{'MIMEObj'}, CommitScrips => $args{'CommitScrips'}, @@ -2389,10 +2432,10 @@ sub DryRun { } my $Message = MIME::Entity->build( + Subject => defined $args{UpdateSubject} ? Encode::encode( "UTF-8", $args{UpdateSubject} ) : "", Type => 'text/plain', - Subject => defined $args{UpdateSubject} ? Encode::encode_utf8( $args{UpdateSubject} ) : "", Charset => 'UTF-8', - Data => $args{'UpdateContent'} || "", + Data => Encode::encode("UTF-8", $args{'UpdateContent'} || ""), ); my ( $Transaction, $Description, $Object ) = $self->$action( @@ -2421,12 +2464,12 @@ sub DryRunCreate { my $self = shift; my %args = @_; my $Message = MIME::Entity->build( - Type => 'text/plain', - Subject => defined $args{Subject} ? Encode::encode_utf8( $args{'Subject'} ) : "", + Subject => defined $args{Subject} ? Encode::encode( "UTF-8", $args{'Subject'} ) : "", (defined $args{'Cc'} ? - ( Cc => Encode::encode_utf8( $args{'Cc'} ) ) : ()), + ( Cc => Encode::encode( "UTF-8", $args{'Cc'} ) ) : ()), + Type => 'text/plain', Charset => 'UTF-8', - Data => $args{'Content'} || "", + Data => Encode::encode( "UTF-8", $args{'Content'} || ""), ); my ( $Transaction, $Object, $Description ) = $self->Create( @@ -2502,7 +2545,7 @@ sub _Links { Delete a link. takes a paramhash of Base, Target, Type, Silent, SilentBase and SilentTarget. Either Base or Target must be null. -The null value will be replaced with this ticket\'s id. +The null value will be replaced with this ticket's id. If Silent is true then no transaction would be recorded, in other case you can control creation of transactions on both base and @@ -2641,7 +2684,7 @@ sub AddLink { } return ( 0, "Can't link to a deleted ticket" ) - if $other_ticket && $other_ticket->Status eq 'deleted'; + if $other_ticket && lc $other_ticket->Status eq 'deleted'; return $self->_AddLink(%args); } @@ -2653,9 +2696,7 @@ sub __GetTicketFromURI { # If the other URI is an RT::Ticket, we want to make sure the user # can modify it too... my $uri_obj = RT::URI->new( $self->CurrentUser ); - $uri_obj->FromURI( $args{'URI'} ); - - unless ( $uri_obj->Resolver && $uri_obj->Scheme ) { + unless ($uri_obj->FromURI( $args{'URI'} )) { my $msg = $self->loc( "Couldn't resolve '[_1]' into a URI.", $args{'URI'} ); $RT::Logger->warning( $msg ); return( 0, $msg ); @@ -3196,11 +3237,16 @@ sub ValidateStatus { return 0; } - +sub Status { + my $self = shift; + my $value = $self->_Value( 'Status' ); + return $value unless $self->QueueObj; + return $self->QueueObj->Lifecycle->CanonicalCase( $value ); +} =head2 SetStatus STATUS -Set this ticket\'s status. STATUS can be one of: new, open, stalled, resolved, rejected or deleted. +Set this ticket's status. STATUS can be one of: new, open, stalled, resolved, rejected or deleted. Alternatively, you can pass in a list of named parameters (Status => STATUS, Force => FORCE, SetStarted => SETSTARTED ). If FORCE is true, ignore unresolved dependencies and force a status change. @@ -3226,7 +3272,7 @@ sub SetStatus { my $lifecycle = $self->QueueObj->Lifecycle; - my $new = $args{'Status'}; + my $new = lc $args{'Status'}; unless ( $lifecycle->IsValid( $new ) ) { return (0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", $self->loc($new))); } @@ -3274,7 +3320,7 @@ sub SetStatus { #Actually update the status my ($val, $msg)= $self->_Set( Field => 'Status', - Value => $args{Status}, + Value => $new, TimeTaken => 0, CheckACL => 0, TransactionType => 'Status', @@ -3578,6 +3624,9 @@ sub _Set { OldValue => $Old, TimeTaken => $args{'TimeTaken'}, ); + # Ensure that we can read the transaction, even if the change + # just made the ticket unreadable to us + $TransObj->{ _object_is_readable } = 1; return ( $Trans, scalar $TransObj->BriefDescription ); } else { @@ -3790,37 +3839,29 @@ sub TransactionCustomFields { } +=head2 LoadCustomFieldByIdentifier -=head2 CustomFieldValues - -# Do name => id mapping (if needed) before falling back to -# RT::Record's CustomFieldValues - -See L +Finds and returns the custom field of the given name for the ticket, +overriding L to look for +queue-specific CFs before global ones. =cut -sub CustomFieldValues { +sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; - return $self->SUPER::CustomFieldValues( $field ) if !$field || $field =~ /^\d+$/; + return $self->SUPER::LoadCustomFieldByIdentifier($field) + if ref $field or $field =~ /^\d+$/; my $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self ); $cf->LoadByNameAndQueue( Name => $field, Queue => $self->Queue ); - unless ( $cf->id ) { - $cf->LoadByNameAndQueue( Name => $field, Queue => 0 ); - } - - # If we didn't find a valid cfid, give up. - return RT::ObjectCustomFieldValues->new( $self->CurrentUser ) unless $cf->id; - - return $self->SUPER::CustomFieldValues( $cf->id ); + $cf->LoadByNameAndQueue( Name => $field, Queue => 0 ) unless $cf->id; + return $cf; } - =head2 CustomFieldLookupType Returns the RT::Ticket lookup type, which can be passed to