X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FTransaction_Overlay.pm;h=469cf0872d470a348ba3c56b1c8aa73371a98373;hb=d32f4c43b0fde5c18b8c2ee8f3d4cb9c6861a403;hp=d49e8d34eb4a2217e3065de10aa70f5e531d8751;hpb=9c68254528b6f2c7d8c1921b452fa56064783782;p=freeside.git diff --git a/rt/lib/RT/Transaction_Overlay.pm b/rt/lib/RT/Transaction_Overlay.pm index d49e8d34e..469cf0872 100644 --- a/rt/lib/RT/Transaction_Overlay.pm +++ b/rt/lib/RT/Transaction_Overlay.pm @@ -1,38 +1,40 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC +# +# # (Except where explicitly superseded by other copyright notices) -# -# +# +# # LICENSE: -# +# # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -# -# +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) -# +# # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that @@ -41,7 +43,7 @@ # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. -# +# # END BPS TAGGED BLOCK }}} =head1 NAME @@ -63,11 +65,6 @@ It can have arbitrary MIME attachments. =head1 METHODS -=begin testing - -ok(require RT::Transaction); - -=end testing =cut @@ -77,10 +74,14 @@ package RT::Transaction; use strict; no warnings qw(redefine); -use vars qw( %_BriefDescriptions ); +use vars qw( %_BriefDescriptions $PreferredContentType ); use RT::Attachments; use RT::Scrips; +use RT::Ruleset; + +use HTML::FormatText; +use HTML::TreeBuilder; # {{{ sub Create @@ -109,12 +110,13 @@ sub Create { NewValue => undef, MIMEObj => undef, ActivateScrips => 1, - CommitScrips => 1, - ObjectType => 'RT::Ticket', - ObjectId => 0, - ReferenceType => undef, - OldReference => undef, - NewReference => undef, + CommitScrips => 1, + ObjectType => 'RT::Ticket', + ObjectId => 0, + ReferenceType => undef, + OldReference => undef, + NewReference => undef, + CustomFields => {}, @_ ); @@ -129,21 +131,21 @@ sub Create { #lets create our transaction my %params = ( - Type => $args{'Type'}, - Data => $args{'Data'}, - Field => $args{'Field'}, - OldValue => $args{'OldValue'}, - NewValue => $args{'NewValue'}, - Created => $args{'Created'}, - ObjectType => $args{'ObjectType'}, - ObjectId => $args{'ObjectId'}, + Type => $args{'Type'}, + Data => $args{'Data'}, + Field => $args{'Field'}, + OldValue => $args{'OldValue'}, + NewValue => $args{'NewValue'}, + Created => $args{'Created'}, + ObjectType => $args{'ObjectType'}, + ObjectId => $args{'ObjectId'}, ReferenceType => $args{'ReferenceType'}, - OldReference => $args{'OldReference'}, - NewReference => $args{'NewReference'}, + OldReference => $args{'OldReference'}, + NewReference => $args{'NewReference'}, ); # Parameters passed in during an import that we probably don't want to touch, otherwise - foreach my $attr qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy) { + foreach my $attr (qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy)) { $params{$attr} = $args{$attr} if ($args{$attr}); } @@ -157,6 +159,10 @@ sub Create { } } + # Set up any custom fields passed at creation. Has to happen + # before scrips. + + $self->UpdateCustomFields(%{ $args{'CustomFields'} }); #Provide a way to turn off scrips if we need to $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id); @@ -171,9 +177,21 @@ sub Create { Ticket => $args{'ObjectId'}, Transaction => $self->id, ); + + # Entry point of the rule system + my $ticket = RT::Ticket->new($RT::SystemUser); + $ticket->Load($args{'ObjectId'}); + my $rules = $self->{rules} = RT::Ruleset->FindAllRules( + Stage => 'TransactionCreate', + Type => $args{'Type'}, + TicketObj => $ticket, + TransactionObj => $self, + ); + if ($args{'CommitScrips'} ) { $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id); $self->{'scrips'}->Commit(); + RT::Ruleset->CommitRules($rules); } } @@ -198,6 +216,22 @@ sub Scrips { } +=head2 Rules + +Returns the array of Rule objects for this transaction. +This routine is only useful on a freshly created transaction object. +Rules do not get persisted to the database with transactions. + + +=cut + + +sub Rules { + my $self = shift; + return($self->{'rules'}); +} + + # {{{ sub Delete =head2 Delete @@ -238,26 +272,28 @@ sub Delete { =head2 Message - Returns the RT::Attachments Object which contains the "top-level"object - attachment for this transaction +Returns the L object which contains the "top-level" object +attachment for this transaction. =cut sub Message { - my $self = shift; + + # XXX: Where is ACL check? - if ( !defined( $self->{'message'} ) ) { + unless ( defined $self->{'message'} ) { - $self->{'message'} = new RT::Attachments( $self->CurrentUser ); + $self->{'message'} = RT::Attachments->new( $self->CurrentUser ); $self->{'message'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id ); - $self->{'message'}->ChildrenOf(0); + } else { + $self->{'message'}->GotoFirstItem; } - return ( $self->{'message'} ); + return $self->{'message'}; } # }}} @@ -266,27 +302,59 @@ sub Message { =head2 Content PARAMHASH -If this transaction has attached mime objects, returns the first text/plain part. -Otherwise, returns undef. +If this transaction has attached mime objects, returns the body of the first +textual part (as defined in RT::I18N::IsTextualContentType). Otherwise, +returns undef. Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message -at $args{'Wrap'}. $args{'Wrap'} defaults to 70. +at $args{'Wrap'}. $args{'Wrap'} defaults to $RT::MessageBoxWidth - 2 or 70. +If $args{'Type'} is set to C, this will return an HTML +part of the message, if available. Otherwise it looks for a text/plain +part. If $args{'Type'} is missing, it defaults to the value of +C<$RT::Transaction::PreferredContentType>, if that's missing too, +defaults to textual. =cut sub Content { my $self = shift; my %args = ( + Type => $PreferredContentType || '', Quote => 0, Wrap => 70, + Wrap => ( $RT::MessageBoxWidth || 72 ) - 2, @_ ); my $content; - my $content_obj = $self->ContentObj; - if ($content_obj) { - $content = $content_obj->Content; + if ( my $content_obj = + $self->ContentObj( $args{Type} ? ( Type => $args{Type} ) : () ) ) + { + $content = $content_obj->Content ||''; + + if ( lc $content_obj->ContentType eq 'text/html' ) { + $content =~ s/

