X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FTransaction.pm;h=781c9e422487da22f223124257dc097a013a7476;hp=3344687dafc63ca93ce6ef872b5255bf4099285a;hb=9aee669886202be7035e6c6049fc71bc99dd3013;hpb=0af38652da3b3be7da2d35b048285ef6f2194e1a diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm index 3344687da..781c9e422 100755 --- a/rt/lib/RT/Transaction.pm +++ b/rt/lib/RT/Transaction.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) @@ -48,7 +48,7 @@ =head1 NAME - RT::Transaction - RT\'s transaction object + RT::Transaction - RT's transaction object =head1 SYNOPSIS @@ -82,9 +82,12 @@ use RT::Attachments; use RT::Scrips; use RT::Ruleset; -use HTML::FormatText; -use HTML::TreeBuilder; +use HTML::FormatText::WithLinks::AndTables; +use HTML::Scrubber; +# For EscapeHTML() and decode_entities() +require RT::Interface::Web; +require HTML::Entities; sub Table {'Transactions'} @@ -141,11 +144,11 @@ sub Create { OldValue => $args{'OldValue'}, NewValue => $args{'NewValue'}, Created => $args{'Created'}, - ObjectType => $args{'ObjectType'}, - ObjectId => $args{'ObjectId'}, - ReferenceType => $args{'ReferenceType'}, - OldReference => $args{'OldReference'}, - NewReference => $args{'NewReference'}, + ObjectType => $args{'ObjectType'}, + ObjectId => $args{'ObjectId'}, + ReferenceType => $args{'ReferenceType'}, + OldReference => $args{'OldReference'}, + NewReference => $args{'NewReference'}, ); # Parameters passed in during an import that we probably don't want to touch, otherwise @@ -173,41 +176,47 @@ sub Create { Content => RT::User->CanonicalizeEmailAddress($_) ) for @{$args{'SquelchMailTo'} || []}; - #Provide a way to turn off scrips if we need to - $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id); - if ( $args{'ActivateScrips'} and $args{'ObjectType'} eq 'RT::Ticket' ) { - $self->{'scrips'} = RT::Scrips->new(RT->SystemUser); + my @return = ( $id, $self->loc("Transaction Created") ); - $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id); + return @return unless $args{'ObjectType'} eq 'RT::Ticket'; - $self->{'scrips'}->Prepare( - Stage => 'TransactionCreate', - Type => $args{'Type'}, - Ticket => $args{'ObjectId'}, - Transaction => $self->id, - ); + # Provide a way to turn off scrips if we need to + unless ( $args{'ActivateScrips'} ) { + $RT::Logger->debug('Skipping scrips for transaction #' .$self->Id); + return @return; + } - # Entry point of the rule system - my $ticket = RT::Ticket->new(RT->SystemUser); - $ticket->Load($args{'ObjectId'}); - my $txn = RT::Transaction->new($RT::SystemUser); - $txn->Load($self->id); - - my $rules = $self->{rules} = RT::Ruleset->FindAllRules( - Stage => 'TransactionCreate', - Type => $args{'Type'}, - TicketObj => $ticket, - TransactionObj => $txn, - ); - - if ($args{'CommitScrips'} ) { - $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id); - $self->{'scrips'}->Commit(); - RT::Ruleset->CommitRules($rules); - } + $self->{'scrips'} = RT::Scrips->new(RT->SystemUser); + + $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id); + + $self->{'scrips'}->Prepare( + Stage => 'TransactionCreate', + Type => $args{'Type'}, + Ticket => $args{'ObjectId'}, + Transaction => $self->id, + ); + + # Entry point of the rule system + my $ticket = RT::Ticket->new(RT->SystemUser); + $ticket->Load($args{'ObjectId'}); + my $txn = RT::Transaction->new($RT::SystemUser); + $txn->Load($self->id); + + my $rules = $self->{rules} = RT::Ruleset->FindAllRules( + Stage => 'TransactionCreate', + Type => $args{'Type'}, + TicketObj => $ticket, + TransactionObj => $txn, + ); + + if ($args{'CommitScrips'} ) { + $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id); + $self->{'scrips'}->Commit(); + RT::Ruleset->CommitRules($rules); } - return ( $id, $self->loc("Transaction Created") ); + return @return; } @@ -339,15 +348,22 @@ sub Content { $content = $content_obj->Content ||''; if ( lc $content_obj->ContentType eq 'text/html' ) { - $content =~ s/

--\s+
.*?$//s if $args{'Quote'}; + $content =~ s/(?:(<\/div>)|

