1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2007 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/copyleft/gpl.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 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,
297 if (my $content_obj = $self->ContentObj) {
298 $content = $content_obj->Content;
300 if ($content_obj->ContentType =~ m{^text/html$}i) {
301 $content =~ s/<p>--\s+<br \/>.*?$//s if $args{'Quote'};
303 if ($args{Type} ne 'text/html') {
304 $content = HTML::FormatText->new(
308 HTML::TreeBuilder->new_from_content( $content )
313 $content =~ s/\n-- \n.*?$//s if $args{'Quote'};
315 if ($args{Type} eq 'text/html') {
316 # Extremely simple text->html converter
317 $content =~ s/&/&/g;
318 $content =~ s/</</g;
319 $content =~ s/>/>/g;
320 $content = "<pre>$content</pre>";
325 # If all else fails, return a message that we couldn't find any content
327 $content = $self->loc('This transaction appears to have no content');
330 if ( $args{'Quote'} ) {
332 # What's the longest line like?
334 foreach ( split ( /\n/, $content ) ) {
335 $max = length if ( length > $max );
339 require Text::Wrapper;
340 my $wrapper = new Text::Wrapper(
341 columns => $args{'Wrap'},
342 body_start => ( $max > 70 * 3 ? ' ' : '' ),
345 $content = $wrapper->wrap($content);
348 $content =~ s/^/> /gm;
349 $content = $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString(), $self->CreatorObj->Name())
362 Returns the RT::Attachment object which contains the content for this Transaction
371 # If we don\'t have any content, return undef now.
372 unless ( $self->Attachments->First ) {
376 # Get the set of toplevel attachments to this transaction.
377 my $Attachment = $self->Attachments->First();
379 # If it's a textual part, just return the body.
380 if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) {
381 return ($Attachment);
384 # If it's a multipart object, first try returning the first part with preferred
385 # MIME type ('text/plain' by default).
387 elsif ( $Attachment->ContentType() =~ '^multipart/' ) {
388 my $plain_parts = $Attachment->Children();
389 $plain_parts->ContentType( VALUE => ($PreferredContentType || 'text/plain') );
391 # If we actully found a part, return its content
392 if ( $plain_parts->First && $plain_parts->First->Content ne '' ) {
393 return ( $plain_parts->First );
397 # If that fails, return the first textual part which has some content.
400 my $all_parts = $self->Attachments();
401 while ( my $part = $all_parts->Next ) {
402 if ( ( RT::I18N::IsTextualContentType($part->ContentType) ) and ( $part->Content() ne '' ) ) {
410 # We found no content. suck
420 If this transaction has attached mime objects, returns the first one's subject
421 Otherwise, returns null
427 if ( $self->Attachments->First ) {
428 return ( $self->Attachments->First->Subject );
437 # {{{ sub Attachments
441 Returns all the RT::Attachment objects which are attached
442 to this transaction. Takes an optional parameter, which is
443 a ContentType that Attachments should be restricted to.
450 unless ( $self->{'attachments'} ) {
451 $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
453 #If it's a comment, return an empty object if they don't have the right to see it
454 if ( $self->Type eq 'Comment' ) {
455 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
456 return ( $self->{'attachments'} );
460 #if they ain't got rights to see, return an empty object
461 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
462 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
463 return ( $self->{'attachments'} );
467 $self->{'attachments'}->Limit( FIELD => 'TransactionId',
468 VALUE => $self->Id );
470 # Get the self->{'attachments'} in the order they're put into
471 # the database. Arguably, we should be returning a tree
472 # of self->{'attachments'}, not a set...but no current app seems to need
475 $self->{'attachments'}->OrderBy( ALIAS => 'main',
480 return ( $self->{'attachments'} );
490 A private method used to attach a mime object to this transaction.
496 my $MIMEObject = shift;
498 if ( !defined($MIMEObject) ) {
500 "$self _Attach: We can't attach a mime object if you don't give us one.\n"
502 return ( 0, $self->loc("[_1]: no attachment specified", $self) );
505 my $Attachment = new RT::Attachment( $self->CurrentUser );
506 my ($id, $msg) = $Attachment->Create(
507 TransactionId => $self->Id,
508 Attachment => $MIMEObject
510 return ( $Attachment, $msg || $self->loc("Attachment created") );
518 # {{{ Routines dealing with Transaction Attributes
520 # {{{ sub Description
524 Returns a text string which describes this transaction
532 #If it's a comment or a comment email record,
533 # we need to be extra special careful
535 if ( $self->__Value('Type') =~ /^Comment/ ) {
536 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
537 return ( $self->loc("Permission Denied") );
541 #if they ain't got rights to see, don't let em
542 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
543 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
544 return ($self->loc("Permission Denied") );
548 if ( !defined( $self->Type ) ) {
549 return ( $self->loc("No transaction type specified"));
552 return ( $self->loc("[_1] by [_2]",$self->BriefDescription , $self->CreatorObj->Name ));
557 # {{{ sub BriefDescription
559 =head2 BriefDescription
561 Returns a text string which briefly describes this transaction
565 sub BriefDescription {
568 #If it's a comment or a comment email record,
569 # we need to be extra special careful
570 if ( $self->__Value('Type') =~ /^Comment/ ) {
571 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
572 return ( $self->loc("Permission Denied") );
576 #if they ain't got rights to see, don't let em
577 elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) {
578 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
579 return ( $self->loc("Permission Denied") );
583 my $type = $self->Type; #cache this, rather than calling it 30 times
585 if ( !defined($type) ) {
586 return $self->loc("No transaction type specified");
589 my $obj_type = $self->FriendlyObjectType;
591 if ( $type eq 'Create' ) {
592 return ( $self->loc( "[_1] created", $obj_type ) );
594 elsif ( $type =~ /Status/ ) {
595 if ( $self->Field eq 'Status' ) {
596 if ( $self->NewValue eq 'deleted' ) {
597 return ( $self->loc( "[_1] deleted", $obj_type ) );
602 "Status changed from [_1] to [_2]",
603 "'" . $self->loc( $self->OldValue ) . "'",
604 "'" . $self->loc( $self->NewValue ) . "'"
612 my $no_value = $self->loc("(no value)");
615 "[_1] changed from [_2] to [_3]",
617 ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ),
618 "'" . $self->NewValue . "'"
623 if ( my $code = $_BriefDescriptions{$type} ) {
624 return $code->($self);
628 "Default: [_1]/[_2] changed from [_3] to [_4]",
633 ? "'" . $self->OldValue . "'"
634 : $self->loc("(no value)")
636 "'" . $self->NewValue . "'"
640 %_BriefDescriptions = (
641 CommentEmailRecord => sub {
643 return $self->loc("Outgoing email about a comment recorded");
647 return $self->loc("Outgoing email recorded");
651 return $self->loc("Correspondence added");
655 return $self->loc("Comments added");
659 my $field = $self->loc('CustomField');
661 if ( $self->Field ) {
662 my $cf = RT::CustomField->new( $self->CurrentUser );
663 $cf->Load( $self->Field );
664 $field = $cf->Name();
667 if ( $self->OldValue eq '' ) {
668 return ( $self->loc("[_1] [_2] added", $field, $self->NewValue) );
670 elsif ( $self->NewValue eq '' ) {
671 return ( $self->loc("[_1] [_2] deleted", $field, $self->OldValue) );
675 return $self->loc("[_1] [_2] changed to [_3]", $field, $self->OldValue, $self->NewValue );
680 return $self->loc("Untaken");
684 return $self->loc("Taken");
688 my $Old = RT::User->new( $self->CurrentUser );
689 $Old->Load( $self->OldValue );
690 my $New = RT::User->new( $self->CurrentUser );
691 $New->Load( $self->NewValue );
693 return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
697 my $Old = RT::User->new( $self->CurrentUser );
698 $Old->Load( $self->OldValue );
699 return $self->loc("Stolen from [_1]", $Old->Name);
703 my $New = RT::User->new( $self->CurrentUser );
704 $New->Load( $self->NewValue );
705 return $self->loc( "Given to [_1]", $New->Name );
709 my $principal = RT::Principal->new($self->CurrentUser);
710 $principal->Load($self->NewValue);
711 return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
715 my $principal = RT::Principal->new($self->CurrentUser);
716 $principal->Load($self->OldValue);
717 return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
721 return $self->loc( "Subject changed to [_1]", $self->Data );
726 if ( $self->NewValue ) {
727 my $URI = RT::URI->new( $self->CurrentUser );
728 $URI->FromURI( $self->NewValue );
729 if ( $URI->Resolver ) {
730 $value = $URI->Resolver->AsString;
733 $value = $self->NewValue;
735 if ( $self->Field eq 'DependsOn' ) {
736 return $self->loc( "Dependency on [_1] added", $value );
738 elsif ( $self->Field eq 'DependedOnBy' ) {
739 return $self->loc( "Dependency by [_1] added", $value );
742 elsif ( $self->Field eq 'RefersTo' ) {
743 return $self->loc( "Reference to [_1] added", $value );
745 elsif ( $self->Field eq 'ReferredToBy' ) {
746 return $self->loc( "Reference by [_1] added", $value );
748 elsif ( $self->Field eq 'MemberOf' ) {
749 return $self->loc( "Membership in [_1] added", $value );
751 elsif ( $self->Field eq 'HasMember' ) {
752 return $self->loc( "Member [_1] added", $value );
754 elsif ( $self->Field eq 'MergedInto' ) {
755 return $self->loc( "Merged into [_1]", $value );
759 return ( $self->Data );
765 if ( $self->OldValue ) {
766 my $URI = RT::URI->new( $self->CurrentUser );
767 $URI->FromURI( $self->OldValue );
768 if ( $URI->Resolver ) {
769 $value = $URI->Resolver->AsString;
772 $value = $self->OldValue;
775 if ( $self->Field eq 'DependsOn' ) {
776 return $self->loc( "Dependency on [_1] deleted", $value );
778 elsif ( $self->Field eq 'DependedOnBy' ) {
779 return $self->loc( "Dependency by [_1] deleted", $value );
782 elsif ( $self->Field eq 'RefersTo' ) {
783 return $self->loc( "Reference to [_1] deleted", $value );
785 elsif ( $self->Field eq 'ReferredToBy' ) {
786 return $self->loc( "Reference by [_1] deleted", $value );
788 elsif ( $self->Field eq 'MemberOf' ) {
789 return $self->loc( "Membership in [_1] deleted", $value );
791 elsif ( $self->Field eq 'HasMember' ) {
792 return $self->loc( "Member [_1] deleted", $value );
796 return ( $self->Data );
801 if ( $self->Field eq 'Password' ) {
802 return $self->loc('Password changed');
804 elsif ( $self->Field eq 'Queue' ) {
805 my $q1 = new RT::Queue( $self->CurrentUser );
806 $q1->Load( $self->OldValue );
807 my $q2 = new RT::Queue( $self->CurrentUser );
808 $q2->Load( $self->NewValue );
809 return $self->loc("[_1] changed from [_2] to [_3]", $self->Field , $q1->Name , $q2->Name);
812 # Write the date/time change at local time:
813 elsif ($self->Field =~ /Due|Starts|Started|Told/) {
814 my $t1 = new RT::Date($self->CurrentUser);
815 $t1->Set(Format => 'ISO', Value => $self->NewValue);
816 my $t2 = new RT::Date($self->CurrentUser);
817 $t2->Set(Format => 'ISO', Value => $self->OldValue);
818 return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $t2->AsString, $t1->AsString );
821 return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
824 PurgeTransaction => sub {
826 return $self->loc("Transaction [_1] purged", $self->Data);
830 my $ticket = RT::Ticket->new($self->CurrentUser);
831 $ticket->Load($self->NewValue);
832 return $self->loc("Reminder '[_1]' added", $ticket->Subject);
834 OpenReminder => sub {
836 my $ticket = RT::Ticket->new($self->CurrentUser);
837 $ticket->Load($self->NewValue);
838 return $self->loc("Reminder '[_1]' reopened", $ticket->Subject);
841 ResolveReminder => sub {
843 my $ticket = RT::Ticket->new($self->CurrentUser);
844 $ticket->Load($self->NewValue);
845 return $self->loc("Reminder '[_1]' completed", $ticket->Subject);
853 # {{{ Utility methods
859 Returns true if the creator of the transaction is a requestor of the ticket.
860 Returns false otherwise
866 $self->ObjectType eq 'RT::Ticket' or return undef;
867 return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
874 sub _OverlayAccessible {
877 ObjectType => { public => 1},
878 ObjectId => { public => 1},
891 return ( 0, $self->loc('Transactions are immutable') );
900 Takes the name of a table column.
901 Returns its value as a string, if the user passes an ACL check
910 #if the field is public, return it.
911 if ( $self->_Accessible( $field, 'public' ) ) {
912 return ( $self->__Value($field) );
916 #If it's a comment, we need to be extra special careful
917 if ( $self->__Value('Type') eq 'Comment' ) {
918 unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
922 elsif ( $self->__Value('Type') eq 'CommentEmailRecord' ) {
923 unless ( $self->CurrentUserHasRight('ShowTicketComments')
924 && $self->CurrentUserHasRight('ShowOutgoingEmail') ) {
929 elsif ( $self->__Value('Type') eq 'EmailRecord' ) {
930 unless ( $self->CurrentUserHasRight('ShowOutgoingEmail')) {
935 # Make sure the user can see the custom field before showing that it changed
936 elsif ( ( $self->__Value('Type') eq 'CustomField' ) && $self->__Value('Field') ) {
937 my $cf = RT::CustomField->new( $self->CurrentUser );
938 $cf->Load( $self->__Value('Field') );
939 return (undef) unless ( $cf->CurrentUserHasRight('SeeCustomField') );
943 #if they ain't got rights to see, don't let em
944 elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
945 unless ( $self->CurrentUserHasRight('ShowTicket') ) {
950 return ( $self->__Value($field) );
956 # {{{ sub CurrentUserHasRight
958 =head2 CurrentUserHasRight RIGHT
960 Calls $self->CurrentUser->HasQueueRight for the right passed in here.
965 sub CurrentUserHasRight {
969 $self->CurrentUser->HasRight(
971 Object => $self->TicketObj
980 return $self->ObjectId;
985 return $self->Object;
990 if ( my $type = $self->__Value('ReferenceType')
991 and my $id = $self->__Value('OldReference') )
993 my $Object = $type->new($self->CurrentUser);
994 $Object->Load( $id );
995 return $Object->Content;
998 return $self->__Value('OldValue');
1004 if ( my $type = $self->__Value('ReferenceType')
1005 and my $id = $self->__Value('NewReference') )
1007 my $Object = $type->new($self->CurrentUser);
1008 $Object->Load( $id );
1009 return $Object->Content;
1012 return $self->__Value('NewValue');
1018 my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
1019 $Object->Load($self->__Value('ObjectId'));
1023 sub FriendlyObjectType {
1025 my $type = $self->ObjectType or return undef;
1027 return $self->loc($type);
1030 =head2 UpdateCustomFields
1034 CustomField-<<Id>> => Value
1037 Object-RT::Transaction-CustomField-<<Id>> => Value parameters to update
1038 this transaction's custom fields
1042 sub UpdateCustomFields {
1046 # This method used to have an API that took a hash of a single
1047 # value "ARGSRef", which was a reference to a hash of arguments.
1048 # This was insane. The next few lines of code preserve that API
1049 # while giving us something saner.
1052 # TODO: 3.6: DEPRECATE OLD API
1056 if ($args{'ARGSRef'}) {
1057 $args = $args{ARGSRef};
1062 foreach my $arg ( keys %$args ) {
1065 /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ );
1066 next if $arg =~ /-Magic$/;
1068 my $values = $args->{$arg};
1070 my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values )
1072 next unless length($value);
1073 $self->_AddCustomFieldValue(
1076 RecordTransaction => 0,
1084 =head2 CustomFieldValues
1086 Do name => id mapping (if needed) before falling back to RT::Record's CustomFieldValues
1092 sub CustomFieldValues {
1096 if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) {
1098 unless ( defined $field && $field =~ /^\d+$/o ) {
1099 my $CFs = RT::CustomFields->new( $self->CurrentUser );
1100 $CFs->Limit( FIELD => 'Name', VALUE => $field);
1101 $CFs->LimitToLookupType($self->CustomFieldLookupType);
1102 $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
1103 $field = $CFs->First->id if $CFs->First;
1106 return $self->SUPER::CustomFieldValues($field);
1111 # {{{ sub CustomFieldLookupType
1113 =head2 CustomFieldLookupType
1115 Returns the RT::Transaction lookup type, which can
1116 be passed to RT::CustomField->Create() via the 'LookupType' hash key.
1122 sub CustomFieldLookupType {
1123 "RT::Queue-RT::Ticket-RT::Transaction";
1126 # Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
1130 'fast_update_p' => 1,
1131 'cache_for_sec' => 6000,