--\s+
.*?$//s if $args{'Quote'}; + + if ($args{Type} ne 'text/html') { + my $tree = HTML::TreeBuilder->new_from_content( $content ); + $content = HTML::FormatText->new( + leftmargin => 0, + rightmargin => 78, + )->format( $tree); + $tree->delete; + } + } + else { + $content =~ s/\n-- \n.*?$//s if $args{'Quote'}; + if ($args{Type} eq 'text/html') { + # Extremely simple text->html converter + $content =~ s/&/&/g; + $content =~ s//>/g; + $content = "

$content
"; + } + } } # If all else fails, return a message that we couldn't find any content @@ -296,16 +364,13 @@ sub Content { if ( $args{'Quote'} ) { - # Remove quoted signature. - $content =~ s/\n-- \n(.*?)$//s; - # What's the longest line like? my $max = 0; foreach ( split ( /\n/, $content ) ) { - $max = length if ( length > $max ); + $max = length if length > $max; } - if ( $max > 76 ) { + if ( $max > $args{'Wrap'}+6 ) { # 76 ) { require Text::Wrapper; my $wrapper = new Text::Wrapper( columns => $args{'Wrap'}, @@ -315,11 +380,9 @@ sub Content { $content = $wrapper->wrap($content); } - $content = '[' - . $self->CreatorObj->Name() . ' - ' - . $self->CreatedAsString() . "]:\n\n" . $content . "\n\n"; $content =~ s/^/> /gm; - + $content = $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name) + . "\n$content\n\n"; } return ($content); @@ -327,6 +390,26 @@ sub Content { # }}} + +=head2 Addresses + +Returns a hashref of addresses related to this transaction. See L for details. + +=cut + +sub Addresses { + my $self = shift; + + if (my $attach = $self->Attachments->First) { + return $attach->Addresses; + } + else { + return {}; + } + +} + + # {{{ ContentObj =head2 ContentObj @@ -336,49 +419,53 @@ Returns the RT::Attachment object which contains the content for this Transactio =cut - sub ContentObj { - my $self = shift; + my %args = ( Type => $PreferredContentType, Attachment => undef, @_ ); - # If we don\'t have any content, return undef now. - unless ( $self->Attachments->First ) { - return (undef); - } - + # If we don't have any content, return undef now. # Get the set of toplevel attachments to this transaction. - my $Attachment = $self->Attachments->First(); - # If it's a message or a plain part, just return the - # body. - if ( $Attachment->ContentType() =~ '^(text/plain$|message/)' ) { - return ($Attachment); - } + my $Attachment = $args{'Attachment'}; - # If it's a multipart object, first try returning the first - # text/plain part. + $Attachment ||= $self->Attachments->First; - elsif ( $Attachment->ContentType() =~ '^multipart/' ) { - my $plain_parts = $Attachment->Children(); - $plain_parts->ContentType( VALUE => 'text/plain' ); + return undef unless ($Attachment); - # If we actully found a part, return its content - if ( $plain_parts->First && $plain_parts->First->Content ne '' ) { - return ( $plain_parts->First ); - } + # If it's a textual part, just return the body. + if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) { + return ($Attachment); + } - # If that fails, return the first text/plain or message/ part - # which has some content. + # If it's a multipart object, first try returning the first part with preferred + # MIME type ('text/plain' by default). - else { - my $all_parts = $self->Attachments; - while ( my $part = $all_parts->Next ) { - if (( $part->ContentType() =~ '^(text/plain$|message/)' ) && $part->Content() ) { - return ($part); - } + elsif ( $Attachment->ContentType =~ qr|^multipart/mixed|i ) { + my $kids = $Attachment->Children; + while (my $child = $kids->Next) { + my $ret = $self->ContentObj(%args, Attachment => $child); + return $ret if ($ret); + } + } + elsif ( $Attachment->ContentType =~ qr|^multipart/|i ) { + if ( $args{Type} ) { + my $plain_parts = $Attachment->Children; + $plain_parts->ContentType( VALUE => $args{Type} ); + $plain_parts->LimitNotEmpty; + + # If we actully found a part, return its content + if ( my $first = $plain_parts->First ) { + return $first; } } + # If that fails, return the first textual part which has some content. + my $all_parts = $self->Attachments; + while ( my $part = $all_parts->Next ) { + next unless RT::I18N::IsTextualContentType($part->ContentType) + && $part->Content; + return $part; + } } # We found no content. suck @@ -398,12 +485,8 @@ Otherwise, returns null sub Subject { my $self = shift; - if ( $self->Attachments->First ) { - return ( $self->Attachments->First->Subject ); - } - else { - return (undef); - } + return undef unless my $first = $self->Attachments->First; + return $first->Subject; } # }}} @@ -412,7 +495,7 @@ sub Subject { =head2 Attachments - Returns all the RT::Attachment objects which are attached +Returns all the RT::Attachment objects which are attached to this transaction. Takes an optional parameter, which is a ContentType that Attachments should be restricted to. @@ -421,38 +504,28 @@ a ContentType that Attachments should be restricted to. sub Attachments { my $self = shift; - unless ( $self->{'attachments'} ) { - $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser ); - - #If it's a comment, return an empty object if they don't have the right to see it - if ( $self->Type eq 'Comment' ) { - unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { - return ( $self->{'attachments'} ); - } - } + if ( $self->{'attachments'} ) { + $self->{'attachments'}->GotoFirstItem; + return $self->{'attachments'}; + } - #if they ain't got rights to see, return an empty object - elsif ($self->__Value('ObjectType') eq "RT::Ticket") { - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return ( $self->{'attachments'} ); - } - } + $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser ); - $self->{'attachments'}->Limit( FIELD => 'TransactionId', - VALUE => $self->Id ); + unless ( $self->CurrentUserCanSee ) { + $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl'); + return $self->{'attachments'}; + } - # Get the self->{'attachments'} in the order they're put into - # the database. Arguably, we should be returning a tree - # of self->{'attachments'}, not a set...but no current app seems to need - # it. + $self->{'attachments'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id ); - $self->{'attachments'}->OrderBy( ALIAS => 'main', - FIELD => 'id', - ORDER => 'asc' ); + # Get the self->{'attachments'} in the order they're put into + # the database. Arguably, we should be returning a tree + # of self->{'attachments'}, not a set...but no current app seems to need + # it. - } - return ( $self->{'attachments'} ); + $self->{'attachments'}->OrderBy( FIELD => 'id', ORDER => 'ASC' ); + return $self->{'attachments'}; } # }}} @@ -469,26 +542,68 @@ sub _Attach { my $self = shift; my $MIMEObject = shift; - if ( !defined($MIMEObject) ) { - $RT::Logger->error( -"$self _Attach: We can't attach a mime object if you don't give us one.\n" - ); + unless ( defined $MIMEObject ) { + $RT::Logger->error("We can't attach a mime object if you don't give us one."); return ( 0, $self->loc("[_1]: no attachment specified", $self) ); } - my $Attachment = new RT::Attachment( $self->CurrentUser ); + my $Attachment = RT::Attachment->new( $self->CurrentUser ); my ($id, $msg) = $Attachment->Create( TransactionId => $self->Id, Attachment => $MIMEObject ); return ( $Attachment, $msg || $self->loc("Attachment created") ); - } # }}} # }}} +sub ContentAsMIME { + my $self = shift; + + my $main_content = $self->ContentObj; + return unless $main_content; + + my $entity = $main_content->ContentAsMIME; + + if ( $main_content->Parent ) { + # main content is not top most entity, we shouldn't loose + # From/To/Cc headers that are on a top part + my $attachments = RT::Attachments->new( $self->CurrentUser ); + $attachments->Columns(qw(id Parent TransactionId Headers)); + $attachments->Limit( FIELD => 'TransactionId', VALUE => $self->id ); + $attachments->Limit( FIELD => 'Parent', VALUE => 0 ); + $attachments->Limit( FIELD => 'Parent', OPERATOR => 'IS', VALUE => 'NULL', QUOTEVALUE => 0 ); + $attachments->OrderBy( FIELD => 'id', ORDER => 'ASC' ); + my $tmp = $attachments->First; + if ( $tmp && $tmp->id ne $main_content->id ) { + $entity->make_multipart; + $entity->head->add( split /:/, $_, 2 ) foreach $tmp->SplitHeaders; + $entity->make_singlepart; + } + } + + my $attachments = RT::Attachments->new( $self->CurrentUser ); + $attachments->Limit( FIELD => 'TransactionId', VALUE => $self->id ); + $attachments->Limit( + FIELD => 'id', + OPERATOR => '!=', + VALUE => $main_content->id, + ); + $attachments->Limit( + FIELD => 'ContentType', + OPERATOR => 'NOT STARTSWITH', + VALUE => 'multipart/', + ); + $attachments->LimitNotEmpty; + while ( my $a = $attachments->Next ) { + $entity->make_multipart unless $entity->is_multipart; + $entity->add_part( $a->ContentAsMIME ); + } + return $entity; +} + # {{{ Routines dealing with Transaction Attributes # {{{ sub Description @@ -502,28 +617,15 @@ Returns a text string which describes this transaction sub Description { my $self = shift; - #Check those ACLs - #If it's a comment or a comment email record, - # we need to be extra special careful - - if ( $self->__Value('Type') =~ /^Comment/ ) { - unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { - return ( $self->loc("Permission Denied") ); - } + unless ( $self->CurrentUserCanSee ) { + return ( $self->loc("Permission Denied") ); } - #if they ain't got rights to see, don't let em - elsif ($self->__Value('ObjectType') eq "RT::Ticket") { - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return ($self->loc("Permission Denied") ); - } - } - - if ( !defined( $self->Type ) ) { + unless ( defined $self->Type ) { return ( $self->loc("No transaction type specified")); } - return ( $self->loc("[_1] by [_2]",$self->BriefDescription , $self->CreatorObj->Name )); + return $self->loc("[_1] by [_2]", $self->BriefDescription , $self->CreatorObj->Name ); } # }}} @@ -539,24 +641,13 @@ Returns a text string which briefly describes this transaction sub BriefDescription { my $self = shift; - #If it's a comment or a comment email record, - # we need to be extra special careful - if ( $self->__Value('Type') =~ /^Comment/ ) { - unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { - return ( $self->loc("Permission Denied") ); - } - } - - #if they ain't got rights to see, don't let em - elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) { - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return ( $self->loc("Permission Denied") ); - } + unless ( $self->CurrentUserCanSee ) { + return ( $self->loc("Permission Denied") ); } my $type = $self->Type; #cache this, rather than calling it 30 times - if ( !defined($type) ) { + unless ( defined $type ) { return $self->loc("No transaction type specified"); } @@ -565,6 +656,12 @@ sub BriefDescription { if ( $type eq 'Create' ) { return ( $self->loc( "[_1] created", $obj_type ) ); } + elsif ( $type eq 'Enabled' ) { + return ( $self->loc( "[_1] enabled", $obj_type ) ); + } + elsif ( $type eq 'Disabled' ) { + return ( $self->loc( "[_1] disabled", $obj_type ) ); + } elsif ( $type =~ /Status/ ) { if ( $self->Field eq 'Status' ) { if ( $self->NewValue eq 'deleted' ) { @@ -593,6 +690,9 @@ sub BriefDescription { ) ); } + elsif ( $type =~ /SystemError/ ) { + return $self->loc("System error"); + } if ( my $code = $_BriefDescriptions{$type} ) { return $code->($self); @@ -634,14 +734,15 @@ sub BriefDescription { if ( $self->Field ) { my $cf = RT::CustomField->new( $self->CurrentUser ); + $cf->SetContextObject( $self->Object ); $cf->Load( $self->Field ); $field = $cf->Name(); } - if ( $self->OldValue eq '' ) { + if ( ! defined $self->OldValue || $self->OldValue eq '' ) { return ( $self->loc("[_1] [_2] added", $field, $self->NewValue) ); } - elsif ( $self->NewValue eq '' ) { + elsif ( !defined $self->NewValue || $self->NewValue eq '' ) { return ( $self->loc("[_1] [_2] deleted", $field, $self->OldValue) ); } @@ -770,6 +871,21 @@ sub BriefDescription { return ( $self->Data ); } }, + Told => sub { + my $self = shift; + if ( $self->Field eq 'Told' ) { + my $t1 = new RT::Date($self->CurrentUser); + $t1->Set(Format => 'ISO', Value => $self->NewValue); + my $t2 = new RT::Date($self->CurrentUser); + $t2->Set(Format => 'ISO', Value => $self->OldValue); + return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); + } + else { + return $self->loc( "[_1] changed from [_2] to [_3]", + $self->loc($self->Field), + ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); + } + }, Set => sub { my $self = shift; if ( $self->Field eq 'Password' ) { @@ -780,7 +896,8 @@ sub BriefDescription { $q1->Load( $self->OldValue ); my $q2 = new RT::Queue( $self->CurrentUser ); $q2->Load( $self->NewValue ); - return $self->loc("[_1] changed from [_2] to [_3]", $self->Field , $q1->Name , $q2->Name); + return $self->loc("[_1] changed from [_2] to [_3]", + $self->loc($self->Field) , $q1->Name , $q2->Name); } # Write the date/time change at local time: @@ -789,16 +906,39 @@ sub BriefDescription { $t1->Set(Format => 'ISO', Value => $self->NewValue); my $t2 = new RT::Date($self->CurrentUser); $t2->Set(Format => 'ISO', Value => $self->OldValue); - return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $t2->AsString, $t1->AsString ); + return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); } else { - return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); + return $self->loc( "[_1] changed from [_2] to [_3]", + $self->loc($self->Field), + ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); } }, PurgeTransaction => sub { my $self = shift; return $self->loc("Transaction [_1] purged", $self->Data); }, + AddReminder => sub { + my $self = shift; + my $ticket = RT::Ticket->new($self->CurrentUser); + $ticket->Load($self->NewValue); + return $self->loc("Reminder '[_1]' added", $ticket->Subject); + }, + OpenReminder => sub { + my $self = shift; + my $ticket = RT::Ticket->new($self->CurrentUser); + $ticket->Load($self->NewValue); + return $self->loc("Reminder '[_1]' reopened", $ticket->Subject); + + }, + ResolveReminder => sub { + my $self = shift; + my $ticket = RT::Ticket->new($self->CurrentUser); + $ticket->Load($self->NewValue); + return $self->loc("Reminder '[_1]' completed", $ticket->Subject); + + + } ); # }}} @@ -856,52 +996,19 @@ Returns its value as a string, if the user passes an ACL check =cut sub _Value { - my $self = shift; my $field = shift; #if the field is public, return it. if ( $self->_Accessible( $field, 'public' ) ) { - return ( $self->__Value($field) ); - + return $self->SUPER::_Value( $field ); } - #If it's a comment, we need to be extra special careful - if ( $self->__Value('Type') eq 'Comment' ) { - unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { - return (undef); - } + unless ( $self->CurrentUserCanSee ) { + return undef; } - elsif ( $self->__Value('Type') eq 'CommentEmailRecord' ) { - unless ( $self->CurrentUserHasRight('ShowTicketComments') - && $self->CurrentUserHasRight('ShowOutgoingEmail') ) { - return (undef); - } - - } - elsif ( $self->__Value('Type') eq 'EmailRecord' ) { - unless ( $self->CurrentUserHasRight('ShowOutgoingEmail')) { - return (undef); - } - - } - # Make sure the user can see the custom field before showing that it changed - elsif ( ( $self->__Value('Type') eq 'CustomField' ) && $self->__Value('Field') ) { - my $cf = RT::CustomField->new( $self->CurrentUser ); - $cf->Load( $self->__Value('Field') ); - return (undef) unless ( $cf->CurrentUserHasRight('SeeCustomField') ); - } - - - #if they ain't got rights to see, don't let em - elsif ($self->__Value('ObjectType') eq "RT::Ticket") { - unless ( $self->CurrentUserHasRight('ShowTicket') ) { - return (undef); - } - } - - return ( $self->__Value($field) ); + return $self->SUPER::_Value( $field ); } # }}} @@ -918,14 +1025,54 @@ passed in here. sub CurrentUserHasRight { my $self = shift; my $right = shift; - return ( - $self->CurrentUser->HasRight( - Right => "$right", - Object => $self->TicketObj - ) + return $self->CurrentUser->HasRight( + Right => $right, + Object => $self->Object ); } +=head2 CurrentUserCanSee + +Returns true if current user has rights to see this particular transaction. + +This fact depends on type of the transaction, type of an object the transaction +is attached to and may be other conditions, so this method is prefered over +custom implementations. + +=cut + +sub CurrentUserCanSee { + my $self = shift; + + # If it's a comment, we need to be extra special careful + my $type = $self->__Value('Type'); + if ( $type eq 'Comment' ) { + unless ( $self->CurrentUserHasRight('ShowTicketComments') ) { + return 0; + } + } + elsif ( $type eq 'CommentEmailRecord' ) { + unless ( $self->CurrentUserHasRight('ShowTicketComments') + && $self->CurrentUserHasRight('ShowOutgoingEmail') ) { + return 0; + } + } + elsif ( $type eq 'EmailRecord' ) { + unless ( $self->CurrentUserHasRight('ShowOutgoingEmail') ) { + return 0; + } + } + # Make sure the user can see the custom field before showing that it changed + elsif ( $type eq 'CustomField' and my $cf_id = $self->__Value('Field') ) { + my $cf = RT::CustomField->new( $self->CurrentUser ); + $cf->SetContextObject( $self->Object ); + $cf->Load( $cf_id ); + return 0 unless $cf->CurrentUserHasRight('SeeCustomField'); + } + # Defer to the object in question + return $self->Object->CurrentUserCanSee("Transaction"); +} + # }}} sub Ticket { @@ -948,7 +1095,7 @@ sub OldValue { return $Object->Content; } else { - return $self->__Value('OldValue'); + return $self->_Value('OldValue'); } } @@ -962,7 +1109,7 @@ sub NewValue { return $Object->Content; } else { - return $self->__Value('NewValue'); + return $self->_Value('NewValue'); } } @@ -970,7 +1117,7 @@ sub Object { my $self = shift; my $Object = $self->__Value('ObjectType')->new($self->CurrentUser); $Object->Load($self->__Value('ObjectId')); - return($Object); + return $Object; } sub FriendlyObjectType { @@ -1000,7 +1147,6 @@ sub UpdateCustomFields { # value "ARGSRef", which was a reference to a hash of arguments. # This was insane. The next few lines of code preserve that API # while giving us something saner. - # TODO: 3.6: DEPRECATE OLD API @@ -1017,6 +1163,7 @@ sub UpdateCustomFields { unless ( $arg =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ); next if $arg =~ /-Magic$/; + next if $arg =~ /-TimeUnits$/; my $cfid = $1; my $values = $args->{$arg}; foreach @@ -1048,9 +1195,12 @@ sub CustomFieldValues { if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) { - unless ( $field =~ /^\d+$/o ) { + # XXX: $field could be undef when we want fetch values for all CFs + # do we want to cover this situation somehow here? + unless ( defined $field && $field =~ /^\d+$/o ) { my $CFs = RT::CustomFields->new( $self->CurrentUser ); - $CFs->Limit( FIELD => 'Name', VALUE => $field); + $CFs->SetContextObject( $self->Object ); + $CFs->Limit( FIELD => 'Name', VALUE => $field ); $CFs->LimitToLookupType($self->CustomFieldLookupType); $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id); $field = $CFs->First->id if $CFs->First; @@ -1076,7 +1226,54 @@ sub CustomFieldLookupType { "RT::Queue-RT::Ticket-RT::Transaction"; } -# Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets. + +=head2 DeferredRecipients($freq, $include_sent ) + +Takes the following arguments: + +=over + +=item * a string to indicate the frequency of digest delivery. Valid values are "daily", "weekly", or "susp". + +=item * an optional argument which, if true, will return addresses even if this notification has been marked as 'sent' for this transaction. + +=back + +Returns an array of users who should now receive the notification that +was recorded in this transaction. Returns an empty array if there were +no deferred users, or if $include_sent was not specified and the deferred +notifications have been sent. + +=cut + +sub DeferredRecipients { + my $self = shift; + my $freq = shift; + my $include_sent = @_? shift : 0; + + my $attr = $self->FirstAttribute('DeferredRecipients'); + + return () unless ($attr); + + my $deferred = $attr->Content; + + return () unless ( ref($deferred) eq 'HASH' && exists $deferred->{$freq} ); + + # Skip it. + + for my $user (keys %{$deferred->{$freq}}) { + if ($deferred->{$freq}->{$user}->{_sent} && !$include_sent) { + delete $deferred->{$freq}->{$user} + } + } + # Now get our users. Easy. + + return keys %{ $deferred->{$freq} }; +} + + + +# Transactions don't change. by adding this cache config directive, we don't lose pathalogically on long tickets. sub _CacheConfig { { 'cache_p' => 1, @@ -1084,4 +1281,27 @@ sub _CacheConfig { 'cache_for_sec' => 6000, } } + + +=head2 ACLEquivalenceObjects + +This method returns a list of objects for which a user's rights also apply +to this Transaction. + +This currently only applies to Transaction Custom Fields on Tickets, so we return +the Ticket's Queue and the Ticket. + +This method is called from L. + +=cut + +sub ACLEquivalenceObjects { + my $self = shift; + + return unless $self->ObjectType eq 'RT::Ticket'; + my $object = $self->Object; + return $object,$object->QueueObj; + +} + 1;