||)\s*--\s+.*?$/$1/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; + $content = RT::Interface::Email::ConvertHTMLToText($content); + } else { + # Scrub out , , , and , and + # leave all else untouched. + my $scrubber = HTML::Scrubber->new(); + $scrubber->rules( + html => 0, + head => 0, + meta => 0, + body => 0, + ); + $scrubber->default( 1 => { '*' => 1 } ); + $content = $scrubber->scrub( $content ); } } else { @@ -357,7 +373,7 @@ sub Content { $content =~ s/&/&/g; $content =~ s//>/g; - $content = "

$content
"; + $content = qq|
$content
|; } } } @@ -368,31 +384,113 @@ sub Content { } if ( $args{'Quote'} ) { - - # What's the longest line like? - my $max = 0; - foreach ( split ( /\n/, $content ) ) { - $max = length if length > $max; + if ($args{Type} eq 'text/html') { + $content = '
' + . $self->QuoteHeader + . '
' + . $content + . '


'; + } else { + $content = $self->ApplyQuoteWrap(content => $content, + cols => $args{'Wrap'} ); + + $content = $self->QuoteHeader . "\n$content\n\n"; } + } - if ( $max > $args{'Wrap'}+6 ) { # 76 ) { - require Text::Wrapper; - my $wrapper = Text::Wrapper->new( - columns => $args{'Wrap'}, - body_start => ( $max > 70 * 3 ? ' ' : '' ), - par_start => '' - ); - $content = $wrapper->wrap($content); - } + return ($content); +} + +=head2 QuoteHeader + +Returns text prepended to content when transaction is quoted +(see C argument in L). By default returns +localized "On wrote:\n". - $content =~ s/^/> /gm; - $content = $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name) - . "\n$content\n\n"; +=cut + +sub QuoteHeader { + my $self = shift; + return $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name); +} + +=head2 ApplyQuoteWrap PARAMHASH + +Wrapper to calculate wrap criteria and apply quote wrapping if needed. + +=cut + +sub ApplyQuoteWrap { + my $self = shift; + my %args = @_; + my $content = $args{content}; + + # What's the longest line like? + my $max = 0; + foreach ( split ( /\n/, $args{content} ) ) { + $max = length if length > $max; } - return ($content); + if ( $max > 76 ) { + require Text::Quoted; + require Text::Wrapper; + + my $structure = Text::Quoted::extract($args{content}); + $content = $self->QuoteWrap(content_ref => $structure, + cols => $args{cols}, + max => $max ); + } + + $content =~ s/^/> /gm; # use regex since string might be multi-line + return $content; } +=head2 QuoteWrap PARAMHASH + +Wrap the contents of transactions based on Wrap settings, maintaining +the quote character from the original. + +=cut + +sub QuoteWrap { + my $self = shift; + my %args = @_; + my $ref = $args{content_ref}; + my $final_string; + + if ( ref $ref eq 'ARRAY' ){ + foreach my $array (@$ref){ + $final_string .= $self->QuoteWrap(content_ref => $array, + cols => $args{cols}, + max => $args{max} ); + } + } + elsif ( ref $ref eq 'HASH' ){ + return $ref->{quoter} . "\n" if $ref->{empty}; # Blank line + + my $col = $args{cols} - (length $ref->{quoter}); + my $wrapper = Text::Wrapper->new( columns => $col ); + + # Wrap on individual lines to honor incoming line breaks + # Otherwise deliberate separate lines (like a list or a sig) + # all get combined incorrectly into single paragraphs. + + my @lines = split /\n/, $ref->{text}; + my $wrap = join '', map { $wrapper->wrap($_) } @lines; + my $quoter = $ref->{quoter}; + + # Only add the space if actually quoting + $quoter .= ' ' if length $quoter; + $wrap =~ s/^/$quoter/mg; # use regex since string might be multi-line + + return $wrap; + } + else{ + $RT::Logger->warning("Can't apply quoting with $ref"); + return; + } + return $final_string; +} =head2 Addresses @@ -402,14 +500,14 @@ Returns a hashref of addresses related to this transaction. See LAttachments->First) { - return $attach->Addresses; - } - else { - return {}; - } + if (my $attach = $self->Attachments->First) { + return $attach->Addresses; + } + else { + return {}; + } } @@ -435,8 +533,36 @@ sub ContentObj { return undef unless ($Attachment); + my $Attachments = $self->Attachments; + while ( my $Attachment = $Attachments->Next ) { + if ( my $content = _FindPreferredContentObj( %args, Attachment => $Attachment ) ) { + return $content; + } + } + + # If that fails, return the first top-level textual part which has some content. + # We probably really want this to become "recurse, looking for the other type of + # displayable". For now, this maintains backcompat + my $all_parts = $self->Attachments; + while ( my $part = $all_parts->Next ) { + next unless _IsDisplayableTextualContentType($part->ContentType) + && $part->Content; + return $part; + } + + return; +} + + +sub _FindPreferredContentObj { + my %args = @_; + my $Attachment = $args{Attachment}; + + # If we don't have any content, return undef now. + return undef unless $Attachment; + # If it's a textual part, just return the body. - if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) { + if ( _IsDisplayableTextualContentType($Attachment->ContentType) ) { return ($Attachment); } @@ -446,7 +572,7 @@ sub ContentObj { elsif ( $Attachment->ContentType =~ m|^multipart/mixed|i ) { my $kids = $Attachment->Children; while (my $child = $kids->Next) { - my $ret = $self->ContentObj(%args, Attachment => $child); + my $ret = _FindPreferredContentObj(%args, Attachment => $child); return $ret if ($ret); } } @@ -460,14 +586,28 @@ sub ContentObj { if ( my $first = $plain_parts->First ) { return $first; } + } else { + my $parts = $Attachment->Children; + $parts->LimitNotEmpty; + + # If we actully found a part, return its content + while (my $part = $parts->Next) { + next unless _IsDisplayableTextualContentType($part->ContentType); + return $part; + } + } + } + + # If this is a message/rfc822 mail, we need to dig into it in order to find + # the actual textual content - # 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; + elsif ( $Attachment->ContentType =~ '^message/rfc822' ) { + my $children = $Attachment->Children; + while ( my $child = $children->Next ) { + if ( my $content = _FindPreferredContentObj( %args, Attachment => $child ) ) { + return $content; + } } } @@ -475,6 +615,18 @@ sub ContentObj { return (undef); } +=head2 _IsDisplayableTextualContentType + +We may need to pull this out to another module later, but for now, this +is better than RT::I18N::IsTextualContentType because that believes that +a message/rfc822 email is displayable, despite it having no content + +=cut + +sub _IsDisplayableTextualContentType { + my $type = shift; + ($type =~ m{^text/(?:plain|html)\b}i) ? 1 : 0; +} =head2 Subject @@ -610,109 +762,216 @@ Returns a text string which briefly describes this transaction =cut -sub BriefDescription { +{ + my $scrubber = HTML::Scrubber->new(default => 0); # deny everything + + sub BriefDescription { + my $self = shift; + my $desc = $self->BriefDescriptionAsHTML; + $desc = $scrubber->scrub($desc); + $desc = HTML::Entities::decode_entities($desc); + return $desc; + } +} + +=head2 BriefDescriptionAsHTML + +Returns an HTML string which briefly describes this transaction. + +=cut + +sub BriefDescriptionAsHTML { my $self = shift; unless ( $self->CurrentUserCanSee ) { return ( $self->loc("Permission Denied") ); } - my $type = $self->Type; #cache this, rather than calling it 30 times + my ($objecttype, $type, $field) = ($self->ObjectType, $self->Type, $self->Field); unless ( defined $type ) { return $self->loc("No transaction type specified"); } - my $obj_type = $self->FriendlyObjectType; + my ($template, @params); + + my @code = grep { ref eq 'CODE' } map { $_BriefDescriptions{$_} } + ( $field + ? ("$objecttype-$type-$field", "$type-$field") + : () ), + "$objecttype-$type", $type; - if ( $type eq 'Create' ) { - return ( $self->loc( "[_1] created", $obj_type ) ); + if (@code) { + ($template, @params) = $code[0]->($self); } - elsif ( $type eq 'Enabled' ) { - return ( $self->loc( "[_1] enabled", $obj_type ) ); + + unless ($template) { + ($template, @params) = ( + "Default: [_1]/[_2] changed from [_3] to [_4]", #loc + $type, + $field, + ( + $self->OldValue + ? "'" . $self->OldValue . "'" + : $self->loc("(no value)") + ), + ( + $self->NewValue + ? "'" . $self->NewValue . "'" + : $self->loc("(no value)") + ), + ); } - elsif ( $type eq 'Disabled' ) { - return ( $self->loc( "[_1] disabled", $obj_type ) ); + return $self->loc($template, $self->_ProcessReturnValues(@params)); +} + +sub _ProcessReturnValues { + my $self = shift; + my @values = @_; + return map { + if (ref eq 'ARRAY') { $_ = join "", $self->_ProcessReturnValues(@$_) } + elsif (ref eq 'SCALAR') { $_ = $$_ } + else { RT::Interface::Web::EscapeHTML(\$_) } + $_ + } @values; +} + +sub _FormatPrincipal { + my $self = shift; + my $principal = shift; + if ($principal->IsUser) { + return $self->_FormatUser( $principal->Object ); + } else { + return $self->loc("group [_1]", $principal->Object->Name); } - elsif ( $type =~ /Status/ ) { +} + +sub _FormatUser { + my $self = shift; + my $user = shift; + return [ + \'', + $user->Format, + \'' + ]; +} + +%_BriefDescriptions = ( + Create => sub { + my $self = shift; + return ( "[_1] created", $self->FriendlyObjectType ); #loc() + }, + Enabled => sub { + my $self = shift; + return ( "[_1] enabled", $self->FriendlyObjectType ); #loc() + }, + Disabled => sub { + my $self = shift; + return ( "[_1] disabled", $self->FriendlyObjectType ); #loc() + }, + Status => sub { + my $self = shift; if ( $self->Field eq 'Status' ) { if ( $self->NewValue eq 'deleted' ) { - return ( $self->loc( "[_1] deleted", $obj_type ) ); + return ( "[_1] deleted", $self->FriendlyObjectType ); #loc() } else { + my $canon = $self->Object->DOES("RT::Record::Role::Status") + ? sub { $self->Object->LifecycleObj->CanonicalCase(@_) } + : sub { return $_[0] }; return ( - $self->loc( - "Status changed from [_1] to [_2]", - "'" . $self->loc( $self->OldValue ) . "'", - "'" . $self->loc( $self->NewValue ) . "'" - ) - ); - + "Status changed from [_1] to [_2]", + "'" . $self->loc( $canon->($self->OldValue) ) . "'", + "'" . $self->loc( $canon->($self->NewValue) ) . "'" + ); # loc() } } # Generic: my $no_value = $self->loc("(no value)"); return ( - $self->loc( - "[_1] changed from [_2] to [_3]", - $self->Field, - ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ), - "'" . $self->NewValue . "'" - ) - ); - } - elsif ( $type =~ /SystemError/ ) { - return $self->loc("System error"); - } - elsif ( $type =~ /Forward Transaction/ ) { - return $self->loc( "Forwarded Transaction #[_1] to [_2]", - $self->Field, $self->Data ); - } - elsif ( $type =~ /Forward Ticket/ ) { - return $self->loc( "Forwarded Ticket to [_1]", $self->Data ); - } - - if ( my $code = $_BriefDescriptions{$type} ) { - return $code->($self); - } + "[_1] changed from [_2] to [_3]", + $self->Field, + ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ), + "'" . $self->NewValue . "'" + ); #loc() + }, + SystemError => sub { + my $self = shift; + return $self->Data // ("System error"); #loc() + }, + AttachmentTruncate => sub { + my $self = shift; + if ( defined $self->Data ) { + return ( "File '[_1]' truncated because its size ([_2] bytes) exceeded configured maximum size setting ([_3] bytes).", + $self->Data, $self->OldValue, $self->NewValue ); #loc() + } + else { + return ( "Content truncated because its size ([_1] bytes) exceeded configured maximum size setting ([_2] bytes).", + $self->OldValue, $self->NewValue ); #loc() + } + }, + AttachmentDrop => sub { + my $self = shift; + if ( defined $self->Data ) { + return ( "File '[_1]' dropped because its size ([_2] bytes) exceeded configured maximum size setting ([_3] bytes).", + $self->Data, $self->OldValue, $self->NewValue ); #loc() + } + else { + return ( "Content dropped because its size ([_1] bytes) exceeded configured maximum size setting ([_2] bytes).", + $self->OldValue, $self->NewValue ); #loc() + } + }, + AttachmentError => sub { + my $self = shift; + if ( defined $self->Data ) { + return ( "File '[_1]' insert failed. See error log for details.", $self->Data ); #loc() + } + else { + return ( "Content insert failed. See error log for details." ); #loc() + } + }, + "Forward Transaction" => sub { + my $self = shift; + my $recipients = join ", ", map { + RT::User->Format( Address => $_, CurrentUser => $self->CurrentUser ) + } RT::EmailParser->ParseEmailAddress($self->Data); - return $self->loc( - "Default: [_1]/[_2] changed from [_3] to [_4]", - $type, - $self->Field, - ( - $self->OldValue - ? "'" . $self->OldValue . "'" - : $self->loc("(no value)") - ), - "'" . $self->NewValue . "'" - ); -} + return ( "Forwarded [_3]Transaction #[_1][_4] to [_2]", + $self->Field, $recipients, + [\''], \''); #loc() + }, + "Forward Ticket" => sub { + my $self = shift; + my $recipients = join ", ", map { + RT::User->Format( Address => $_, CurrentUser => $self->CurrentUser ) + } RT::EmailParser->ParseEmailAddress($self->Data); -%_BriefDescriptions = ( + return ( "Forwarded Ticket to [_1]", $recipients ); #loc() + }, CommentEmailRecord => sub { my $self = shift; - return $self->loc("Outgoing email about a comment recorded"); + return ("Outgoing email about a comment recorded"); #loc() }, EmailRecord => sub { my $self = shift; - return $self->loc("Outgoing email recorded"); + return ("Outgoing email recorded"); #loc() }, Correspond => sub { my $self = shift; - return $self->loc("Correspondence added"); + return ("Correspondence added"); #loc() }, Comment => sub { my $self = shift; - return $self->loc("Comments added"); + return ("Comments added"); #loc() }, CustomField => sub { my $self = shift; my $field = $self->loc('CustomField'); + my $cf; if ( $self->Field ) { - my $cf = RT::CustomField->new( $self->CurrentUser ); + $cf = RT::CustomField->new( $self->CurrentUser ); $cf->SetContextObject( $self->Object ); $cf->Load( $self->Field ); $field = $cf->Name(); @@ -722,23 +981,61 @@ sub BriefDescription { my $new = $self->NewValue; my $old = $self->OldValue; + if ( $cf ) { + + if ( $cf->Type eq 'DateTime' ) { + if ($old) { + my $date = RT::Date->new( $self->CurrentUser ); + $date->Set( Format => 'ISO', Value => $old ); + $old = $date->AsString; + } + + if ($new) { + my $date = RT::Date->new( $self->CurrentUser ); + $date->Set( Format => 'ISO', Value => $new ); + $new = $date->AsString; + } + } + elsif ( $cf->Type eq 'Date' ) { + if ($old) { + my $date = RT::Date->new( $self->CurrentUser ); + $date->Set( + Format => 'unknown', + Value => $old, + Timezone => 'UTC', + ); + $old = $date->AsString( Time => 0, Timezone => 'UTC' ); + } + + if ($new) { + my $date = RT::Date->new( $self->CurrentUser ); + $date->Set( + Format => 'unknown', + Value => $new, + Timezone => 'UTC', + ); + $new = $date->AsString( Time => 0, Timezone => 'UTC' ); + } + } + } + if ( !defined($old) || $old eq '' ) { - return $self->loc("[_1] [_2] added", $field, $new); + return ("[_1] [_2] added", $field, $new); #loc() } elsif ( !defined($new) || $new eq '' ) { - return $self->loc("[_1] [_2] deleted", $field, $old); + return ("[_1] [_2] deleted", $field, $old); #loc() } else { - return $self->loc("[_1] [_2] changed to [_3]", $field, $old, $new); + return ("[_1] [_2] changed to [_3]", $field, $old, $new); #loc() } }, Untake => sub { my $self = shift; - return $self->loc("Untaken"); + return ("Untaken"); #loc() }, Take => sub { my $self = shift; - return $self->loc("Taken"); + return ("Taken"); #loc() }, Force => sub { my $self = shift; @@ -747,73 +1044,83 @@ sub BriefDescription { my $New = RT::User->new( $self->CurrentUser ); $New->Load( $self->NewValue ); - return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name); + return ("Owner forcibly changed from [_1] to [_2]", + map { $self->_FormatUser($_) } $Old, $New); #loc() }, Steal => sub { my $self = shift; my $Old = RT::User->new( $self->CurrentUser ); $Old->Load( $self->OldValue ); - return $self->loc("Stolen from [_1]", $Old->Name); + return ("Stolen from [_1]", $self->_FormatUser($Old)); #loc() }, Give => sub { my $self = shift; my $New = RT::User->new( $self->CurrentUser ); $New->Load( $self->NewValue ); - return $self->loc( "Given to [_1]", $New->Name ); + return ( "Given to [_1]", $self->_FormatUser($New)); #loc() }, AddWatcher => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->NewValue); - return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name); + return ( "[_1] [_2] added", $self->loc($self->Field), $self->_FormatPrincipal($principal)); #loc() }, DelWatcher => sub { my $self = shift; my $principal = RT::Principal->new($self->CurrentUser); $principal->Load($self->OldValue); - return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name); + return ( "[_1] [_2] deleted", $self->loc($self->Field), $self->_FormatPrincipal($principal)); #loc() + }, + SetWatcher => sub { + my $self = shift; + my $principal = RT::Principal->new($self->CurrentUser); + $principal->Load($self->NewValue); + return ( "[_1] set to [_2]", $self->loc($self->Field), $self->_FormatPrincipal($principal)); #loc() }, Subject => sub { my $self = shift; - return $self->loc( "Subject changed to [_1]", $self->Data ); + return ( "Subject changed to [_1]", $self->Data ); #loc() }, AddLink => sub { my $self = shift; my $value; if ( $self->NewValue ) { my $URI = RT::URI->new( $self->CurrentUser ); - $URI->FromURI( $self->NewValue ); - if ( $URI->Resolver ) { - $value = $URI->Resolver->AsString; + if ( $URI->FromURI( $self->NewValue ) ) { + $value = [ + \'', + $URI->AsString, + \'' + ]; } else { $value = $self->NewValue; } + if ( $self->Field eq 'DependsOn' ) { - return $self->loc( "Dependency on [_1] added", $value ); + return ( "Dependency on [_1] added", $value ); #loc() } elsif ( $self->Field eq 'DependedOnBy' ) { - return $self->loc( "Dependency by [_1] added", $value ); - + return ( "Dependency by [_1] added", $value ); #loc() } elsif ( $self->Field eq 'RefersTo' ) { - return $self->loc( "Reference to [_1] added", $value ); + return ( "Reference to [_1] added", $value ); #loc() } elsif ( $self->Field eq 'ReferredToBy' ) { - return $self->loc( "Reference by [_1] added", $value ); + return ( "Reference by [_1] added", $value ); #loc() } elsif ( $self->Field eq 'MemberOf' ) { - return $self->loc( "Membership in [_1] added", $value ); + return ( "Membership in [_1] added", $value ); #loc() } elsif ( $self->Field eq 'HasMember' ) { - return $self->loc( "Member [_1] added", $value ); + return ( "Member [_1] added", $value ); #loc() } elsif ( $self->Field eq 'MergedInto' ) { - return $self->loc( "Merged into [_1]", $value ); + return ( "Merged into [_1]", $value ); #loc() } } else { - return ( $self->Data ); + return ( "[_1]", $self->Data ); #loc() } }, DeleteLink => sub { @@ -821,36 +1128,38 @@ sub BriefDescription { my $value; if ( $self->OldValue ) { my $URI = RT::URI->new( $self->CurrentUser ); - $URI->FromURI( $self->OldValue ); - if ( $URI->Resolver ) { - $value = $URI->Resolver->AsString; + if ( $URI->FromURI( $self->OldValue ) ) { + $value = [ + \'', + $URI->AsString, + \'' + ]; } else { $value = $self->OldValue; } if ( $self->Field eq 'DependsOn' ) { - return $self->loc( "Dependency on [_1] deleted", $value ); + return ( "Dependency on [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'DependedOnBy' ) { - return $self->loc( "Dependency by [_1] deleted", $value ); - + return ( "Dependency by [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'RefersTo' ) { - return $self->loc( "Reference to [_1] deleted", $value ); + return ( "Reference to [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'ReferredToBy' ) { - return $self->loc( "Reference by [_1] deleted", $value ); + return ( "Reference by [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'MemberOf' ) { - return $self->loc( "Membership in [_1] deleted", $value ); + return ( "Membership in [_1] deleted", $value ); #loc() } elsif ( $self->Field eq 'HasMember' ) { - return $self->loc( "Member [_1] deleted", $value ); + return ( "Member [_1] deleted", $value ); #loc() } } else { - return ( $self->Data ); + return ( "[_1]", $self->Data ); #loc() } }, Told => sub { @@ -860,26 +1169,26 @@ sub BriefDescription { $t1->Set(Format => 'ISO', Value => $self->NewValue); my $t2 = RT::Date->new($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 ); + return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); #loc() } else { - return $self->loc( "[_1] changed from [_2] to [_3]", - $self->loc($self->Field), - ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); + return ( "[_1] changed from [_2] to [_3]", + $self->loc($self->Field), + ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); #loc() } }, Set => sub { my $self = shift; if ( $self->Field eq 'Password' ) { - return $self->loc('Password changed'); + return ('Password changed'); #loc() } elsif ( $self->Field eq 'Queue' ) { my $q1 = RT::Queue->new( $self->CurrentUser ); $q1->Load( $self->OldValue ); my $q2 = RT::Queue->new( $self->CurrentUser ); $q2->Load( $self->NewValue ); - return $self->loc("[_1] changed from [_2] to [_3]", - $self->loc($self->Field) , $q1->Name , $q2->Name); + return ("[_1] changed from [_2] to [_3]", + $self->loc($self->Field) , $q1->Name , $q2->Name); #loc() } # Write the date/time change at local time: @@ -888,7 +1197,7 @@ sub BriefDescription { $t1->Set(Format => 'ISO', Value => $self->NewValue); my $t2 = RT::Date->new($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 ); + return ( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString ); #loc() } elsif ( $self->Field eq 'Owner' ) { my $Old = RT::User->new( $self->CurrentUser ); @@ -898,61 +1207,89 @@ sub BriefDescription { if ( $Old->id == RT->Nobody->id ) { if ( $New->id == $self->Creator ) { - return $self->loc("Taken"); + return ("Taken"); #loc() } else { - return $self->loc( "Given to [_1]", $New->Name ); + return ( "Given to [_1]", $self->_FormatUser($New) ); #loc() } } else { if ( $New->id == $self->Creator ) { - return $self->loc("Stolen from [_1]", $Old->Name); + return ("Stolen from [_1]", $self->_FormatUser($Old) ); #loc() } elsif ( $Old->id == $self->Creator ) { if ( $New->id == RT->Nobody->id ) { - return $self->loc("Untaken"); + return ("Untaken"); #loc() } else { - return $self->loc( "Given to [_1]", $New->Name ); + return ( "Given to [_1]", $self->_FormatUser($New) ); #loc() } } else { - return $self->loc( + return ( "Owner forcibly changed from [_1] to [_2]", - $Old->Name, $New->Name ); + map { $self->_FormatUser($_) } $Old, $New + ); #loc() } } } else { - return $self->loc( "[_1] changed from [_2] to [_3]", - $self->loc($self->Field), - ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" ); + return ( "[_1] changed from [_2] to [_3]", + $self->loc($self->Field), + ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")), + ($self->NewValue? "'".$self->NewValue ."'" : $self->loc("(no value)"))); #loc() + } + }, + "Set-TimeWorked" => sub { + my $self = shift; + my $old = $self->OldValue || 0; + my $new = $self->NewValue || 0; + my $duration = $new - $old; + if ($duration < 0) { + return ("Adjusted time worked by [quant,_1,minute,minutes]", $duration); # loc() + } + elsif ($duration < 60) { + return ("Worked [quant,_1,minute,minutes]", $duration); # loc() + } else { + return ("Worked [quant,_1,hour,hours] ([quant,_2,minute,minutes])", sprintf("%.1f", $duration / 60), $duration); # loc() } }, PurgeTransaction => sub { my $self = shift; - return $self->loc("Transaction [_1] purged", $self->Data); + return ("Transaction [_1] purged", $self->Data); #loc() }, AddReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); - return $self->loc("Reminder '[_1]' added", $ticket->Subject); + my $subject = [ + \'id, \'">', $ticket->Subject, \'' + ]; + return ("Reminder '[_1]' added", $subject); #loc() }, OpenReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); - return $self->loc("Reminder '[_1]' reopened", $ticket->Subject); - + my $subject = [ + \'id, \'">', $ticket->Subject, \'' + ]; + return ("Reminder '[_1]' reopened", $subject); #loc() }, ResolveReminder => sub { my $self = shift; my $ticket = RT::Ticket->new($self->CurrentUser); $ticket->Load($self->NewValue); - return $self->loc("Reminder '[_1]' completed", $ticket->Subject); - - + my $subject = [ + \'id, \'">', $ticket->Subject, \'' + ]; + return ("Reminder '[_1]' completed", $subject); #loc() } ); @@ -1017,23 +1354,6 @@ sub _Value { } - -=head2 CurrentUserHasRight RIGHT - -Calls $self->CurrentUser->HasQueueRight for the right passed in here. -passed in here. - -=cut - -sub CurrentUserHasRight { - my $self = shift; - my $right = shift; - return $self->CurrentUser->HasRight( - Right => $right, - Object => $self->Object - ); -} - =head2 CurrentUserCanSee Returns true if current user has rights to see this particular transaction. @@ -1042,38 +1362,30 @@ 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. +It always returns true if current user is system user. + =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; - } - } + return 1 if $self->CurrentUser->PrincipalObj->Id == RT->SystemUser->Id; + # 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 $type = $self->__Value('Type'); + if ( $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'); } + + # Transactions that might have changed the ->Object's visibility to + # the current user are marked readable + return 1 if $self->{ _object_is_readable }; + # Defer to the object in question - return $self->Object->CurrentUserCanSee("Transaction"); + return $self->Object->CurrentUserCanSee("Transaction", $self); } @@ -1089,11 +1401,7 @@ sub TicketObj { sub OldValue { my $self = shift; - if ( my $type = $self->__Value('ReferenceType') - and my $id = $self->__Value('OldReference') ) - { - my $Object = $type->new($self->CurrentUser); - $Object->Load( $id ); + if ( my $Object = $self->OldReferenceObject ) { return $Object->Content; } else { @@ -1103,11 +1411,7 @@ sub OldValue { sub NewValue { my $self = shift; - if ( my $type = $self->__Value('ReferenceType') - and my $id = $self->__Value('NewReference') ) - { - my $Object = $type->new($self->CurrentUser); - $Object->Load( $id ); + if ( my $Object = $self->NewReferenceObject ) { return $Object->Content; } else { @@ -1122,22 +1426,53 @@ sub Object { return $Object; } +=head2 NewReferenceObject + +=head2 OldReferenceObject + +Returns an object of the class specified by the column C and +loaded with the id specified by the column C or C. +C is assumed to be an L subclass. + +The object may be unloaded (check C<< $object->id >>) if the reference is +corrupt (such as if the referenced record was improperly deleted). + +Returns undef if either C or C/C is +false. + +=cut + +sub NewReferenceObject { $_[0]->_ReferenceObject("New") } +sub OldReferenceObject { $_[0]->_ReferenceObject("Old") } + +sub _ReferenceObject { + my $self = shift; + my $which = shift; + my $type = $self->__Value("ReferenceType"); + my $id = $self->__Value("${which}Reference"); + return unless $type and $id; + + my $object = $type->new($self->CurrentUser); + $object->Load( $id ); + return $object; +} + sub FriendlyObjectType { my $self = shift; - my $type = $self->ObjectType or return undef; - $type =~ s/^RT:://; - return $self->loc($type); + return $self->loc( $self->Object->RecordType ); } =head2 UpdateCustomFields - - Takes a hash of - CustomField-<> => Value - or +Takes a hash of: + + CustomField-C => Value - Object-RT::Transaction-CustomField-<> => Value parameters to update - this transaction's custom fields +or: + + Object-RT::Transaction-CustomField-C => Value + +parameters to update this transaction's custom fields. =cut @@ -1149,12 +1484,9 @@ 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 - - my $args; - - if ($args{'ARGSRef'}) { + my $args; + if ($args{'ARGSRef'}) { + RT->Deprecated( Arguments => "ARGSRef", Remove => "4.4" ); $args = $args{ARGSRef}; } else { $args = \%args; @@ -1168,6 +1500,8 @@ sub UpdateCustomFields { next if $arg =~ /-TimeUnits$/; my $cfid = $1; my $values = $args->{$arg}; + my $cf = $self->LoadCustomFieldByIdentifier($cfid); + next unless $cf->ObjectTypeFromLookupType($cf->__Value('LookupType'))->isa(ref $self); foreach my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values ) { @@ -1181,37 +1515,31 @@ sub UpdateCustomFields { } } +=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 +transaction, overriding L to +look for queue-specific CFs before global ones. =cut -sub CustomFieldValues { +sub LoadCustomFieldByIdentifier { my $self = shift; my $field = shift; - if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) { - - # 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->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; - } - } - return $self->SUPER::CustomFieldValues($field); -} + return $self->SUPER::LoadCustomFieldByIdentifier($field) + if ref $field or $field =~ /^\d+$/; + return $self->SUPER::LoadCustomFieldByIdentifier($field) + unless UNIVERSAL::can( $self->Object, 'QueueObj' ); + my $CFs = RT::CustomFields->new( $self->CurrentUser ); + $CFs->SetContextObject( $self->Object ); + $CFs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 ); + $CFs->LimitToLookupType($self->CustomFieldLookupType); + $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id); + return $CFs->First || RT::CustomField->new( $self->CurrentUser ); +} =head2 CustomFieldLookupType @@ -1320,8 +1648,6 @@ sub DeferredRecipients { # Transactions don't change. by adding this cache config directive, we don't lose pathalogically on long tickets. sub _CacheConfig { { - 'cache_p' => 1, - 'fast_update_p' => 1, 'cache_for_sec' => 6000, } } @@ -1582,37 +1908,182 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ObjectType => - {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, ObjectId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, TimeTaken => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Type => - {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 20, is_blob => 0, is_numeric => 0, type => 'varchar(20)', default => ''}, Field => - {read => 1, write => 1, sql_type => 12, length => 40, is_blob => 0, is_numeric => 0, type => 'varchar(40)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 40, is_blob => 0, is_numeric => 0, type => 'varchar(40)', default => ''}, OldValue => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, NewValue => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ReferenceType => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, OldReference => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, NewReference => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Data => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + $deps->Add( out => $self->Object ); + $deps->Add( in => $self->Attachments ); + + my $type = $self->Type; + if ($type eq "CustomField") { + my $cf = RT::CustomField->new( RT->SystemUser ); + $cf->Load( $self->Field ); + $deps->Add( out => $cf ); + } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) { + for my $field (qw/OldValue NewValue/) { + my $user = RT::User->new( RT->SystemUser ); + $user->Load( $self->$field ); + $deps->Add( out => $user ); + } + } elsif ($type eq "DelWatcher") { + my $principal = RT::Principal->new( RT->SystemUser ); + $principal->Load( $self->OldValue ); + $deps->Add( out => $principal->Object ); + } elsif ($type eq "AddWatcher") { + my $principal = RT::Principal->new( RT->SystemUser ); + $principal->Load( $self->NewValue ); + $deps->Add( out => $principal->Object ); + } elsif ($type eq "DeleteLink") { + if ($self->OldValue) { + my $base = RT::URI->new( $self->CurrentUser ); + $base->FromURI( $self->OldValue ); + $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object; + } + } elsif ($type eq "AddLink") { + if ($self->NewValue) { + my $base = RT::URI->new( $self->CurrentUser ); + $base->FromURI( $self->NewValue ); + $deps->Add( out => $base->Object ) if $base->Resolver and $base->Object; + } + } elsif ($type eq "Set" and $self->Field eq "Queue") { + for my $field (qw/OldValue NewValue/) { + my $queue = RT::Queue->new( RT->SystemUser ); + $queue->Load( $self->$field ); + $deps->Add( out => $queue ); + } + } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) { + my $ticket = RT::Ticket->new( RT->SystemUser ); + $ticket->Load( $self->NewValue ); + $deps->Add( out => $ticket ); + } +} + +sub __DependsOn { + my $self = shift; + my %args = ( + Shredder => undef, + Dependencies => undef, + @_, + ); + my $deps = $args{'Dependencies'}; + + $deps->_PushDependencies( + BaseObject => $self, + Flags => RT::Shredder::Constants::DEPENDS_ON, + TargetObjects => $self->Attachments, + Shredder => $args{'Shredder'} + ); + + return $self->SUPER::__DependsOn( %args ); +} + +sub Serialize { + my $self = shift; + my %args = (@_); + my %store = $self->SUPER::Serialize(@_); + + my $type = $store{Type}; + if ($type eq "CustomField") { + my $cf = RT::CustomField->new( RT->SystemUser ); + $cf->Load( $store{Field} ); + $store{Field} = \($cf->UID); + } elsif ($type =~ /^(Take|Untake|Force|Steal|Give)$/) { + for my $field (qw/OldValue NewValue/) { + my $user = RT::User->new( RT->SystemUser ); + $user->Load( $store{$field} ); + $store{$field} = \($user->UID); + } + } elsif ($type eq "DelWatcher") { + my $principal = RT::Principal->new( RT->SystemUser ); + $principal->Load( $store{OldValue} ); + $store{OldValue} = \($principal->UID); + } elsif ($type eq "AddWatcher") { + my $principal = RT::Principal->new( RT->SystemUser ); + $principal->Load( $store{NewValue} ); + $store{NewValue} = \($principal->UID); + } elsif ($type eq "DeleteLink") { + if ($store{OldValue}) { + my $base = RT::URI->new( $self->CurrentUser ); + $base->FromURI( $store{OldValue} ); + $store{OldValue} = \($base->Object->UID) if $base->Resolver and $base->Object; + } + } elsif ($type eq "AddLink") { + if ($store{NewValue}) { + my $base = RT::URI->new( $self->CurrentUser ); + $base->FromURI( $store{NewValue} ); + $store{NewValue} = \($base->Object->UID) if $base->Resolver and $base->Object; + } + } elsif ($type eq "Set" and $store{Field} eq "Queue") { + for my $field (qw/OldValue NewValue/) { + my $queue = RT::Queue->new( RT->SystemUser ); + $queue->Load( $store{$field} ); + $store{$field} = \($queue->UID); + } + } elsif ($type =~ /^(Add|Open|Resolve)Reminder$/) { + my $ticket = RT::Ticket->new( RT->SystemUser ); + $ticket->Load( $store{NewValue} ); + $store{NewValue} = \($ticket->UID); + } + + return %store; +} + +sub PreInflate { + my $class = shift; + my ($importer, $uid, $data) = @_; + + if ($data->{Object} and ref $data->{Object}) { + my $on_uid = ${ $data->{Object} }; + return if $importer->ShouldSkipTransaction($on_uid); + } + + if ($data->{Type} eq "DeleteLink" and ref $data->{OldValue}) { + my $uid = ${ $data->{OldValue} }; + my $obj = $importer->LookupObj( $uid ); + $data->{OldValue} = $obj->URI; + } elsif ($data->{Type} eq "AddLink" and ref $data->{NewValue}) { + my $uid = ${ $data->{NewValue} }; + my $obj = $importer->LookupObj( $uid ); + $data->{NewValue} = $obj->URI; + } + + return $class->SUPER::PreInflate( $importer, $uid, $data ); +} + RT::Base->_ImportOverlays(); 1;