Upgrade 4.0.17 clean.
[usit-rt.git] / lib / RT / Transaction.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
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
403d7b0b 51 RT::Transaction - RT's transaction object
84fb5b46
MKG
52
53=head1 SYNOPSIS
54
55 use RT::Transaction;
56
57
58=head1 DESCRIPTION
59
60
61Each RT::Transaction describes an atomic change to a ticket object
62or an update to an RT::Ticket object.
63It can have arbitrary MIME attachments.
64
65
66=head1 METHODS
67
68
69=cut
70
71
72package RT::Transaction;
73
74use base 'RT::Record';
75use strict;
76use warnings;
77
78
79use vars qw( %_BriefDescriptions $PreferredContentType );
80
81use RT::Attachments;
82use RT::Scrips;
83use RT::Ruleset;
84
85use HTML::FormatText;
86use HTML::TreeBuilder;
87
88
89sub Table {'Transactions'}
90
91# {{{ sub Create
92
93=head2 Create
94
95Create a new transaction.
96
97This routine should _never_ be called by anything other than RT::Ticket.
98It should not be called
99from client code. Ever. Not ever. If you do this, we will hunt you down and break your kneecaps.
100Then the unpleasant stuff will start.
101
102TODO: Document what gets passed to this
103
104=cut
105
106sub 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
212Returns the Scrips object for this transaction.
213This routine is only useful on a freshly created transaction object.
214Scrips do not get persisted to the database with transactions.
215
216
217=cut
218
219
220sub Scrips {
221 my $self = shift;
222 return($self->{'scrips'});
223}
224
225
226=head2 Rules
227
228Returns the array of Rule objects for this transaction.
229This routine is only useful on a freshly created transaction object.
230Rules do not get persisted to the database with transactions.
231
232
233=cut
234
235
236sub Rules {
237 my $self = shift;
238 return($self->{'rules'});
239}
240
241
242
243=head2 Delete
244
245Delete this transaction. Currently DOES NOT CHECK ACLS
246
247=cut
248
249sub 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
278Returns the L<RT::Attachments> object which contains the "top-level" object
279attachment for this transaction.
280
281=cut
282
283sub 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
306If this transaction has attached mime objects, returns the body of the first
307textual part (as defined in RT::I18N::IsTextualContentType). Otherwise,
308returns undef.
309
310Takes a paramhash. If the $args{'Quote'} parameter is set, wraps this message
311at $args{'Wrap'}. $args{'Wrap'} defaults to 70.
312
313If $args{'Type'} is set to C<text/html>, this will return an HTML
314part of the message, if available. Otherwise it looks for a text/plain
315part. If $args{'Type'} is missing, it defaults to the value of
316C<$RT::Transaction::PreferredContentType>, if that's missing too,
317defaults to textual.
318
319=cut
320
321sub 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'} ) {
01e3b242
MKG
366 $content = $self->ApplyQuoteWrap(content => $content,
367 cols => $args{'Wrap'} );
84fb5b46 368
c36a7e1d 369 $content = $self->QuoteHeader . "\n$content\n\n";
84fb5b46
MKG
370 }
371
372 return ($content);
373}
374
c36a7e1d
MKG
375=head2 QuoteHeader
376
377Returns text prepended to content when transaction is quoted
378(see C<Quote> argument in L</Content>). By default returns
379localized "On <date> <user name> wrote:\n".
380
381=cut
382
383sub QuoteHeader {
384 my $self = shift;
385 return $self->loc("On [_1], [_2] wrote:", $self->CreatedAsString, $self->CreatorObj->Name);
386}
84fb5b46 387
01e3b242
MKG
388=head2 ApplyQuoteWrap PARAMHASH
389
390Wrapper to calculate wrap criteria and apply quote wrapping if needed.
391
392=cut
393
394sub 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
421Wrap the contents of transactions based on Wrap settings, maintaining
422the quote character from the original.
423
424=cut
425
426sub 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
84fb5b46
MKG
466
467=head2 Addresses
468
469Returns a hashref of addresses related to this transaction. See L<RT::Attachment/Addresses> for details.
470
471=cut
472
473sub 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
489Returns the RT::Attachment object which contains the content for this Transaction
490
491=cut
492
493
494sub 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
551If this transaction has attached mime objects, returns the first one's subject
552Otherwise, returns null
553
554=cut
555
556sub 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
566Returns all the RT::Attachment objects which are attached
567to this transaction. Takes an optional parameter, which is
568a ContentType that Attachments should be restricted to.
569
570=cut
571
572sub 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
603A private method used to attach a mime object to this transaction.
604
605=cut
606
607sub _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
626sub 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
656Returns a text string which describes this transaction
657
658=cut
659
660sub 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
678Returns a text string which briefly describes this transaction
679
680=cut
681
682sub 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 {
5b0d0914
MKG
712 my $canon = $self->Object->can("QueueObj")
713 ? sub { $self->Object->QueueObj->Lifecycle->CanonicalCase(@_) }
714 : sub { return $_[0] };
84fb5b46
MKG
715 return (
716 $self->loc(
717 "Status changed from [_1] to [_2]",
5b0d0914
MKG
718 "'" . $self->loc( $canon->($self->OldValue) ) . "'",
719 "'" . $self->loc( $canon->($self->NewValue) ) . "'"
84fb5b46
MKG
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 );
403d7b0b 857 if ( $URI->FromURI( $self->NewValue ) ) {
84fb5b46
MKG
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 );
403d7b0b 895 if ( $URI->FromURI( $self->OldValue ) ){
84fb5b46
MKG
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
1034Returns true if the creator of the transaction is a requestor of the ticket.
1035Returns false otherwise
1036
1037=cut
1038
1039sub 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
1047sub _OverlayAccessible {
1048 {
1049
1050 ObjectType => { public => 1},
1051 ObjectId => { public => 1},
1052
1053 }
1054};
1055
1056
1057
1058
1059sub _Set {
1060 my $self = shift;
1061 return ( 0, $self->loc('Transactions are immutable') );
1062}
1063
1064
1065
1066=head2 _Value
1067
1068Takes the name of a table column.
1069Returns its value as a string, if the user passes an ACL check
1070
1071=cut
1072
1073sub _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
1093Calls $self->CurrentUser->HasQueueRight for the right passed in here.
1094passed in here.
1095
1096=cut
1097
1098sub 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
1109Returns true if current user has rights to see this particular transaction.
1110
1111This fact depends on type of the transaction, type of an object the transaction
1112is attached to and may be other conditions, so this method is prefered over
1113custom implementations.
1114
1115=cut
1116
1117sub 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 }
403d7b0b
MKG
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
84fb5b46
MKG
1150 # Defer to the object in question
1151 return $self->Object->CurrentUserCanSee("Transaction");
1152}
1153
1154
1155sub Ticket {
1156 my $self = shift;
1157 return $self->ObjectId;
1158}
1159
1160sub TicketObj {
1161 my $self = shift;
1162 return $self->Object;
1163}
1164
1165sub 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
1179sub 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
1193sub 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
1200sub 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
1219sub 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
403d7b0b 1258=head2 LoadCustomFieldByIdentifier
84fb5b46 1259
403d7b0b
MKG
1260Finds and returns the custom field of the given name for the
1261transaction, overriding L<RT::Record/LoadCustomFieldByIdentifier> to
1262look for queue-specific CFs before global ones.
84fb5b46
MKG
1263
1264=cut
1265
403d7b0b 1266sub LoadCustomFieldByIdentifier {
84fb5b46
MKG
1267 my $self = shift;
1268 my $field = shift;
1269
403d7b0b
MKG
1270 return $self->SUPER::LoadCustomFieldByIdentifier($field)
1271 if ref $field or $field =~ /^\d+$/;
84fb5b46 1272
403d7b0b
MKG
1273 return $self->SUPER::LoadCustomFieldByIdentifier($field)
1274 unless UNIVERSAL::can( $self->Object, 'QueueObj' );
84fb5b46 1275
403d7b0b
MKG
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}
84fb5b46
MKG
1283
1284=head2 CustomFieldLookupType
1285
1286Returns the RT::Transaction lookup type, which can
1287be passed to RT::CustomField->Create() via the 'LookupType' hash key.
1288
1289=cut
1290
1291
1292sub CustomFieldLookupType {
1293 "RT::Queue-RT::Ticket-RT::Transaction";
1294}
1295
1296
1297=head2 SquelchMailTo
1298
1299Similar to Ticket class SquelchMailTo method - returns a list of
1300transaction's squelched addresses. As transactions are immutable, the
1301list of squelched recipients cannot be modified after creation.
1302
1303=cut
1304
1305sub SquelchMailTo {
1306 my $self = shift;
1307 return () unless $self->CurrentUserCanSee;
1308 return $self->Attributes->Named('SquelchMailTo');
1309}
1310
1311=head2 Recipients
1312
1313Returns the list of email addresses (as L<Email::Address> objects)
1314that this transaction would send mail to. There may be duplicates.
1315
1316=cut
1317
1318sub 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
1344Takes 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
1354Returns an array of users who should now receive the notification that
1355was recorded in this transaction. Returns an empty array if there were
1356no deferred users, or if $include_sent was not specified and the deferred
1357notifications have been sent.
1358
1359=cut
1360
1361sub 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.
1389sub _CacheConfig {
1390 {
1391 'cache_p' => 1,
1392 'fast_update_p' => 1,
1393 'cache_for_sec' => 6000,
1394 }
1395}
1396
1397
1398=head2 ACLEquivalenceObjects
1399
1400This method returns a list of objects for which a user's rights also apply
1401to this Transaction.
1402
1403This currently only applies to Transaction Custom Fields on Tickets, so we return
1404the Ticket's Queue and the Ticket.
1405
1406This method is called from L<RT::Principal/HasRight>.
1407
1408=cut
1409
1410sub 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
1425Returns 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
1434Returns the current value of ObjectType.
1435(In the database, ObjectType is stored as varchar(64).)
1436
1437
1438
1439=head2 SetObjectType VALUE
1440
1441
1442Set ObjectType to VALUE.
1443Returns (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
1452Returns the current value of ObjectId.
1453(In the database, ObjectId is stored as int(11).)
1454
1455
1456
1457=head2 SetObjectId VALUE
1458
1459
1460Set ObjectId to VALUE.
1461Returns (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
1470Returns the current value of TimeTaken.
1471(In the database, TimeTaken is stored as int(11).)
1472
1473
1474
1475=head2 SetTimeTaken VALUE
1476
1477
1478Set TimeTaken to VALUE.
1479Returns (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
1488Returns the current value of Type.
1489(In the database, Type is stored as varchar(20).)
1490
1491
1492
1493=head2 SetType VALUE
1494
1495
1496Set Type to VALUE.
1497Returns (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
1506Returns the current value of Field.
1507(In the database, Field is stored as varchar(40).)
1508
1509
1510
1511=head2 SetField VALUE
1512
1513
1514Set Field to VALUE.
1515Returns (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
1524Returns the current value of OldValue.
1525(In the database, OldValue is stored as varchar(255).)
1526
1527
1528
1529=head2 SetOldValue VALUE
1530
1531
1532Set OldValue to VALUE.
1533Returns (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
1542Returns the current value of NewValue.
1543(In the database, NewValue is stored as varchar(255).)
1544
1545
1546
1547=head2 SetNewValue VALUE
1548
1549
1550Set NewValue to VALUE.
1551Returns (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
1560Returns the current value of ReferenceType.
1561(In the database, ReferenceType is stored as varchar(255).)
1562
1563
1564
1565=head2 SetReferenceType VALUE
1566
1567
1568Set ReferenceType to VALUE.
1569Returns (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
1578Returns the current value of OldReference.
1579(In the database, OldReference is stored as int(11).)
1580
1581
1582
1583=head2 SetOldReference VALUE
1584
1585
1586Set OldReference to VALUE.
1587Returns (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
1596Returns the current value of NewReference.
1597(In the database, NewReference is stored as int(11).)
1598
1599
1600
1601=head2 SetNewReference VALUE
1602
1603
1604Set NewReference to VALUE.
1605Returns (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
1614Returns the current value of Data.
1615(In the database, Data is stored as varchar(255).)
1616
1617
1618
1619=head2 SetData VALUE
1620
1621
1622Set Data to VALUE.
1623Returns (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
1632Returns 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
1641Returns the current value of Created.
1642(In the database, Created is stored as datetime.)
1643
1644
1645=cut
1646
1647
1648
1649sub _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
1684RT::Base->_ImportOverlays();
1685
16861;