1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
50 RT::Transaction - RT\'s transaction object
60 Each RT::Transaction describes an atomic change to a ticket object
61 or an update to an RT::Ticket object.
62 It can have arbitrary MIME attachments.
69 ok(require RT::Transaction);
76 package RT::Transaction;
79 no warnings qw(redefine);
81 use vars qw( %_BriefDescriptions $PreferredContentType );
87 use HTML::TreeBuilder;
94 Create a new transaction.
96 This routine should _never_ be called by anything other than RT::Ticket.
97 It should not be called
98 from client code. Ever. Not ever. If you do this, we will hunt you down and break your kneecaps.
99 Then the unpleasant stuff will start.
101 TODO: Document what gets passed to this
118 ObjectType => 'RT::Ticket',
120 ReferenceType => undef,
121 OldReference => undef,
122 NewReference => undef,
126 $args{ObjectId} ||= $args{Ticket};
128 #if we didn't specify a ticket, we need to bail
129 unless ( $args{'ObjectId'} && $args{'ObjectType'}) {
130 return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
135 #lets create our transaction
137 Type => $args{'Type'},
138 Data => $args{'Data'},
139 Field => $args{'Field'},
140 OldValue => $args{'OldValue'},
141 NewValue => $args{'NewValue'},
142 Created => $args{'Created'},
143 ObjectType => $args{'ObjectType'},
144 ObjectId => $args{'ObjectId'},
145 ReferenceType => $args{'ReferenceType'},
146 OldReference => $args{'OldReference'},
147 NewReference => $args{'NewReference'},
150 # Parameters passed in during an import that we probably don't want to touch, otherwise
151 foreach my $attr qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy) {
152 $params{$attr} = $args{$attr} if ($args{$attr});
155 my $id = $self->SUPER::Create(%params);
157 if ( defined $args{'MIMEObj'} ) {
158 my ($id, $msg) = $self->_Attach( $args{'MIMEObj'} );
160 $RT::Logger->error("Couldn't add attachment: $msg");
161 return ( 0, $self->loc("Couldn't add attachment") );
166 #Provide a way to turn off scrips if we need to
167 $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id);
168 if ( $args{'ActivateScrips'} and $args{'ObjectType'} eq 'RT::Ticket' ) {
169 $self->{'scrips'} = RT::Scrips->new($RT::SystemUser);
171 $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id);
173 $self->{'scrips'}->Prepare(
174 Stage => 'TransactionCreate',
175 Type => $args{'Type'},
176 Ticket => $args{'ObjectId'},
177 Transaction => $self->id,
179 if ($args{'CommitScrips'} ) {
180 $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id);
181 $self->{'scrips'}->Commit();
185 return ( $id, $self->loc("Transaction Created") );
192 Returns the Scrips object for this transaction.
193 This routine is only useful on a freshly created transaction object.
194 Scrips do not get persisted to the database with transactions.
202 return($self->{'scrips'});
210 Delete this transaction. Currently DOES NOT CHECK ACLS
218 $RT::Handle->BeginTransaction();
220 my $attachments = $self->Attachments;
222 while (my $attachment = $attachments->Next) {
223 my ($id, $msg) = $attachment->Delete();
225 $RT::Handle->Rollback();
226 return($id, $self->loc("System Error: [_1]", $msg));
229 my ($id,$msg) = $self->SUPER::Delete();
231 $RT::Handle->Rollback();
232 return($id, $self->loc("System Error: [_1]", $msg));
234 $RT::Handle->Commit();
240 # {{{ Routines dealing with Attachments
246 Returns the RT::Attachments Object which contains the "top-level"object
247 attachment for this transaction
255 if ( !defined( $self->{'message'} ) ) {
257 $self->{'message'} = new RT::Attachments( $self->CurrentUser );
258 $self->{'message'}->Limit(
259 FIELD => 'TransactionId',
263 $self->{'message'}->ChildrenOf(0);
265 return ( $self->{'message'} );
272 =head2 Content PARAMHASH
274 If this transaction has attached mime objects, returns the body of the first
275 textual part (as defined in RT::I18N::IsTextualContentType). Otherwise,
278 Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message
279 at $args{'Wrap'}. $args{'Wrap'} defaults to $RT::MessageBoxWidth - 2 or 70.
281 If $args{'Type'} is set to C<text/html>, plain texts are upgraded to HTML.
282 Otherwise, HTML texts are downgraded to plain text. If $args{'Type'} is
283 missing, it defaults to the value of C<$RT::Transaction::PreferredContentType>.
290 Type => $PreferredContentType,
293 Wrap => ( $RT::MessageBoxWidth || 72 ) - 2,
298 if (my $content_obj = $self->ContentObj) {
299 $content = $content_obj->Content;
301 if ($content_obj->ContentType =~ m{^text/html$}i) {
302 $content =~ s/<p>--\s+<br \/>.*?$//s if $args{'Quote'};
304 if ($args{Type} ne 'text/html') {
305 $content = HTML::FormatText->new(
309 HTML::TreeBuilder->new_from_content( $content )
314 $content =~ s/\n-- \n.*?$//s if $args{'Quote'};
316 if ($args{Type} eq 'text/html') {
317 # Extremely simple text->html converter
318 $content =~ s/&/&/g;
319 $content =~ s/</</g;
320 $content =~ s/>/>/g;
321 $content = "<pre>$content</pre>";
326 # If all else fails, return a message that we couldn't find any content
328 $content = $self->loc('This transaction appears to have no content');
331 if ( $args{'Quote'} ) {
333 # What's the longest line like?
335 foreach ( split ( /\n/, $content ) ) {
336 $max = length if ( length > $max );
339 if ( $max > $args{'Wrap'}+6 ) { # 76 ) {
340 require Text::Wrapper;
341 my $wrapper = new Text::Wrapper(
342 columns => $args{'Wrap'},
343 body_start => ( $max > 70 * 3 ? ' ' : '' ),
346 $content = $wrapper->wrap($content);
349 $content =~ s/^/> /gm;
350 $content = $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString(), $self->CreatorObj->Name())
363 Returns the RT::Attachment object which contains the content for this Transaction
372 # If we don\'t have any content, return undef now.
373 unless ( $self->Attachments->First ) {
377 # Get the set of toplevel attachments to this transaction.
378 my $Attachment = $self->Attachments->First();
380 # If it's a textual part, just return the body.
381 if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) {
382 return ($Attachment);
385 # If it's a multipart object, first try returning the first part with preferred
386 # MIME type ('text/plain' by default).
388 elsif ( $Attachment->ContentType() =~ '^multipart/' ) {
389 my $plain_parts = $Attachment->Children();
390 $plain_parts->ContentType( VALUE => ($PreferredContentType || 'text/plain') );
392 # If we actully found a part, return its content
393 if ( $plain_parts->First && $plain_parts->First->Content ne '' ) {
394 return ( $plain_parts->First );
398 # If that fails, return the first textual part which has some content.
401 my $all_parts = $self->Attachments();
402 while ( my $part = $all_parts->Next ) {
403 if ( ( RT::I18N::IsTextualContentType($part->ContentType) ) and ( $part->Content() ne '' ) ) {
411 # We found no content. suck
421 If this transaction has attached mime objects, returns the first one's subject
422 Otherwise, returns null
428 if ( $self->Attachments->First ) {
429 return ( $self->Attachments->First->Subject );
438 # {{{ sub Attachments
442 Returns all the RT::Attachment objects which are attached
443 to this transaction. Takes an optional parameter, which is
444 a ContentType that Attachments should be restricted to.
451 unless ( $self->{'attachments'} ) {
452 $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
454 #If it's a comment, return an empty object if they don't have the right to see it
455 if ( $self->Type eq 'Comment' ) {
456 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
457 return ( $self->{'attachments'} );
461 #if they ain't got rights to see, return an empty object
462 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
463 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
464 return ( $self->{'attachments'} );
468 $self->{'attachments'}->Limit( FIELD => 'TransactionId',
469 VALUE => $self->Id );
471 # Get the self->{'attachments'} in the order they're put into
472 # the database. Arguably, we should be returning a tree
473 # of self->{'attachments'}, not a set...but no current app seems to need
476 $self->{'attachments'}->OrderBy( ALIAS => 'main',
481 return ( $self->{'attachments'} );
491 A private method used to attach a mime object to this transaction.
497 my $MIMEObject = shift;
499 if ( !defined($MIMEObject) ) {
501 "$self _Attach: We can't attach a mime object if you don't give us one.\n"
503 return ( 0, $self->loc("[_1]: no attachment specified", $self) );
506 my $Attachment = new RT::Attachment( $self->CurrentUser );
507 my ($id, $msg) = $Attachment->Create(
508 TransactionId => $self->Id,
509 Attachment => $MIMEObject
511 return ( $Attachment, $msg || $self->loc("Attachment created") );
519 # {{{ Routines dealing with Transaction Attributes
521 # {{{ sub Description
525 Returns a text string which describes this transaction
533 #If it's a comment or a comment email record,
534 # we need to be extra special careful
536 if ( $self->__Value('Type') =~ /^Comment/ ) {
537 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
538 return ( $self->loc("Permission Denied") );
542 #if they ain't got rights to see, don't let em
543 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
544 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
545 return ($self->loc("Permission Denied") );
549 if ( !defined( $self->Type ) ) {
550 return ( $self->loc("No transaction type specified"));
553 return ( $self->loc("[_1] by [_2]",$self->BriefDescription , $self->CreatorObj->Name ));
558 # {{{ sub BriefDescription
560 =head2 BriefDescription
562 Returns a text string which briefly describes this transaction
566 sub BriefDescription {
569 #If it's a comment or a comment email record,
570 # we need to be extra special careful
571 if ( $self->__Value('Type') =~ /^Comment/ ) {
572 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
573 return ( $self->loc("Permission Denied") );
577 #if they ain't got rights to see, don't let em
578 elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) {
579 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
580 return ( $self->loc("Permission Denied") );
584 my $type = $self->Type; #cache this, rather than calling it 30 times
586 if ( !defined($type) ) {
587 return $self->loc("No transaction type specified");
590 my $obj_type = $self->FriendlyObjectType;
592 if ( $type eq 'Create' ) {
593 return ( $self->loc( "[_1] created", $obj_type ) );
595 elsif ( $type =~ /Status/ ) {
596 if ( $self->Field eq 'Status' ) {
597 if ( $self->NewValue eq 'deleted' ) {
598 return ( $self->loc( "[_1] deleted", $obj_type ) );
603 "Status changed from [_1] to [_2]",
604 "'" . $self->loc( $self->OldValue ) . "'",
605 "'" . $self->loc( $self->NewValue ) . "'"
613 my $no_value = $self->loc("(no value)");
616 "[_1] changed from [_2] to [_3]",
618 ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ),
619 "'" . $self->NewValue . "'"
624 if ( my $code = $_BriefDescriptions{$type} ) {
625 return $code->($self);
629 "Default: [_1]/[_2] changed from [_3] to [_4]",
634 ? "'" . $self->OldValue . "'"
635 : $self->loc("(no value)")
637 "'" . $self->NewValue . "'"
641 %_BriefDescriptions = (
642 CommentEmailRecord => sub {
644 return $self->loc("Outgoing email about a comment recorded");
648 return $self->loc("Outgoing email recorded");
652 return $self->loc("Correspondence added");
656 return $self->loc("Comments added");
660 my $field = $self->loc('CustomField');
662 if ( $self->Field ) {
663 my $cf = RT::CustomField->new( $self->CurrentUser );
664 $cf->Load( $self->Field );
665 $field = $cf->Name();
668 if ( $self->OldValue eq '' ) {
669 return ( $self->loc("[_1] [_2] added", $field, $self->NewValue) );
671 elsif ( $self->NewValue eq '' ) {
672 return ( $self->loc("[_1] [_2] deleted", $field, $self->OldValue) );
676 return $self->loc("[_1] [_2] changed to [_3]", $field, $self->OldValue, $self->NewValue );
681 return $self->loc("Untaken");
685 return $self->loc("Taken");
689 my $Old = RT::User->new( $self->CurrentUser );
690 $Old->Load( $self->OldValue );
691 my $New = RT::User->new( $self->CurrentUser );
692 $New->Load( $self->NewValue );
694 return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
698 my $Old = RT::User->new( $self->CurrentUser );
699 $Old->Load( $self->OldValue );
700 return $self->loc("Stolen from [_1]", $Old->Name);
704 my $New = RT::User->new( $self->CurrentUser );
705 $New->Load( $self->NewValue );
706 return $self->loc( "Given to [_1]", $New->Name );
710 my $principal = RT::Principal->new($self->CurrentUser);
711 $principal->Load($self->NewValue);
712 return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
716 my $principal = RT::Principal->new($self->CurrentUser);
717 $principal->Load($self->OldValue);
718 return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
722 return $self->loc( "Subject changed to [_1]", $self->Data );
727 if ( $self->NewValue ) {
728 my $URI = RT::URI->new( $self->CurrentUser );
729 $URI->FromURI( $self->NewValue );
730 if ( $URI->Resolver ) {
731 $value = $URI->Resolver->AsString;
734 $value = $self->NewValue;
736 if ( $self->Field eq 'DependsOn' ) {
737 return $self->loc( "Dependency on [_1] added", $value );
739 elsif ( $self->Field eq 'DependedOnBy' ) {
740 return $self->loc( "Dependency by [_1] added", $value );
743 elsif ( $self->Field eq 'RefersTo' ) {
744 return $self->loc( "Reference to [_1] added", $value );
746 elsif ( $self->Field eq 'ReferredToBy' ) {
747 return $self->loc( "Reference by [_1] added", $value );
749 elsif ( $self->Field eq 'MemberOf' ) {
750 return $self->loc( "Membership in [_1] added", $value );
752 elsif ( $self->Field eq 'HasMember' ) {
753 return $self->loc( "Member [_1] added", $value );
755 elsif ( $self->Field eq 'MergedInto' ) {
756 return $self->loc( "Merged into [_1]", $value );
760 return ( $self->Data );
766 if ( $self->OldValue ) {
767 my $URI = RT::URI->new( $self->CurrentUser );
768 $URI->FromURI( $self->OldValue );
769 if ( $URI->Resolver ) {
770 $value = $URI->Resolver->AsString;
773 $value = $self->OldValue;
776 if ( $self->Field eq 'DependsOn' ) {
777 return $self->loc( "Dependency on [_1] deleted", $value );
779 elsif ( $self->Field eq 'DependedOnBy' ) {
780 return $self->loc( "Dependency by [_1] deleted", $value );
783 elsif ( $self->Field eq 'RefersTo' ) {
784 return $self->loc( "Reference to [_1] deleted", $value );
786 elsif ( $self->Field eq 'ReferredToBy' ) {
787 return $self->loc( "Reference by [_1] deleted", $value );
789 elsif ( $self->Field eq 'MemberOf' ) {
790 return $self->loc( "Membership in [_1] deleted", $value );
792 elsif ( $self->Field eq 'HasMember' ) {
793 return $self->loc( "Member [_1] deleted", $value );
797 return ( $self->Data );
802 if ( $self->Field eq 'Password' ) {
803 return $self->loc('Password changed');
805 elsif ( $self->Field eq 'Queue' ) {
806 my $q1 = new RT::Queue( $self->CurrentUser );
807 $q1->Load( $self->OldValue );
808 my $q2 = new RT::Queue( $self->CurrentUser );
809 $q2->Load( $self->NewValue );
810 return $self->loc("[_1] changed from [_2] to [_3]", $self->Field , $q1->Name , $q2->Name);
813 # Write the date/time change at local time:
814 elsif ($self->Field =~ /Due|Starts|Started|Told/) {
815 my $t1 = new RT::Date($self->CurrentUser);
816 $t1->Set(Format => 'ISO', Value => $self->NewValue);
817 my $t2 = new RT::Date($self->CurrentUser);
818 $t2->Set(Format => 'ISO', Value => $self->OldValue);
819 return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $t2->AsString, $t1->AsString );
822 return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
825 PurgeTransaction => sub {
827 return $self->loc("Transaction [_1] purged", $self->Data);
831 my $ticket = RT::Ticket->new($self->CurrentUser);
832 $ticket->Load($self->NewValue);
833 return $self->loc("Reminder '[_1]' added", $ticket->Subject);
835 OpenReminder => sub {
837 my $ticket = RT::Ticket->new($self->CurrentUser);
838 $ticket->Load($self->NewValue);
839 return $self->loc("Reminder '[_1]' reopened", $ticket->Subject);
842 ResolveReminder => sub {
844 my $ticket = RT::Ticket->new($self->CurrentUser);
845 $ticket->Load($self->NewValue);
846 return $self->loc("Reminder '[_1]' completed", $ticket->Subject);
854 # {{{ Utility methods
860 Returns true if the creator of the transaction is a requestor of the ticket.
861 Returns false otherwise
867 $self->ObjectType eq 'RT::Ticket' or return undef;
868 return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
875 sub _OverlayAccessible {
878 ObjectType => { public => 1},
879 ObjectId => { public => 1},
892 return ( 0, $self->loc('Transactions are immutable') );
901 Takes the name of a table column.
902 Returns its value as a string, if the user passes an ACL check
911 #if the field is public, return it.
912 if ( $self->_Accessible( $field, 'public' ) ) {
913 return ( $self->__Value($field) );
917 #If it's a comment, we need to be extra special careful
918 if ( $self->__Value('Type') eq 'Comment' ) {
919 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
923 elsif ( $self->__Value('Type') eq 'CommentEmailRecord' ) {
924 unless ( $self->CurrentUserHasRight('ShowTicketComments')
925 && $self->CurrentUserHasRight('ShowOutgoingEmail') ) {
930 elsif ( $self->__Value('Type') eq 'EmailRecord' ) {
931 unless ( $self->CurrentUserHasRight('ShowOutgoingEmail')) {
936 # Make sure the user can see the custom field before showing that it changed
937 elsif ( ( $self->__Value('Type') eq 'CustomField' ) && $self->__Value('Field') ) {
938 my $cf = RT::CustomField->new( $self->CurrentUser );
939 $cf->Load( $self->__Value('Field') );
940 return (undef) unless ( $cf->CurrentUserHasRight('SeeCustomField') );
944 #if they ain't got rights to see, don't let em
945 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
946 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
951 return ( $self->__Value($field) );
957 # {{{ sub CurrentUserHasRight
959 =head2 CurrentUserHasRight RIGHT
961 Calls $self->CurrentUser->HasQueueRight for the right passed in here.
966 sub CurrentUserHasRight {
970 $self->CurrentUser->HasRight(
972 Object => $self->TicketObj
981 return $self->ObjectId;
986 return $self->Object;
991 if ( my $type = $self->__Value('ReferenceType')
992 and my $id = $self->__Value('OldReference') )
994 my $Object = $type->new($self->CurrentUser);
995 $Object->Load( $id );
996 return $Object->Content;
999 return $self->__Value('OldValue');
1005 if ( my $type = $self->__Value('ReferenceType')
1006 and my $id = $self->__Value('NewReference') )
1008 my $Object = $type->new($self->CurrentUser);
1009 $Object->Load( $id );
1010 return $Object->Content;
1013 return $self->__Value('NewValue');
1019 my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
1020 $Object->Load($self->__Value('ObjectId'));
1024 sub FriendlyObjectType {
1026 my $type = $self->ObjectType or return undef;
1028 return $self->loc($type);
1031 =head2 UpdateCustomFields
1035 CustomField-<<Id>> => Value
1038 Object-RT::Transaction-CustomField-<<Id>> => Value parameters to update
1039 this transaction's custom fields
1043 sub UpdateCustomFields {
1047 # This method used to have an API that took a hash of a single
1048 # value "ARGSRef", which was a reference to a hash of arguments.
1049 # This was insane. The next few lines of code preserve that API
1050 # while giving us something saner.
1053 # TODO: 3.6: DEPRECATE OLD API
1057 if ($args{'ARGSRef'}) {
1058 $args = $args{ARGSRef};
1063 foreach my $arg ( keys %$args ) {
1066 /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ );
1067 next if $arg =~ /-Magic$/;
1069 my $values = $args->{$arg};
1071 my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values )
1073 next unless length($value);
1074 $self->_AddCustomFieldValue(
1077 RecordTransaction => 0,
1085 =head2 CustomFieldValues
1087 Do name => id mapping (if needed) before falling back to RT::Record's CustomFieldValues
1093 sub CustomFieldValues {
1097 if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) {
1099 unless ( defined $field && $field =~ /^\d+$/o ) {
1100 my $CFs = RT::CustomFields->new( $self->CurrentUser );
1101 $CFs->Limit( FIELD => 'Name', VALUE => $field);
1102 $CFs->LimitToLookupType($self->CustomFieldLookupType);
1103 $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
1104 $field = $CFs->First->id if $CFs->First;
1107 return $self->SUPER::CustomFieldValues($field);
1112 # {{{ sub CustomFieldLookupType
1114 =head2 CustomFieldLookupType
1116 Returns the RT::Transaction lookup type, which can
1117 be passed to RT::CustomField->Create() via the 'LookupType' hash key.
1123 sub CustomFieldLookupType {
1124 "RT::Queue-RT::Ticket-RT::Transaction";
1127 # Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
1131 'fast_update_p' => 1,
1132 'cache_for_sec' => 6000,