Upgrade 4.0.17 clean.
[usit-rt.git] / lib / RT / Transaction.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 #                                          <sales@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., 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.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
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.)
37 #
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.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 =head1 NAME
50
51   RT::Transaction - RT's transaction object
52
53 =head1 SYNOPSIS
54
55   use RT::Transaction;
56
57
58 =head1 DESCRIPTION
59
60
61 Each RT::Transaction describes an atomic change to a ticket object 
62 or an update to an RT::Ticket object.
63 It can have arbitrary MIME attachments.
64
65
66 =head1 METHODS
67
68
69 =cut
70
71
72 package RT::Transaction;
73
74 use base 'RT::Record';
75 use strict;
76 use warnings;
77
78
79 use vars qw( %_BriefDescriptions $PreferredContentType );
80
81 use RT::Attachments;
82 use RT::Scrips;
83 use RT::Ruleset;
84
85 use HTML::FormatText;
86 use HTML::TreeBuilder;
87
88
89 sub Table {'Transactions'}
90
91 # {{{ sub Create 
92
93 =head2 Create
94
95 Create a new transaction.
96
97 This routine should _never_ be called by anything other than RT::Ticket. 
98 It should not be called 
99 from client code. Ever. Not ever.  If you do this, we will hunt you down and break your kneecaps.
100 Then the unpleasant stuff will start.
101
102 TODO: Document what gets passed to this
103
104 =cut
105
106 sub Create {
107     my $self = shift;
108     my %args = (
109         id             => undef,
110         TimeTaken      => 0,
111         Type           => 'undefined',
112         Data           => '',
113         Field          => undef,
114         OldValue       => undef,
115         NewValue       => undef,
116         MIMEObj        => undef,
117         ActivateScrips => 1,
118         CommitScrips   => 1,
119         ObjectType     => 'RT::Ticket',
120         ObjectId       => 0,
121         ReferenceType  => undef,
122         OldReference   => undef,
123         NewReference   => undef,
124         SquelchMailTo  => undef,
125         @_
126     );
127
128     $args{ObjectId} ||= $args{Ticket};
129
130     #if we didn't specify a ticket, we need to bail
131     unless ( $args{'ObjectId'} && $args{'ObjectType'}) {
132         return ( 0, $self->loc( "Transaction->Create couldn't, as you didn't specify an object type and id"));
133     }
134
135
136
137     #lets create our transaction
138     my %params = (
139         Type      => $args{'Type'},
140         Data      => $args{'Data'},
141         Field     => $args{'Field'},
142         OldValue  => $args{'OldValue'},
143         NewValue  => $args{'NewValue'},
144         Created   => $args{'Created'},
145         ObjectType => $args{'ObjectType'},
146         ObjectId => $args{'ObjectId'},
147         ReferenceType => $args{'ReferenceType'},
148         OldReference => $args{'OldReference'},
149         NewReference => $args{'NewReference'},
150     );
151
152     # Parameters passed in during an import that we probably don't want to touch, otherwise
153     foreach my $attr (qw(id Creator Created LastUpdated TimeTaken LastUpdatedBy)) {
154         $params{$attr} = $args{$attr} if ($args{$attr});
155     }
156  
157     my $id = $self->SUPER::Create(%params);
158     $self->Load($id);
159     if ( defined $args{'MIMEObj'} ) {
160         my ($id, $msg) = $self->_Attach( $args{'MIMEObj'} );
161         unless ( $id ) {
162             $RT::Logger->error("Couldn't add attachment: $msg");
163             return ( 0, $self->loc("Couldn't add attachment") );
164         }
165     }
166
167     $self->AddAttribute(
168         Name    => 'SquelchMailTo',
169         Content => RT::User->CanonicalizeEmailAddress($_)
170     ) for @{$args{'SquelchMailTo'} || []};
171
172     #Provide a way to turn off scrips if we need to
173         $RT::Logger->debug('About to think about scrips for transaction #' .$self->Id);
174     if ( $args{'ActivateScrips'} and $args{'ObjectType'} eq 'RT::Ticket' ) {
175        $self->{'scrips'} = RT::Scrips->new(RT->SystemUser);
176
177         $RT::Logger->debug('About to prepare scrips for transaction #' .$self->Id); 
178
179         $self->{'scrips'}->Prepare(
180             Stage       => 'TransactionCreate',
181             Type        => $args{'Type'},
182             Ticket      => $args{'ObjectId'},
183             Transaction => $self->id,
184         );
185
186        # Entry point of the rule system
187        my $ticket = RT::Ticket->new(RT->SystemUser);
188        $ticket->Load($args{'ObjectId'});
189        my $txn = RT::Transaction->new($RT::SystemUser);
190        $txn->Load($self->id);
191
192        my $rules = $self->{rules} = RT::Ruleset->FindAllRules(
193             Stage       => 'TransactionCreate',
194             Type        => $args{'Type'},
195             TicketObj   => $ticket,
196             TransactionObj => $txn,
197        );
198
199         if ($args{'CommitScrips'} ) {
200             $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id);
201             $self->{'scrips'}->Commit();
202             RT::Ruleset->CommitRules($rules);
203         }
204     }
205
206     return ( $id, $self->loc("Transaction Created") );
207 }
208
209
210 =head2 Scrips
211
212 Returns the Scrips object for this transaction.
213 This routine is only useful on a freshly created transaction object.
214 Scrips do not get persisted to the database with transactions.
215
216
217 =cut
218
219
220 sub Scrips {
221     my $self = shift;
222     return($self->{'scrips'});
223 }
224
225
226 =head2 Rules
227
228 Returns the array of Rule objects for this transaction.
229 This routine is only useful on a freshly created transaction object.
230 Rules do not get persisted to the database with transactions.
231
232
233 =cut
234
235
236 sub Rules {
237     my $self = shift;
238     return($self->{'rules'});
239 }
240
241
242
243 =head2 Delete
244
245 Delete this transaction. Currently DOES NOT CHECK ACLS
246
247 =cut
248
249 sub Delete {
250     my $self = shift;
251
252
253     $RT::Handle->BeginTransaction();
254
255     my $attachments = $self->Attachments;
256
257     while (my $attachment = $attachments->Next) {
258         my ($id, $msg) = $attachment->Delete();
259         unless ($id) {
260             $RT::Handle->Rollback();
261             return($id, $self->loc("System Error: [_1]", $msg));
262         }
263     }
264     my ($id,$msg) = $self->SUPER::Delete();
265         unless ($id) {
266             $RT::Handle->Rollback();
267             return($id, $self->loc("System Error: [_1]", $msg));
268         }
269     $RT::Handle->Commit();
270     return ($id,$msg);
271 }
272
273
274
275
276 =head2 Message
277
278 Returns the L<RT::Attachments> object which contains the "top-level" object
279 attachment for this transaction.
280
281 =cut
282
283 sub Message {
284     my $self = shift;
285
286     # XXX: Where is ACL check?
287     
288     unless ( defined $self->{'message'} ) {
289
290         $self->{'message'} = RT::Attachments->new( $self->CurrentUser );
291         $self->{'message'}->Limit(
292             FIELD => 'TransactionId',
293             VALUE => $self->Id
294         );
295         $self->{'message'}->ChildrenOf(0);
296     } else {
297         $self->{'message'}->GotoFirstItem;
298     }
299     return $self->{'message'};
300 }
301
302
303
304 =head2 Content PARAMHASH
305
306 If this transaction has attached mime objects, returns the body of the first
307 textual part (as defined in RT::I18N::IsTextualContentType).  Otherwise,
308 returns undef.
309
310 Takes a paramhash.  If the $args{'Quote'} parameter is set, wraps this message 
311 at $args{'Wrap'}.  $args{'Wrap'} defaults to 70.
312
313 If $args{'Type'} is set to C<text/html>, this will return an HTML 
314 part of the message, if available.  Otherwise it looks for a text/plain
315 part. If $args{'Type'} is missing, it defaults to the value of 
316 C<$RT::Transaction::PreferredContentType>, if that's missing too, 
317 defaults to textual.
318
319 =cut
320
321 sub Content {
322     my $self = shift;
323     my %args = (
324         Type => $PreferredContentType || '',
325         Quote => 0,
326         Wrap  => 70,
327         @_
328     );
329
330     my $content;
331     if ( my $content_obj =
332         $self->ContentObj( $args{Type} ? ( Type => $args{Type} ) : () ) )
333     {
334         $content = $content_obj->Content ||'';
335
336         if ( lc $content_obj->ContentType eq 'text/html' ) {
337             $content =~ s/<p>--\s+<br \/>.*?$//s if $args{'Quote'};
338
339             if ($args{Type} ne 'text/html') {
340                 my $tree = HTML::TreeBuilder->new_from_content( $content );
341                 $content = HTML::FormatText->new(
342                     leftmargin  => 0,
343                     rightmargin => 78,
344                 )->format( $tree);
345                 $tree->delete;
346             }
347         }
348         else {
349             $content =~ s/\n-- \n.*?$//s if $args{'Quote'};
350             if ($args{Type} eq 'text/html') {
351                 # Extremely simple text->html converter
352                 $content =~ s/&/&#38;/g;
353                 $content =~ s/</&lt;/g;
354                 $content =~ s/>/&gt;/g;
355                 $content = "<pre>$content</pre>";
356             }
357         }
358     }
359
360     # If all else fails, return a message that we couldn't find any content
361     else {
362         $content = $self->loc('This transaction appears to have no content');
363     }
364
365     if ( $args{'Quote'} ) {
366         $content = $self->ApplyQuoteWrap(content => $content,
367                                          cols    => $args{'Wrap'} );
368
369         $content = $self->QuoteHeader . "\n$content\n\n";
370     }
371
372     return ($content);
373 }
374
375 =head2 QuoteHeader
376
377 Returns text prepended to content when transaction is quoted
378 (see C<Quote> argument in L</Content>). By default returns
379 localized "On <date> <user name> wrote:\n".
380
381 =cut
382
383 sub QuoteHeader {
384     my $self = shift;
385     return $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name);
386 }
387
388 =head2 ApplyQuoteWrap PARAMHASH
389
390 Wrapper to calculate wrap criteria and apply quote wrapping if needed.
391
392 =cut
393
394 sub ApplyQuoteWrap {
395     my $self = shift;
396     my %args = @_;
397     my $content = $args{content};
398
399     # What's the longest line like?
400     my $max = 0;
401     foreach ( split ( /\n/, $args{content} ) ) {
402         $max = length if length > $max;
403     }
404
405     if ( $max > 76 ) {
406         require Text::Quoted;
407         require Text::Wrapper;
408
409         my $structure = Text::Quoted::extract($args{content});
410         $content = $self->QuoteWrap(content_ref => $structure,
411                                     cols        => $args{cols},
412                                     max         => $max );
413     }
414
415     $content =~ s/^/> /gm;  # use regex since string might be multi-line
416     return $content;
417 }
418
419 =head2 QuoteWrap PARAMHASH
420
421 Wrap the contents of transactions based on Wrap settings, maintaining
422 the quote character from the original.
423
424 =cut
425
426 sub QuoteWrap {
427     my $self = shift;
428     my %args = @_;
429     my $ref = $args{content_ref};
430     my $final_string;
431
432     if ( ref $ref eq 'ARRAY' ){
433         foreach my $array (@$ref){
434             $final_string .= $self->QuoteWrap(content_ref => $array,
435                                               cols        => $args{cols},
436                                               max         => $args{max} );
437         }
438     }
439     elsif ( ref $ref eq 'HASH' ){
440         return $ref->{quoter} . "\n" if $ref->{empty}; # Blank line
441
442         my $col = $args{cols} - (length $ref->{quoter});
443         my $wrapper = Text::Wrapper->new( columns => $col );
444
445         # Wrap on individual lines to honor incoming line breaks
446         # Otherwise deliberate separate lines (like a list or a sig)
447         # all get combined incorrectly into single paragraphs.
448
449         my @lines = split /\n/, $ref->{text};
450         my $wrap = join '', map { $wrapper->wrap($_) } @lines;
451         my $quoter = $ref->{quoter};
452
453         # Only add the space if actually quoting
454         $quoter .= ' ' if length $quoter;
455         $wrap =~ s/^/$quoter/mg;  # use regex since string might be multi-line
456
457         return $wrap;
458     }
459     else{
460         $RT::Logger->warning("Can't apply quoting with $ref");
461         return;
462     }
463     return $final_string;
464 }
465
466
467 =head2 Addresses
468
469 Returns a hashref of addresses related to this transaction. See L<RT::Attachment/Addresses> for details.
470
471 =cut
472
473 sub Addresses {
474         my $self = shift;
475
476         if (my $attach = $self->Attachments->First) {   
477                 return $attach->Addresses;
478         }
479         else {
480                 return {};
481         }
482
483 }
484
485
486
487 =head2 ContentObj 
488
489 Returns the RT::Attachment object which contains the content for this Transaction
490
491 =cut
492
493
494 sub ContentObj {
495     my $self = shift;
496     my %args = ( Type => $PreferredContentType, Attachment => undef, @_ );
497
498     # If we don't have any content, return undef now.
499     # Get the set of toplevel attachments to this transaction.
500
501     my $Attachment = $args{'Attachment'};
502
503     $Attachment ||= $self->Attachments->First;
504
505     return undef unless ($Attachment);
506
507     # If it's a textual part, just return the body.
508     if ( RT::I18N::IsTextualContentType($Attachment->ContentType) ) {
509         return ($Attachment);
510     }
511
512     # If it's a multipart object, first try returning the first part with preferred
513     # MIME type ('text/plain' by default).
514
515     elsif ( $Attachment->ContentType =~ m|^multipart/mixed|i ) {
516         my $kids = $Attachment->Children;
517         while (my $child = $kids->Next) {
518             my $ret =  $self->ContentObj(%args, Attachment => $child);
519             return $ret if ($ret);
520         }
521     }
522     elsif ( $Attachment->ContentType =~ m|^multipart/|i ) {
523         if ( $args{Type} ) {
524             my $plain_parts = $Attachment->Children;
525             $plain_parts->ContentType( VALUE => $args{Type} );
526             $plain_parts->LimitNotEmpty;
527
528             # If we actully found a part, return its content
529             if ( my $first = $plain_parts->First ) {
530                 return $first;
531             }
532         }
533
534         # If that fails, return the first textual part which has some content.
535         my $all_parts = $self->Attachments;
536         while ( my $part = $all_parts->Next ) {
537             next unless RT::I18N::IsTextualContentType($part->ContentType)
538                         && $part->Content;
539             return $part;
540         }
541     }
542
543     # We found no content. suck
544     return (undef);
545 }
546
547
548
549 =head2 Subject
550
551 If this transaction has attached mime objects, returns the first one's subject
552 Otherwise, returns null
553   
554 =cut
555
556 sub Subject {
557     my $self = shift;
558     return undef unless my $first = $self->Attachments->First;
559     return $first->Subject;
560 }
561
562
563
564 =head2 Attachments
565
566 Returns all the RT::Attachment objects which are attached
567 to this transaction. Takes an optional parameter, which is
568 a ContentType that Attachments should be restricted to.
569
570 =cut
571
572 sub Attachments {
573     my $self = shift;
574
575     if ( $self->{'attachments'} ) {
576         $self->{'attachments'}->GotoFirstItem;
577         return $self->{'attachments'};
578     }
579
580     $self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
581
582     unless ( $self->CurrentUserCanSee ) {
583         $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl');
584         return $self->{'attachments'};
585     }
586
587     $self->{'attachments'}->Limit( FIELD => 'TransactionId', VALUE => $self->Id );
588
589     # Get the self->{'attachments'} in the order they're put into
590     # the database.  Arguably, we should be returning a tree
591     # of self->{'attachments'}, not a set...but no current app seems to need
592     # it.
593
594     $self->{'attachments'}->OrderBy( FIELD => 'id', ORDER => 'ASC' );
595
596     return $self->{'attachments'};
597 }
598
599
600
601 =head2 _Attach
602
603 A private method used to attach a mime object to this transaction.
604
605 =cut
606
607 sub _Attach {
608     my $self       = shift;
609     my $MIMEObject = shift;
610
611     unless ( defined $MIMEObject ) {
612         $RT::Logger->error("We can't attach a mime object if you don't give us one.");
613         return ( 0, $self->loc("[_1]: no attachment specified", $self) );
614     }
615
616     my $Attachment = RT::Attachment->new( $self->CurrentUser );
617     my ($id, $msg) = $Attachment->Create(
618         TransactionId => $self->Id,
619         Attachment    => $MIMEObject
620     );
621     return ( $Attachment, $msg || $self->loc("Attachment created") );
622 }
623
624
625
626 sub ContentAsMIME {
627     my $self = shift;
628
629     # RT::Attachments doesn't limit ACLs as strictly as RT::Transaction does
630     # since it has less information available without looking to it's parent
631     # transaction.  Check ACLs here before we go any further.
632     return unless $self->CurrentUserCanSee;
633
634     my $attachments = RT::Attachments->new( $self->CurrentUser );
635     $attachments->OrderBy( FIELD => 'id', ORDER => 'ASC' );
636     $attachments->Limit( FIELD => 'TransactionId', VALUE => $self->id );
637     $attachments->Limit( FIELD => 'Parent',        VALUE => 0 );
638     $attachments->RowsPerPage(1);
639
640     my $top = $attachments->First;
641     return unless $top;
642
643     my $entity = MIME::Entity->build(
644         Type        => 'message/rfc822',
645         Description => 'transaction ' . $self->id,
646         Data        => $top->ContentAsMIME(Children => 1)->as_string,
647     );
648
649     return $entity;
650 }
651
652
653
654 =head2 Description
655
656 Returns a text string which describes this transaction
657
658 =cut
659
660 sub Description {
661     my $self = shift;
662
663     unless ( $self->CurrentUserCanSee ) {
664         return ( $self->loc("Permission Denied") );
665     }
666
667     unless ( defined $self->Type ) {
668         return ( $self->loc("No transaction type specified"));
669     }
670
671     return $self->loc("[_1] by [_2]", $self->BriefDescription , $self->CreatorObj->Name );
672 }
673
674
675
676 =head2 BriefDescription
677
678 Returns a text string which briefly describes this transaction
679
680 =cut
681
682 sub BriefDescription {
683     my $self = shift;
684
685     unless ( $self->CurrentUserCanSee ) {
686         return ( $self->loc("Permission Denied") );
687     }
688
689     my $type = $self->Type;    #cache this, rather than calling it 30 times
690
691     unless ( defined $type ) {
692         return $self->loc("No transaction type specified");
693     }
694
695     my $obj_type = $self->FriendlyObjectType;
696
697     if ( $type eq 'Create' ) {
698         return ( $self->loc( "[_1] created", $obj_type ) );
699     }
700     elsif ( $type eq 'Enabled' ) {
701         return ( $self->loc( "[_1] enabled", $obj_type ) );
702     }
703     elsif ( $type eq 'Disabled' ) {
704         return ( $self->loc( "[_1] disabled", $obj_type ) );
705     }
706     elsif ( $type =~ /Status/ ) {
707         if ( $self->Field eq 'Status' ) {
708             if ( $self->NewValue eq 'deleted' ) {
709                 return ( $self->loc( "[_1] deleted", $obj_type ) );
710             }
711             else {
712                 my $canon = $self->Object->can("QueueObj")
713                     ? sub { $self->Object->QueueObj->Lifecycle->CanonicalCase(@_) }
714                     : sub { return $_[0] };
715                 return (
716                     $self->loc(
717                         "Status changed from [_1] to [_2]",
718                         "'" . $self->loc( $canon->($self->OldValue) ) . "'",
719                         "'" . $self->loc( $canon->($self->NewValue) ) . "'"
720                     )
721                 );
722
723             }
724         }
725
726         # Generic:
727         my $no_value = $self->loc("(no value)");
728         return (
729             $self->loc(
730                 "[_1] changed from [_2] to [_3]",
731                 $self->Field,
732                 ( $self->OldValue ? "'" . $self->OldValue . "'" : $no_value ),
733                 "'" . $self->NewValue . "'"
734             )
735         );
736     }
737     elsif ( $type =~ /SystemError/ ) {
738         return $self->loc("System error");
739     }
740     elsif ( $type =~ /Forward Transaction/ ) {
741         return $self->loc( "Forwarded Transaction #[_1] to [_2]",
742             $self->Field, $self->Data );
743     }
744     elsif ( $type =~ /Forward Ticket/ ) {
745         return $self->loc( "Forwarded Ticket to [_1]", $self->Data );
746     }
747
748     if ( my $code = $_BriefDescriptions{$type} ) {
749         return $code->($self);
750     }
751
752     return $self->loc(
753         "Default: [_1]/[_2] changed from [_3] to [_4]",
754         $type,
755         $self->Field,
756         (
757             $self->OldValue
758             ? "'" . $self->OldValue . "'"
759             : $self->loc("(no value)")
760         ),
761         "'" . $self->NewValue . "'"
762     );
763 }
764
765 %_BriefDescriptions = (
766     CommentEmailRecord => sub {
767         my $self = shift;
768         return $self->loc("Outgoing email about a comment recorded");
769     },
770     EmailRecord => sub {
771         my $self = shift;
772         return $self->loc("Outgoing email recorded");
773     },
774     Correspond => sub {
775         my $self = shift;
776         return $self->loc("Correspondence added");
777     },
778     Comment => sub {
779         my $self = shift;
780         return $self->loc("Comments added");
781     },
782     CustomField => sub {
783         my $self = shift;
784         my $field = $self->loc('CustomField');
785
786         if ( $self->Field ) {
787             my $cf = RT::CustomField->new( $self->CurrentUser );
788             $cf->SetContextObject( $self->Object );
789             $cf->Load( $self->Field );
790             $field = $cf->Name();
791             $field = $self->loc('a custom field') if !defined($field);
792         }
793
794         my $new = $self->NewValue;
795         my $old = $self->OldValue;
796
797         if ( !defined($old) || $old eq '' ) {
798             return $self->loc("[_1] [_2] added", $field, $new);
799         }
800         elsif ( !defined($new) || $new eq '' ) {
801             return $self->loc("[_1] [_2] deleted", $field, $old);
802         }
803         else {
804             return $self->loc("[_1] [_2] changed to [_3]", $field, $old, $new);
805         }
806     },
807     Untake => sub {
808         my $self = shift;
809         return $self->loc("Untaken");
810     },
811     Take => sub {
812         my $self = shift;
813         return $self->loc("Taken");
814     },
815     Force => sub {
816         my $self = shift;
817         my $Old = RT::User->new( $self->CurrentUser );
818         $Old->Load( $self->OldValue );
819         my $New = RT::User->new( $self->CurrentUser );
820         $New->Load( $self->NewValue );
821
822         return $self->loc("Owner forcibly changed from [_1] to [_2]" , $Old->Name , $New->Name);
823     },
824     Steal => sub {
825         my $self = shift;
826         my $Old = RT::User->new( $self->CurrentUser );
827         $Old->Load( $self->OldValue );
828         return $self->loc("Stolen from [_1]",  $Old->Name);
829     },
830     Give => sub {
831         my $self = shift;
832         my $New = RT::User->new( $self->CurrentUser );
833         $New->Load( $self->NewValue );
834         return $self->loc( "Given to [_1]",  $New->Name );
835     },
836     AddWatcher => sub {
837         my $self = shift;
838         my $principal = RT::Principal->new($self->CurrentUser);
839         $principal->Load($self->NewValue);
840         return $self->loc( "[_1] [_2] added", $self->Field, $principal->Object->Name);
841     },
842     DelWatcher => sub {
843         my $self = shift;
844         my $principal = RT::Principal->new($self->CurrentUser);
845         $principal->Load($self->OldValue);
846         return $self->loc( "[_1] [_2] deleted", $self->Field, $principal->Object->Name);
847     },
848     Subject => sub {
849         my $self = shift;
850         return $self->loc( "Subject changed to [_1]", $self->Data );
851     },
852     AddLink => sub {
853         my $self = shift;
854         my $value;
855         if ( $self->NewValue ) {
856             my $URI = RT::URI->new( $self->CurrentUser );
857             if ( $URI->FromURI( $self->NewValue ) ) {
858                 $value = $URI->Resolver->AsString;
859             }
860             else {
861                 $value = $self->NewValue;
862             }
863             if ( $self->Field eq 'DependsOn' ) {
864                 return $self->loc( "Dependency on [_1] added", $value );
865             }
866             elsif ( $self->Field eq 'DependedOnBy' ) {
867                 return $self->loc( "Dependency by [_1] added", $value );
868
869             }
870             elsif ( $self->Field eq 'RefersTo' ) {
871                 return $self->loc( "Reference to [_1] added", $value );
872             }
873             elsif ( $self->Field eq 'ReferredToBy' ) {
874                 return $self->loc( "Reference by [_1] added", $value );
875             }
876             elsif ( $self->Field eq 'MemberOf' ) {
877                 return $self->loc( "Membership in [_1] added", $value );
878             }
879             elsif ( $self->Field eq 'HasMember' ) {
880                 return $self->loc( "Member [_1] added", $value );
881             }
882             elsif ( $self->Field eq 'MergedInto' ) {
883                 return $self->loc( "Merged into [_1]", $value );
884             }
885         }
886         else {
887             return ( $self->Data );
888         }
889     },
890     DeleteLink => sub {
891         my $self = shift;
892         my $value;
893         if ( $self->OldValue ) {
894             my $URI = RT::URI->new( $self->CurrentUser );
895             if ( $URI->FromURI( $self->OldValue ) ){
896                 $value = $URI->Resolver->AsString;
897             }
898             else {
899                 $value = $self->OldValue;
900             }
901
902             if ( $self->Field eq 'DependsOn' ) {
903                 return $self->loc( "Dependency on [_1] deleted", $value );
904             }
905             elsif ( $self->Field eq 'DependedOnBy' ) {
906                 return $self->loc( "Dependency by [_1] deleted", $value );
907
908             }
909             elsif ( $self->Field eq 'RefersTo' ) {
910                 return $self->loc( "Reference to [_1] deleted", $value );
911             }
912             elsif ( $self->Field eq 'ReferredToBy' ) {
913                 return $self->loc( "Reference by [_1] deleted", $value );
914             }
915             elsif ( $self->Field eq 'MemberOf' ) {
916                 return $self->loc( "Membership in [_1] deleted", $value );
917             }
918             elsif ( $self->Field eq 'HasMember' ) {
919                 return $self->loc( "Member [_1] deleted", $value );
920             }
921         }
922         else {
923             return ( $self->Data );
924         }
925     },
926     Told => sub {
927         my $self = shift;
928         if ( $self->Field eq 'Told' ) {
929             my $t1 = RT::Date->new($self->CurrentUser);
930             $t1->Set(Format => 'ISO', Value => $self->NewValue);
931             my $t2 = RT::Date->new($self->CurrentUser);
932             $t2->Set(Format => 'ISO', Value => $self->OldValue);
933             return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString );
934         }
935         else {
936             return $self->loc( "[_1] changed from [_2] to [_3]",
937                                $self->loc($self->Field),
938                                ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
939         }
940     },
941     Set => sub {
942         my $self = shift;
943         if ( $self->Field eq 'Password' ) {
944             return $self->loc('Password changed');
945         }
946         elsif ( $self->Field eq 'Queue' ) {
947             my $q1 = RT::Queue->new( $self->CurrentUser );
948             $q1->Load( $self->OldValue );
949             my $q2 = RT::Queue->new( $self->CurrentUser );
950             $q2->Load( $self->NewValue );
951             return $self->loc("[_1] changed from [_2] to [_3]",
952                               $self->loc($self->Field) , $q1->Name , $q2->Name);
953         }
954
955         # Write the date/time change at local time:
956         elsif ($self->Field =~  /Due|Starts|Started|Told/) {
957             my $t1 = RT::Date->new($self->CurrentUser);
958             $t1->Set(Format => 'ISO', Value => $self->NewValue);
959             my $t2 = RT::Date->new($self->CurrentUser);
960             $t2->Set(Format => 'ISO', Value => $self->OldValue);
961             return $self->loc( "[_1] changed from [_2] to [_3]", $self->loc($self->Field), $t2->AsString, $t1->AsString );
962         }
963         elsif ( $self->Field eq 'Owner' ) {
964             my $Old = RT::User->new( $self->CurrentUser );
965             $Old->Load( $self->OldValue );
966             my $New = RT::User->new( $self->CurrentUser );
967             $New->Load( $self->NewValue );
968
969             if ( $Old->id == RT->Nobody->id ) {
970                 if ( $New->id == $self->Creator ) {
971                     return $self->loc("Taken");
972                 }
973                 else {
974                     return $self->loc( "Given to [_1]",  $New->Name );
975                 }
976             }
977             else {
978                 if ( $New->id == $self->Creator ) {
979                     return $self->loc("Stolen from [_1]",  $Old->Name);
980                 }
981                 elsif ( $Old->id == $self->Creator ) {
982                     if ( $New->id == RT->Nobody->id ) {
983                         return $self->loc("Untaken");
984                     }
985                     else {
986                         return $self->loc( "Given to [_1]", $New->Name );
987                     }
988                 }
989                 else {
990                     return $self->loc(
991                         "Owner forcibly changed from [_1] to [_2]",
992                         $Old->Name, $New->Name );
993                 }
994             }
995         }
996         else {
997             return $self->loc( "[_1] changed from [_2] to [_3]",
998                                $self->loc($self->Field),
999                                ($self->OldValue? "'".$self->OldValue ."'" : $self->loc("(no value)")) , "'". $self->NewValue."'" );
1000         }
1001     },
1002     PurgeTransaction => sub {
1003         my $self = shift;
1004         return $self->loc("Transaction [_1] purged", $self->Data);
1005     },
1006     AddReminder => sub {
1007         my $self = shift;
1008         my $ticket = RT::Ticket->new($self->CurrentUser);
1009         $ticket->Load($self->NewValue);
1010         return $self->loc("Reminder '[_1]' added", $ticket->Subject);
1011     },
1012     OpenReminder => sub {
1013         my $self = shift;
1014         my $ticket = RT::Ticket->new($self->CurrentUser);
1015         $ticket->Load($self->NewValue);
1016         return $self->loc("Reminder '[_1]' reopened", $ticket->Subject);
1017     
1018     },
1019     ResolveReminder => sub {
1020         my $self = shift;
1021         my $ticket = RT::Ticket->new($self->CurrentUser);
1022         $ticket->Load($self->NewValue);
1023         return $self->loc("Reminder '[_1]' completed", $ticket->Subject);
1024     
1025     
1026     }
1027 );
1028
1029
1030
1031
1032 =head2 IsInbound
1033
1034 Returns true if the creator of the transaction is a requestor of the ticket.
1035 Returns false otherwise
1036
1037 =cut
1038
1039 sub IsInbound {
1040     my $self = shift;
1041     $self->ObjectType eq 'RT::Ticket' or return undef;
1042     return ( $self->TicketObj->IsRequestor( $self->CreatorObj->PrincipalId ) );
1043 }
1044
1045
1046
1047 sub _OverlayAccessible {
1048     {
1049
1050           ObjectType => { public => 1},
1051           ObjectId => { public => 1},
1052
1053     }
1054 };
1055
1056
1057
1058
1059 sub _Set {
1060     my $self = shift;
1061     return ( 0, $self->loc('Transactions are immutable') );
1062 }
1063
1064
1065
1066 =head2 _Value
1067
1068 Takes the name of a table column.
1069 Returns its value as a string, if the user passes an ACL check
1070
1071 =cut
1072
1073 sub _Value {
1074     my $self  = shift;
1075     my $field = shift;
1076
1077     #if the field is public, return it.
1078     if ( $self->_Accessible( $field, 'public' ) ) {
1079         return $self->SUPER::_Value( $field );
1080     }
1081
1082     unless ( $self->CurrentUserCanSee ) {
1083         return undef;
1084     }
1085
1086     return $self->SUPER::_Value( $field );
1087 }
1088
1089
1090
1091 =head2 CurrentUserHasRight RIGHT
1092
1093 Calls $self->CurrentUser->HasQueueRight for the right passed in here.
1094 passed in here.
1095
1096 =cut
1097
1098 sub CurrentUserHasRight {
1099     my $self  = shift;
1100     my $right = shift;
1101     return $self->CurrentUser->HasRight(
1102         Right  => $right,
1103         Object => $self->Object
1104     );
1105 }
1106
1107 =head2 CurrentUserCanSee
1108
1109 Returns true if current user has rights to see this particular transaction.
1110
1111 This fact depends on type of the transaction, type of an object the transaction
1112 is attached to and may be other conditions, so this method is prefered over
1113 custom implementations.
1114
1115 =cut
1116
1117 sub CurrentUserCanSee {
1118     my $self = shift;
1119
1120     # If it's a comment, we need to be extra special careful
1121     my $type = $self->__Value('Type');
1122     if ( $type eq 'Comment' ) {
1123         unless ( $self->CurrentUserHasRight('ShowTicketComments') ) {
1124             return 0;
1125         }
1126     }
1127     elsif ( $type eq 'CommentEmailRecord' ) {
1128         unless ( $self->CurrentUserHasRight('ShowTicketComments')
1129             && $self->CurrentUserHasRight('ShowOutgoingEmail') ) {
1130             return 0;
1131         }
1132     }
1133     elsif ( $type eq 'EmailRecord' ) {
1134         unless ( $self->CurrentUserHasRight('ShowOutgoingEmail') ) {
1135             return 0;
1136         }
1137     }
1138     # Make sure the user can see the custom field before showing that it changed
1139     elsif ( $type eq 'CustomField' and my $cf_id = $self->__Value('Field') ) {
1140         my $cf = RT::CustomField->new( $self->CurrentUser );
1141         $cf->SetContextObject( $self->Object );
1142         $cf->Load( $cf_id );
1143         return 0 unless $cf->CurrentUserHasRight('SeeCustomField');
1144     }
1145
1146     # Transactions that might have changed the ->Object's visibility to
1147     # the current user are marked readable
1148     return 1 if $self->{ _object_is_readable };
1149
1150     # Defer to the object in question
1151     return $self->Object->CurrentUserCanSee("Transaction");
1152 }
1153
1154
1155 sub Ticket {
1156     my $self = shift;
1157     return $self->ObjectId;
1158 }
1159
1160 sub TicketObj {
1161     my $self = shift;
1162     return $self->Object;
1163 }
1164
1165 sub OldValue {
1166     my $self = shift;
1167     if ( my $type = $self->__Value('ReferenceType')
1168          and my $id = $self->__Value('OldReference') )
1169     {
1170         my $Object = $type->new($self->CurrentUser);
1171         $Object->Load( $id );
1172         return $Object->Content;
1173     }
1174     else {
1175         return $self->_Value('OldValue');
1176     }
1177 }
1178
1179 sub NewValue {
1180     my $self = shift;
1181     if ( my $type = $self->__Value('ReferenceType')
1182          and my $id = $self->__Value('NewReference') )
1183     {
1184         my $Object = $type->new($self->CurrentUser);
1185         $Object->Load( $id );
1186         return $Object->Content;
1187     }
1188     else {
1189         return $self->_Value('NewValue');
1190     }
1191 }
1192
1193 sub Object {
1194     my $self  = shift;
1195     my $Object = $self->__Value('ObjectType')->new($self->CurrentUser);
1196     $Object->Load($self->__Value('ObjectId'));
1197     return $Object;
1198 }
1199
1200 sub FriendlyObjectType {
1201     my $self = shift;
1202     my $type = $self->ObjectType or return undef;
1203     $type =~ s/^RT:://;
1204     return $self->loc($type);
1205 }
1206
1207 =head2 UpdateCustomFields
1208     
1209     Takes a hash of 
1210
1211     CustomField-<<Id>> => Value
1212         or 
1213
1214     Object-RT::Transaction-CustomField-<<Id>> => Value parameters to update
1215     this transaction's custom fields
1216
1217 =cut
1218
1219 sub UpdateCustomFields {
1220     my $self = shift;
1221     my %args = (@_);
1222
1223     # This method used to have an API that took a hash of a single
1224     # value "ARGSRef", which was a reference to a hash of arguments.
1225     # This was insane. The next few lines of code preserve that API
1226     # while giving us something saner.
1227
1228     # TODO: 3.6: DEPRECATE OLD API
1229
1230     my $args; 
1231
1232     if ($args{'ARGSRef'}) { 
1233         $args = $args{ARGSRef};
1234     } else {
1235         $args = \%args;
1236     }
1237
1238     foreach my $arg ( keys %$args ) {
1239         next
1240           unless ( $arg =~
1241             /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ );
1242         next if $arg =~ /-Magic$/;
1243         my $cfid   = $1;
1244         my $values = $args->{$arg};
1245         foreach
1246           my $value ( UNIVERSAL::isa( $values, 'ARRAY' ) ? @$values : $values )
1247         {
1248             next unless (defined($value) && length($value));
1249             $self->_AddCustomFieldValue(
1250                 Field             => $cfid,
1251                 Value             => $value,
1252                 RecordTransaction => 0,
1253             );
1254         }
1255     }
1256 }
1257
1258 =head2 LoadCustomFieldByIdentifier
1259
1260 Finds and returns the custom field of the given name for the
1261 transaction, overriding L<RT::Record/LoadCustomFieldByIdentifier> to
1262 look for queue-specific CFs before global ones.
1263
1264 =cut
1265
1266 sub LoadCustomFieldByIdentifier {
1267     my $self  = shift;
1268     my $field = shift;
1269
1270     return $self->SUPER::LoadCustomFieldByIdentifier($field)
1271         if ref $field or $field =~ /^\d+$/;
1272
1273     return $self->SUPER::LoadCustomFieldByIdentifier($field)
1274         unless UNIVERSAL::can( $self->Object, 'QueueObj' );
1275
1276     my $CFs = RT::CustomFields->new( $self->CurrentUser );
1277     $CFs->SetContextObject( $self->Object );
1278     $CFs->Limit( FIELD => 'Name', VALUE => $field );
1279     $CFs->LimitToLookupType($self->CustomFieldLookupType);
1280     $CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
1281     return $CFs->First || RT::CustomField->new( $self->CurrentUser );
1282 }
1283
1284 =head2 CustomFieldLookupType
1285
1286 Returns the RT::Transaction lookup type, which can 
1287 be passed to RT::CustomField->Create() via the 'LookupType' hash key.
1288
1289 =cut
1290
1291
1292 sub CustomFieldLookupType {
1293     "RT::Queue-RT::Ticket-RT::Transaction";
1294 }
1295
1296
1297 =head2 SquelchMailTo
1298
1299 Similar to Ticket class SquelchMailTo method - returns a list of
1300 transaction's squelched addresses.  As transactions are immutable, the
1301 list of squelched recipients cannot be modified after creation.
1302
1303 =cut
1304
1305 sub SquelchMailTo {
1306     my $self = shift;
1307     return () unless $self->CurrentUserCanSee;
1308     return $self->Attributes->Named('SquelchMailTo');
1309 }
1310
1311 =head2 Recipients
1312
1313 Returns the list of email addresses (as L<Email::Address> objects)
1314 that this transaction would send mail to.  There may be duplicates.
1315
1316 =cut
1317
1318 sub Recipients {
1319     my $self = shift;
1320     my @recipients;
1321     foreach my $scrip ( @{ $self->Scrips->Prepared } ) {
1322         my $action = $scrip->ActionObj->Action;
1323         next unless $action->isa('RT::Action::SendEmail');
1324
1325         foreach my $type (qw(To Cc Bcc)) {
1326             push @recipients, $action->$type();
1327         }
1328     }
1329
1330     if ( $self->Rules ) {
1331         for my $rule (@{$self->Rules}) {
1332             next unless $rule->{hints} && $rule->{hints}{class} eq 'SendEmail';
1333             my $data = $rule->{hints}{recipients};
1334             foreach my $type (qw(To Cc Bcc)) {
1335                 push @recipients, map {Email::Address->new($_)} @{$data->{$type}};
1336             }
1337         }
1338     }
1339     return @recipients;
1340 }
1341
1342 =head2 DeferredRecipients($freq, $include_sent )
1343
1344 Takes the following arguments:
1345
1346 =over
1347
1348 =item * a string to indicate the frequency of digest delivery.  Valid values are "daily", "weekly", or "susp".
1349
1350 =item * an optional argument which, if true, will return addresses even if this notification has been marked as 'sent' for this transaction.
1351
1352 =back
1353
1354 Returns an array of users who should now receive the notification that
1355 was recorded in this transaction.  Returns an empty array if there were
1356 no deferred users, or if $include_sent was not specified and the deferred
1357 notifications have been sent.
1358
1359 =cut
1360
1361 sub DeferredRecipients {
1362     my $self = shift;
1363     my $freq = shift;
1364     my $include_sent = @_? shift : 0;
1365
1366     my $attr = $self->FirstAttribute('DeferredRecipients');
1367
1368     return () unless ($attr);
1369
1370     my $deferred = $attr->Content;
1371
1372     return () unless ( ref($deferred) eq 'HASH' && exists $deferred->{$freq} );
1373
1374     # Skip it.
1375    
1376     for my $user (keys %{$deferred->{$freq}}) {
1377         if ($deferred->{$freq}->{$user}->{_sent} && !$include_sent) { 
1378             delete $deferred->{$freq}->{$user} 
1379         }
1380     }
1381     # Now get our users.  Easy.
1382     
1383     return keys %{ $deferred->{$freq} };
1384 }
1385
1386
1387
1388 # Transactions don't change. by adding this cache config directive, we don't lose pathalogically on long tickets.
1389 sub _CacheConfig {
1390   {
1391      'cache_p'        => 1,
1392      'fast_update_p'  => 1,
1393      'cache_for_sec'  => 6000,
1394   }
1395 }
1396
1397
1398 =head2 ACLEquivalenceObjects
1399
1400 This method returns a list of objects for which a user's rights also apply
1401 to this Transaction.
1402
1403 This currently only applies to Transaction Custom Fields on Tickets, so we return
1404 the Ticket's Queue and the Ticket.
1405
1406 This method is called from L<RT::Principal/HasRight>.
1407
1408 =cut
1409
1410 sub ACLEquivalenceObjects {
1411     my $self = shift;
1412
1413     return unless $self->ObjectType eq 'RT::Ticket';
1414     my $object = $self->Object;
1415     return $object,$object->QueueObj;
1416
1417 }
1418
1419
1420
1421
1422
1423 =head2 id
1424
1425 Returns the current value of id.
1426 (In the database, id is stored as int(11).)
1427
1428
1429 =cut
1430
1431
1432 =head2 ObjectType
1433
1434 Returns the current value of ObjectType.
1435 (In the database, ObjectType is stored as varchar(64).)
1436
1437
1438
1439 =head2 SetObjectType VALUE
1440
1441
1442 Set ObjectType to VALUE.
1443 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1444 (In the database, ObjectType will be stored as a varchar(64).)
1445
1446
1447 =cut
1448
1449
1450 =head2 ObjectId
1451
1452 Returns the current value of ObjectId.
1453 (In the database, ObjectId is stored as int(11).)
1454
1455
1456
1457 =head2 SetObjectId VALUE
1458
1459
1460 Set ObjectId to VALUE.
1461 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1462 (In the database, ObjectId will be stored as a int(11).)
1463
1464
1465 =cut
1466
1467
1468 =head2 TimeTaken
1469
1470 Returns the current value of TimeTaken.
1471 (In the database, TimeTaken is stored as int(11).)
1472
1473
1474
1475 =head2 SetTimeTaken VALUE
1476
1477
1478 Set TimeTaken to VALUE.
1479 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1480 (In the database, TimeTaken will be stored as a int(11).)
1481
1482
1483 =cut
1484
1485
1486 =head2 Type
1487
1488 Returns the current value of Type.
1489 (In the database, Type is stored as varchar(20).)
1490
1491
1492
1493 =head2 SetType VALUE
1494
1495
1496 Set Type to VALUE.
1497 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1498 (In the database, Type will be stored as a varchar(20).)
1499
1500
1501 =cut
1502
1503
1504 =head2 Field
1505
1506 Returns the current value of Field.
1507 (In the database, Field is stored as varchar(40).)
1508
1509
1510
1511 =head2 SetField VALUE
1512
1513
1514 Set Field to VALUE.
1515 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1516 (In the database, Field will be stored as a varchar(40).)
1517
1518
1519 =cut
1520
1521
1522 =head2 OldValue
1523
1524 Returns the current value of OldValue.
1525 (In the database, OldValue is stored as varchar(255).)
1526
1527
1528
1529 =head2 SetOldValue VALUE
1530
1531
1532 Set OldValue to VALUE.
1533 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1534 (In the database, OldValue will be stored as a varchar(255).)
1535
1536
1537 =cut
1538
1539
1540 =head2 NewValue
1541
1542 Returns the current value of NewValue.
1543 (In the database, NewValue is stored as varchar(255).)
1544
1545
1546
1547 =head2 SetNewValue VALUE
1548
1549
1550 Set NewValue to VALUE.
1551 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1552 (In the database, NewValue will be stored as a varchar(255).)
1553
1554
1555 =cut
1556
1557
1558 =head2 ReferenceType
1559
1560 Returns the current value of ReferenceType.
1561 (In the database, ReferenceType is stored as varchar(255).)
1562
1563
1564
1565 =head2 SetReferenceType VALUE
1566
1567
1568 Set ReferenceType to VALUE.
1569 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1570 (In the database, ReferenceType will be stored as a varchar(255).)
1571
1572
1573 =cut
1574
1575
1576 =head2 OldReference
1577
1578 Returns the current value of OldReference.
1579 (In the database, OldReference is stored as int(11).)
1580
1581
1582
1583 =head2 SetOldReference VALUE
1584
1585
1586 Set OldReference to VALUE.
1587 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1588 (In the database, OldReference will be stored as a int(11).)
1589
1590
1591 =cut
1592
1593
1594 =head2 NewReference
1595
1596 Returns the current value of NewReference.
1597 (In the database, NewReference is stored as int(11).)
1598
1599
1600
1601 =head2 SetNewReference VALUE
1602
1603
1604 Set NewReference to VALUE.
1605 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1606 (In the database, NewReference will be stored as a int(11).)
1607
1608
1609 =cut
1610
1611
1612 =head2 Data
1613
1614 Returns the current value of Data.
1615 (In the database, Data is stored as varchar(255).)
1616
1617
1618
1619 =head2 SetData VALUE
1620
1621
1622 Set Data to VALUE.
1623 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1624 (In the database, Data will be stored as a varchar(255).)
1625
1626
1627 =cut
1628
1629
1630 =head2 Creator
1631
1632 Returns the current value of Creator.
1633 (In the database, Creator is stored as int(11).)
1634
1635
1636 =cut
1637
1638
1639 =head2 Created
1640
1641 Returns the current value of Created.
1642 (In the database, Created is stored as datetime.)
1643
1644
1645 =cut
1646
1647
1648
1649 sub _CoreAccessible {
1650     {
1651
1652         id =>
1653                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1654         ObjectType =>
1655                 {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
1656         ObjectId =>
1657                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1658         TimeTaken =>
1659                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1660         Type =>
1661                 {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
1662         Field =>
1663                 {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
1664         OldValue =>
1665                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1666         NewValue =>
1667                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1668         ReferenceType =>
1669                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1670         OldReference =>
1671                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1672         NewReference =>
1673                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
1674         Data =>
1675                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
1676         Creator =>
1677                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
1678         Created =>
1679                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
1680
1681  }
1682 };
1683
1684 RT::Base->_ImportOverlays();
1685
1686 1;