import rt 3.4.6
[freeside.git] / rt / lib / RT / Transaction_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
6 #                                          <jesse@bestpractical.com>
7
8 # (Except where explicitly superseded by other copyright notices)
9
10
11 # LICENSE:
12
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
16 # from www.gnu.org.
17
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.
22
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., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # END BPS TAGGED BLOCK }}}
46
47 =head1 NAME
48
49   RT::Transaction - RT\'s transaction object
50
51 =head1 SYNOPSIS
52
53   use RT::Transaction;
54
55
56 =head1 DESCRIPTION
57
58
59 Each RT::Transaction describes an atomic change to a ticket object 
60 or an update to an RT::Ticket object.
61 It can have arbitrary MIME attachments.
62
63
64 =head1 METHODS
65
66 =begin testing
67
68 ok(require RT::Transaction);
69
70 =end testing
71
72 =cut
73
74
75 package RT::Transaction;
76
77 use strict;
78 no warnings qw(redefine);
79
80 use vars qw( %_BriefDescriptions );
81
82 use RT::Attachments;
83 use RT::Scrips;
84
85 # {{{ sub Create 
86
87 =head2 Create
88
89 Create a new transaction.
90
91 This routine should _never_ be called by anything other than RT::Ticket. 
92 It should not be called 
93 from client code. Ever. Not ever.  If you do this, we will hunt you down and break your kneecaps.
94 Then the unpleasant stuff will start.
95
96 TODO: Document what gets passed to this
97
98 =cut
99
100 sub Create {
101     my $self = shift;
102     my %args = (
103         id             => undef,
104         TimeTaken      => 0,
105         Type           => 'undefined',
106         Data           => '',
107         Field          => undef,
108         OldValue       => undef,
109         NewValue       => undef,
110         MIMEObj        => undef,
111         ActivateScrips => 1,
112         CommitScrips => 1,
113         ObjectType => 'RT::Ticket',
114         ObjectId => 0,
115         ReferenceType => undef,
116         OldReference       => undef,
117         NewReference       => undef,
118         @_
119     );
120
121     $args{ObjectId} ||= $args{Ticket};
122
123     #if we didn't specify a ticket, we need to bail
124     unless ( $args{'ObjectId'} && $args{'ObjectType'}) {
125         return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
126     }
127
128
129
130     #lets create our transaction
131     my %params = (
132         Type      => $args{'Type'},
133         Data      => $args{'Data'},
134         Field     => $args{'Field'},
135         OldValue  => $args{'OldValue'},
136         NewValue  => $args{'NewValue'},
137         Created   => $args{'Created'},
138         ObjectType => $args{'ObjectType'},
139         ObjectId => $args{'ObjectId'},
140         ReferenceType => $args{'ReferenceType'},
141         OldReference => $args{'OldReference'},
142         NewReference => $args{'NewReference'},
143     );
144
145     # Parameters passed in during an import that we probably don't want to touch, otherwise
146     foreach my $attr qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy) {
147         $params{$attr} = $args{$attr} if ($args{$attr});
148     }
149  
150     my $id = $self->SUPER::Create(%params);
151     $self->Load($id);
152     if ( defined $args{'MIMEObj'} ) {
153         my ($id, $msg) = $self->_Attach( $args{'MIMEObj'} );
154         unless ( $id ) {
155             $RT::Logger->error("Couldn't add attachment: $msg");
156             return ( 0, $self->loc("Couldn't add attachment") );
157         }
158     }
159
160
161     #Provide a way to turn off scrips if we need to
162         $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id);
163     if ( $args{'ActivateScrips'} and $args{'ObjectType'} eq 'RT::Ticket' ) {
164        $self->{'scrips'} = RT::Scrips->new($RT::SystemUser);
165
166         $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id); 
167
168         $self->{'scrips'}->Prepare(
169             Stage       => 'TransactionCreate',
170             Type        => $args{'Type'},
171             Ticket      => $args{'ObjectId'},
172             Transaction => $self->id,
173         );
174         if ($args{'CommitScrips'} ) {
175             $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id);
176             $self->{'scrips'}->Commit();
177         }
178     }
179
180     return ( $id, $self->loc("Transaction Created") );
181 }
182
183 # }}}
184
185 =head2 Scrips
186
187 Returns the Scrips object for this transaction.
188 This routine is only useful on a freshly created transaction object.
189 Scrips do not get persisted to the database with transactions.
190
191
192 =cut
193
194
195 sub Scrips {
196     my $self = shift;
197     return($self->{'scrips'});
198 }
199
200
201 # {{{ sub Delete
202
203 =head2 Delete
204
205 Delete this transaction. Currently DOES NOT CHECK ACLS
206
207 =cut
208
209 sub Delete {
210     my $self = shift;
211
212
213     $RT::Handle->BeginTransaction();
214
215     my $attachments = $self->Attachments;
216
217     while (my $attachment = $attachments->Next) {
218         my ($id, $msg) = $attachment->Delete();
219         unless ($id) {
220             $RT::Handle->Rollback();
221             return($id, $self->loc("System Error: [_1]", $msg));
222         }
223     }
224     my ($id,$msg) = $self->SUPER::Delete();
225         unless ($id) {
226             $RT::Handle->Rollback();
227             return($id, $self->loc("System Error: [_1]", $msg));
228         }
229     $RT::Handle->Commit();
230     return ($id,$msg);
231 }
232
233 # }}}
234
235 # {{{ Routines dealing with Attachments
236
237 # {{{ sub Message 
238
239 =head2 Message
240
241   Returns the RT::Attachments Object which contains the "top-level"object
242   attachment for this transaction
243
244 =cut
245
246 sub Message {
247
248     my $self = shift;
249     
250     if ( !defined( $self->{'message'} ) ) {
251
252         $self->{'message'} = new RT::Attachments( $self->CurrentUser );
253         $self->{'message'}->Limit(
254             FIELD => 'TransactionId',
255             VALUE => $self->Id
256         );
257
258         $self->{'message'}->ChildrenOf(0);
259     }
260     return ( $self->{'message'} );
261 }
262
263 # }}}
264
265 # {{{ sub Content
266
267 =head2 Content PARAMHASH
268
269 If this transaction has attached mime objects, returns the first text/plain part.
270 Otherwise, returns undef.
271
272 Takes a paramhash.  If the $args{'Quote'} parameter is set, wraps this message 
273 at $args{'Wrap'}.  $args{'Wrap'} defaults to 70.
274
275
276 =cut
277
278 sub Content {
279     my $self = shift;
280     my %args = (
281         Quote => 0,
282         Wrap  => 70,
283         @_
284     );
285
286     my $content;
287     my $content_obj = $self->ContentObj;
288     if ($content_obj) {
289         $content = $content_obj->Content;
290     }
291
292     # If all else fails, return a message that we couldn't find any content
293     else {
294         $content = $self->loc('This transaction appears to have no content');
295     }
296
297     if ( $args{'Quote'} ) {
298
299         # Remove quoted signature.
300         $content =~ s/\n-- \n(.*?)$//s;
301
302         # What's the longest line like?
303         my $max = 0;
304         foreach ( split ( /\n/, $content ) ) {
305             $max = length if ( length > $max );
306         }
307
308         if ( $max > 76 ) {
309             require Text::Wrapper;
310             my $wrapper = new Text::Wrapper(
311                 columns    => $args{'Wrap'},
312                 body_start => ( $max > 70 * 3 ? '   ' : '' ),
313                 par_start  => ''
314             );
315             $content = $wrapper->wrap($content);
316         }
317
318         $content = '['
319           . $self->CreatorObj->Name() . ' - '
320           . $self->CreatedAsString() . "]:\n\n" . $content . "\n\n";
321         $content =~ s/^/> /gm;
322
323     }
324
325     return ($content);
326 }
327
328 # }}}
329
330 # {{{ ContentObj
331
332 =head2 ContentObj 
333
334 Returns the RT::Attachment object which contains the content for this Transaction
335
336 =cut
337
338
339
340 sub ContentObj {
341
342     my $self = shift;
343
344     # If we don\'t have any content, return undef now.
345     unless ( $self->Attachments->First ) {
346         return (undef);
347     }
348
349     # Get the set of toplevel attachments to this transaction.
350     my $Attachment = $self->Attachments->First();
351
352     # If it's a message or a plain part, just return the
353     # body.
354     if ( $Attachment->ContentType() =~ '^(text/plain$|message/)' ) {
355         return ($Attachment);
356     }
357
358     # If it's a multipart object, first try returning the first
359     # text/plain part.
360
361     elsif ( $Attachment->ContentType() =~ '^multipart/' ) {
362         my $plain_parts = $Attachment->Children();
363         $plain_parts->ContentType( VALUE => 'text/plain' );
364
365         # If we actully found a part, return its content
366         if ( $plain_parts->First && $plain_parts->First->Content ne '' ) {
367             return ( $plain_parts->First );
368         }
369
370         # If that fails, return the  first text/plain or message/ part
371         # which has some content.
372
373         else {
374             my $all_parts = $self->Attachments;
375             while ( my $part = $all_parts->Next ) {
376                 if (( $part->ContentType() =~ '^(text/plain$|message/)' ) &&  $part->Content()  ) {
377                     return ($part);
378                 }
379             }
380         }
381
382     }
383
384     # We found no content. suck
385     return (undef);
386 }
387
388 # }}}
389
390 # {{{ sub Subject
391
392 =head2 Subject
393
394 If this transaction has attached mime objects, returns the first one's subject
395 Otherwise, returns null
396   
397 =cut
398
399 sub Subject {
400     my $self = shift;
401     if ( $self->Attachments->First ) {
402         return ( $self->Attachments->First->Subject );
403     }
404     else {
405         return (undef);
406     }
407 }
408
409 # }}}
410
411 # {{{ sub Attachments 
412
413 =head2 Attachments
414
415   Returns all the RT::Attachment objects which are attached
416 to this transaction. Takes an optional parameter, which is
417 a ContentType that Attachments should be restricted to.
418
419 =cut
420
421 sub Attachments {
422     my $self = shift;
423
424     unless ( $self->{'attachments'} ) {
425         $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
426
427         #If it's a comment, return an empty object if they don't have the right to see it
428         if ( $self->Type eq 'Comment' ) {
429             unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
430                 return ( $self->{'attachments'} );
431             }
432         }
433
434         #if they ain't got rights to see, return an empty object
435         elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
436             unless ( $self->CurrentUserHasRight('ShowTicket') ) {
437                 return ( $self->{'attachments'} );
438             }
439         }
440
441         $self->{'attachments'}->Limit( FIELD => 'TransactionId',
442                                        VALUE => $self->Id );
443
444         # Get the self->{'attachments'} in the order they're put into
445         # the database.  Arguably, we should be returning a tree
446         # of self->{'attachments'}, not a set...but no current app seems to need
447         # it.
448
449         $self->{'attachments'}->OrderBy( ALIAS => 'main',
450                                          FIELD => 'id',
451                                          ORDER => 'asc' );
452
453     }
454     return ( $self->{'attachments'} );
455
456 }
457
458 # }}}
459
460 # {{{ sub _Attach 
461
462 =head2 _Attach
463
464 A private method used to attach a mime object to this transaction.
465
466 =cut
467
468 sub _Attach {
469     my $self       = shift;
470     my $MIMEObject = shift;
471
472     if ( !defined($MIMEObject) ) {
473         $RT::Logger->error(
474 "$self _Attach: We can't attach a mime object if you don't give us one.\n"
475         );
476         return ( 0, $self->loc("[_1]: no attachment specified", $self) );
477     }
478
479     my $Attachment = new RT::Attachment( $self->CurrentUser );
480     my ($id, $msg) = $Attachment->Create(
481         TransactionId => $self->Id,
482         Attachment    => $MIMEObject
483     );
484     return ( $Attachment, $msg || $self->loc("Attachment created") );
485
486 }
487
488 # }}}
489
490 # }}}
491
492 # {{{ Routines dealing with Transaction Attributes
493
494 # {{{ sub Description 
495
496 =head2 Description
497
498 Returns a text string which describes this transaction
499
500 =cut
501
502 sub Description {
503     my $self = shift;
504
505     #Check those ACLs
506     #If it's a comment or a comment email record,
507     #  we need to be extra special careful
508
509     if ( $self->__Value('Type') =~ /^Comment/ ) {
510         unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
511             return ( $self->loc("Permission Denied") );
512         }
513     }
514
515     #if they ain't got rights to see, don't let em
516     elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
517         unless ( $self->CurrentUserHasRight('ShowTicket') ) {
518             return ($self->loc("Permission Denied") );
519         }
520     }
521
522     if ( !defined( $self->Type ) ) {
523         return ( $self->loc("No transaction type specified"));
524     }
525
526     return ( $self->loc("[_1] by [_2]",$self->BriefDescription , $self->CreatorObj->Name ));
527 }
528
529 # }}}
530
531 # {{{ sub BriefDescription 
532
533 =head2 BriefDescription
534
535 Returns a text string which briefly describes this transaction
536
537 =cut
538
539 sub BriefDescription {
540     my $self = shift;
541
542     #If it's a comment or a comment email record,
543     #  we need to be extra special careful
544     if ( $self->__Value('Type') =~ /^Comment/ ) {
545         unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
546             return ( $self->loc("Permission Denied") );
547         }
548     }
549
550     #if they ain't got rights to see, don't let em
551     elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) {
552         unless ( $self->CurrentUserHasRight('ShowTicket') ) {
553             return ( $self->loc("Permission Denied") );
554         }
555     }
556
557     my $type = $self->Type;    #cache this, rather than calling it 30 times
558
559     if ( !defined($type) ) {
560         return $self->loc("No transaction type specified");
561     }
562
563     my $obj_type = $self->FriendlyObjectType;
564
565     if ( $type eq 'Create' ) {
566         return ( $self->loc( "[_1] created", $obj_type ) );
567     }
568     elsif ( $type =~ /Status/ ) {
569         if ( $self->Field eq 'Status' ) {
570             if ( $self->NewValue eq 'deleted' ) {
571                 return ( $self->loc( "[_1] deleted", $obj_type ) );
572             }
573             else {
574                 return (
575                     $self->loc(
576                         "Status changed from [_1] to [_2]",
577                         "'" . $self->loc( $self->OldValue ) . "'",
578                         "'" . $self->loc( $self->NewValue ) . "'"
579                     )
580                 );
581
582             }
583         }
584
585         # Generic:
586         my $no_value = $self->loc("(no value)");
587         return (
588             $self->loc(
589                 "[_1] changed from [_2] to [_3]",
590                 $self->Field,
591                 ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ),
592                 "'" . $self->NewValue . "'"
593             )
594         );
595     }
596
597     if ( my $code = $_BriefDescriptions{$type} ) {
598         return $code->($self);
599     }
600
601     return $self->loc(
602         "Default: [_1]/[_2] changed from [_3] to [_4]",
603         $type,
604         $self->Field,
605         (
606             $self->OldValue
607             ? "'" . $self->OldValue . "'"
608             : $self->loc("(no value)")
609         ),
610         "'" . $self->NewValue . "'"
611     );
612 }
613
614 %_BriefDescriptions = (
615     CommentEmailRecord => sub {
616         my $self = shift;
617         return $self->loc("Outgoing email about a comment recorded");
618     },
619     EmailRecord => sub {
620         my $self = shift;
621         return $self->loc("Outgoing email recorded");
622     },
623     Correspond => sub {
624         my $self = shift;
625         return $self->loc("Correspondence added");
626     },
627     Comment => sub {
628         my $self = shift;
629         return $self->loc("Comments added");
630     },
631     CustomField => sub {
632         my $self = shift;
633         my $field = $self->loc('CustomField');
634
635         if ( $self->Field ) {
636             my $cf = RT::CustomField->new( $self->CurrentUser );
637             $cf->Load( $self->Field );
638             $field = $cf->Name();
639         }
640
641         if ( $self->OldValue eq '' ) {
642             return ( $self->loc("[_1] [_2] added", $field, $self->NewValue) );
643         }
644         elsif ( $self->NewValue eq '' ) {
645             return ( $self->loc("[_1] [_2] deleted", $field, $self->OldValue) );
646
647         }
648         else {
649             return $self->loc("[_1] [_2] changed to [_3]", $field, $self->OldValue, $self->NewValue );
650         }
651     },
652     Untake => sub {
653         my $self = shift;
654         return $self->loc("Untaken");
655     },
656     Take => sub {
657         my $self = shift;
658         return $self->loc("Taken");
659     },
660     Force => sub {
661         my $self = shift;
662         my $Old = RT::User->new( $self->CurrentUser );
663         $Old->Load( $self->OldValue );
664         my $New = RT::User->new( $self->CurrentUser );
665         $New->Load( $self->NewValue );
666
667         return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
668     },
669     Steal => sub {
670         my $self = shift;
671         my $Old = RT::User->new( $self->CurrentUser );
672         $Old->Load( $self->OldValue );
673         return $self->loc("Stolen from [_1]",  $Old->Name);
674     },
675     Give => sub {
676         my $self = shift;
677         my $New = RT::User->new( $self->CurrentUser );
678         $New->Load( $self->NewValue );
679         return $self->loc( "Given to [_1]",  $New->Name );
680     },
681     AddWatcher => sub {
682         my $self = shift;
683         my $principal = RT::Principal->new($self->CurrentUser);
684         $principal->Load($self->NewValue);
685         return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
686     },
687     DelWatcher => sub {
688         my $self = shift;
689         my $principal = RT::Principal->new($self->CurrentUser);
690         $principal->Load($self->OldValue);
691         return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
692     },
693     Subject => sub {
694         my $self = shift;
695         return $self->loc( "Subject changed to [_1]", $self->Data );
696     },
697     AddLink => sub {
698         my $self = shift;
699         my $value;
700         if ( $self->NewValue ) {
701             my $URI = RT::URI->new( $self->CurrentUser );
702             $URI->FromURI( $self->NewValue );
703             if ( $URI->Resolver ) {
704                 $value = $URI->Resolver->AsString;
705             }
706             else {
707                 $value = $self->NewValue;
708             }
709             if ( $self->Field eq 'DependsOn' ) {
710                 return $self->loc( "Dependency on [_1] added", $value );
711             }
712             elsif ( $self->Field eq 'DependedOnBy' ) {
713                 return $self->loc( "Dependency by [_1] added", $value );
714
715             }
716             elsif ( $self->Field eq 'RefersTo' ) {
717                 return $self->loc( "Reference to [_1] added", $value );
718             }
719             elsif ( $self->Field eq 'ReferredToBy' ) {
720                 return $self->loc( "Reference by [_1] added", $value );
721             }
722             elsif ( $self->Field eq 'MemberOf' ) {
723                 return $self->loc( "Membership in [_1] added", $value );
724             }
725             elsif ( $self->Field eq 'HasMember' ) {
726                 return $self->loc( "Member [_1] added", $value );
727             }
728             elsif ( $self->Field eq 'MergedInto' ) {
729                 return $self->loc( "Merged into [_1]", $value );
730             }
731         }
732         else {
733             return ( $self->Data );
734         }
735     },
736     DeleteLink => sub {
737         my $self = shift;
738         my $value;
739         if ( $self->OldValue ) {
740             my $URI = RT::URI->new( $self->CurrentUser );
741             $URI->FromURI( $self->OldValue );
742             if ( $URI->Resolver ) {
743                 $value = $URI->Resolver->AsString;
744             }
745             else {
746                 $value = $self->OldValue;
747             }
748
749             if ( $self->Field eq 'DependsOn' ) {
750                 return $self->loc( "Dependency on [_1] deleted", $value );
751             }
752             elsif ( $self->Field eq 'DependedOnBy' ) {
753                 return $self->loc( "Dependency by [_1] deleted", $value );
754
755             }
756             elsif ( $self->Field eq 'RefersTo' ) {
757                 return $self->loc( "Reference to [_1] deleted", $value );
758             }
759             elsif ( $self->Field eq 'ReferredToBy' ) {
760                 return $self->loc( "Reference by [_1] deleted", $value );
761             }
762             elsif ( $self->Field eq 'MemberOf' ) {
763                 return $self->loc( "Membership in [_1] deleted", $value );
764             }
765             elsif ( $self->Field eq 'HasMember' ) {
766                 return $self->loc( "Member [_1] deleted", $value );
767             }
768         }
769         else {
770             return ( $self->Data );
771         }
772     },
773     Set => sub {
774         my $self = shift;
775         if ( $self->Field eq 'Password' ) {
776             return $self->loc('Password changed');
777         }
778         elsif ( $self->Field eq 'Queue' ) {
779             my $q1 = new RT::Queue( $self->CurrentUser );
780             $q1->Load( $self->OldValue );
781             my $q2 = new RT::Queue( $self->CurrentUser );
782             $q2->Load( $self->NewValue );
783             return $self->loc("[_1] changed from [_2] to [_3]", $self->Field , $q1->Name , $q2->Name);
784         }
785
786         # Write the date/time change at local time:
787         elsif ($self->Field =~  /Due|Starts|Started|Told/) {
788             my $t1 = new RT::Date($self->CurrentUser);
789             $t1->Set(Format => 'ISO', Value => $self->NewValue);
790             my $t2 = new RT::Date($self->CurrentUser);
791             $t2->Set(Format => 'ISO', Value => $self->OldValue);
792             return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, $t2->AsString, $t1->AsString );
793         }
794         else {
795             return $self->loc( "[_1] changed from [_2] to [_3]", $self->Field, ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
796         }
797     },
798     PurgeTransaction => sub {
799         my $self = shift;
800         return $self->loc("Transaction [_1] purged", $self->Data);
801     },
802 );
803
804 # }}}
805
806 # {{{ Utility methods
807
808 # {{{ sub IsInbound
809
810 =head2 IsInbound
811
812 Returns true if the creator of the transaction is a requestor of the ticket.
813 Returns false otherwise
814
815 =cut
816
817 sub IsInbound {
818     my $self = shift;
819     $self->ObjectType eq 'RT::Ticket' or return undef;
820     return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
821 }
822
823 # }}}
824
825 # }}}
826
827 sub _OverlayAccessible {
828     {
829
830           ObjectType => { public => 1},
831           ObjectId => { public => 1},
832
833     }
834 };
835
836 # }}}
837
838 # }}}
839
840 # {{{ sub _Set
841
842 sub _Set {
843     my $self = shift;
844     return ( 0, $self->loc('Transactions are immutable') );
845 }
846
847 # }}}
848
849 # {{{ sub _Value 
850
851 =head2 _Value
852
853 Takes the name of a table column.
854 Returns its value as a string, if the user passes an ACL check
855
856 =cut
857
858 sub _Value {
859
860     my $self  = shift;
861     my $field = shift;
862
863     #if the field is public, return it.
864     if ( $self->_Accessible( $field, 'public' ) ) {
865         return ( $self->__Value($field) );
866
867     }
868
869     #If it's a comment, we need to be extra special careful
870     if ( $self->__Value('Type') eq 'Comment' ) {
871         unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
872             return (undef);
873         }
874     }
875     elsif ( $self->__Value('Type') eq 'CommentEmailRecord' ) {
876         unless ( $self->CurrentUserHasRight('ShowTicketComments')
877             && $self->CurrentUserHasRight('ShowOutgoingEmail') ) {
878             return (undef);
879         }
880
881     }
882     elsif ( $self->__Value('Type') eq 'EmailRecord' ) {
883         unless ( $self->CurrentUserHasRight('ShowOutgoingEmail')) {
884             return (undef);
885         }
886
887     }
888     # Make sure the user can see the custom field before showing that it changed
889     elsif ( ( $self->__Value('Type') eq 'CustomField' ) && $self->__Value('Field') ) {
890         my $cf = RT::CustomField->new( $self->CurrentUser );
891         $cf->Load( $self->__Value('Field') );
892         return (undef) unless ( $cf->CurrentUserHasRight('SeeCustomField') );
893     }
894
895
896     #if they ain't got rights to see, don't let em
897     elsif ($self->__Value('ObjectType') eq "RT::Ticket") {
898         unless ( $self->CurrentUserHasRight('ShowTicket') ) {
899             return (undef);
900         }
901     }
902
903     return ( $self->__Value($field) );
904
905 }
906
907 # }}}
908
909 # {{{ sub CurrentUserHasRight
910
911 =head2 CurrentUserHasRight RIGHT
912
913 Calls $self->CurrentUser->HasQueueRight for the right passed in here.
914 passed in here.
915
916 =cut
917
918 sub CurrentUserHasRight {
919     my $self  = shift;
920     my $right = shift;
921     return (
922         $self->CurrentUser->HasRight(
923             Right     => "$right",
924             Object => $self->TicketObj
925           )
926     );
927 }
928
929 # }}}
930
931 sub Ticket {
932     my $self = shift;
933     return $self->ObjectId;
934 }
935
936 sub TicketObj {
937     my $self = shift;
938     return $self->Object;
939 }
940
941 sub OldValue {
942     my $self = shift;
943     if ( my $type = $self->__Value('ReferenceType')
944          and my $id = $self->__Value('OldReference') )
945     {
946         my $Object = $type->new($self->CurrentUser);
947         $Object->Load( $id );
948         return $Object->Content;
949     }
950     else {
951         return $self->__Value('OldValue');
952     }
953 }
954
955 sub NewValue {
956     my $self = shift;
957     if ( my $type = $self->__Value('ReferenceType')
958          and my $id = $self->__Value('NewReference') )
959     {
960         my $Object = $type->new($self->CurrentUser);
961         $Object->Load( $id );
962         return $Object->Content;
963     }
964     else {
965         return $self->__Value('NewValue');
966     }
967 }
968
969 sub Object {
970     my $self  = shift;
971     my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
972     $Object->Load($self->__Value('ObjectId'));
973     return($Object);
974 }
975
976 sub FriendlyObjectType {
977     my $self = shift;
978     my $type = $self->ObjectType or return undef;
979     $type =~ s/^RT:://;
980     return $self->loc($type);
981 }
982
983 =head2 UpdateCustomFields
984     
985     Takes a hash of 
986
987     CustomField-<<Id>> => Value
988         or 
989
990     Object-RT::Transaction-CustomField-<<Id>> => Value parameters to update
991     this transaction's custom fields
992
993 =cut
994
995 sub UpdateCustomFields {
996     my $self = shift;
997     my %args = (@_);
998
999     # This method used to have an API that took a hash of a single
1000     # value "ARGSRef", which was a reference to a hash of arguments.
1001     # This was insane. The next few lines of code preserve that API
1002     # while giving us something saner.
1003        
1004
1005     # TODO: 3.6: DEPRECATE OLD API
1006
1007     my $args; 
1008
1009     if ($args{'ARGSRef'}) { 
1010         $args = $args{ARGSRef};
1011     } else {
1012         $args = \%args;
1013     }
1014
1015     foreach my $arg ( keys %$args ) {
1016         next
1017           unless ( $arg =~
1018             /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ );
1019         next if $arg =~ /-Magic$/;
1020         my $cfid   = $1;
1021         my $values = $args->{$arg};
1022         foreach
1023           my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values )
1024         {
1025             next unless length($value);
1026             $self->_AddCustomFieldValue(
1027                 Field             => $cfid,
1028                 Value             => $value,
1029                 RecordTransaction => 0,
1030             );
1031         }
1032     }
1033 }
1034
1035
1036
1037 =head2 CustomFieldValues
1038
1039  Do name => id mapping (if needed) before falling back to RT::Record's CustomFieldValues
1040
1041  See L<RT::Record>
1042
1043 =cut
1044
1045 sub CustomFieldValues {
1046     my $self  = shift;
1047     my $field = shift;
1048
1049     if ( UNIVERSAL::can( $self->Object, 'QueueObj' ) ) {
1050
1051         unless ( $field =~ /^\d+$/o ) {
1052             my $CFs = RT::CustomFields->new( $self->CurrentUser );
1053              $CFs->Limit( FIELD => 'Name', VALUE => $field);
1054             $CFs->LimitToLookupType($self->CustomFieldLookupType);
1055             $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
1056             $field = $CFs->First->id if $CFs->First;
1057         }
1058     }
1059     return $self->SUPER::CustomFieldValues($field);
1060 }
1061
1062 # }}}
1063
1064 # {{{ sub CustomFieldLookupType
1065
1066 =head2 CustomFieldLookupType
1067
1068 Returns the RT::Transaction lookup type, which can 
1069 be passed to RT::CustomField->Create() via the 'LookupType' hash key.
1070
1071 =cut
1072
1073 # }}}
1074
1075 sub CustomFieldLookupType {
1076     "RT::Queue-RT::Ticket-RT::Transaction";
1077 }
1078
1079 # Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
1080 sub _CacheConfig {
1081   {
1082      'cache_p'        => 1,
1083      'fast_update_p'  => 1,
1084      'cache_for_sec'  => 6000,
1085   }
1086 }
1087 1;