starting to work...
[freeside.git] / rt / lib / RT / Template.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2012 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 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org> 
50
51 =head1 NAME
52
53   RT::Template - RT's template object
54
55 =head1 SYNOPSIS
56
57   use RT::Template;
58
59 =head1 DESCRIPTION
60
61
62 =head1 METHODS
63
64
65 =cut
66
67
68 package RT::Template;
69
70 use strict;
71 use warnings;
72
73
74
75 use Text::Template;
76 use MIME::Entity;
77 use MIME::Parser;
78 use Scalar::Util 'blessed';
79
80 sub _Accessible {
81     my $self = shift;
82     my %Cols = (
83         id            => 'read',
84         Name          => 'read/write',
85         Description   => 'read/write',
86         Type          => 'read/write',    #Type is one of Perl or Simple
87         Content       => 'read/write',
88         Queue         => 'read/write',
89         Creator       => 'read/auto',
90         Created       => 'read/auto',
91         LastUpdatedBy => 'read/auto',
92         LastUpdated   => 'read/auto'
93     );
94     return $self->SUPER::_Accessible( @_, %Cols );
95 }
96
97 sub _Set {
98     my $self = shift;
99     
100     unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
101         return ( 0, $self->loc('Permission Denied') );
102     }
103     return $self->SUPER::_Set( @_ );
104 }
105
106 =head2 _Value
107
108 Takes the name of a table column. Returns its value as a string,
109 if the user passes an ACL check, otherwise returns undef.
110
111 =cut
112
113 sub _Value {
114     my $self  = shift;
115
116     unless ( $self->CurrentUserCanRead() ) {
117         return undef;
118     }
119     return $self->__Value( @_ );
120
121 }
122
123 =head2 Load <identifier>
124
125 Load a template, either by number or by name.
126
127 Note that loading templates by name using this method B<is
128 ambiguous>. Several queues may have template with the same name
129 and as well global template with the same name may exist.
130 Use L</LoadGlobalTemplate> and/or L<LoadQueueTemplate> to get
131 precise result.
132
133 =cut
134
135 sub Load {
136     my $self       = shift;
137     my $identifier = shift;
138     return undef unless $identifier;
139
140     if ( $identifier =~ /\D/ ) {
141         return $self->LoadByCol( 'Name', $identifier );
142     }
143     return $self->LoadById( $identifier );
144 }
145
146 =head2 LoadGlobalTemplate NAME
147
148 Load the global template with the name NAME
149
150 =cut
151
152 sub LoadGlobalTemplate {
153     my $self = shift;
154     my $name = shift;
155
156     return ( $self->LoadQueueTemplate( Queue => 0, Name => $name ) );
157 }
158
159 =head2 LoadQueueTemplate (Queue => QUEUEID, Name => NAME)
160
161 Loads the Queue template named NAME for Queue QUEUE.
162
163 Note that this method doesn't load a global template with the same name
164 if template in the queue doesn't exist. THe following code can be used:
165
166     $template->LoadQueueTemplate( Queue => $queue_id, Name => $template_name );
167     unless ( $template->id ) {
168         $template->LoadGlobalTemplate( $template_name );
169         unless ( $template->id ) {
170             # no template
171             ...
172         }
173     }
174     # ok, template either queue's or global
175     ...
176
177 =cut
178
179 sub LoadQueueTemplate {
180     my $self = shift;
181     my %args = (
182         Queue => undef,
183         Name  => undef,
184         @_
185     );
186
187     return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
188
189 }
190
191 =head2 Create
192
193 Takes a paramhash of Content, Queue, Name and Description.
194 Name should be a unique string identifying this Template.
195 Description and Content should be the template's title and content.
196 Queue should be 0 for a global template and the queue # for a queue-specific 
197 template.
198
199 Returns the Template's id # if the create was successful. Returns undef for
200 unknown database failure.
201
202 =cut
203
204 sub Create {
205     my $self = shift;
206     my %args = (
207         Content     => undef,
208         Queue       => 0,
209         Description => '[no description]',
210         Type        => 'Perl',
211         Name        => undef,
212         @_
213     );
214
215     if ( $args{Type} eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System) ) {
216         return ( undef, $self->loc('Permission Denied') );
217     }
218
219     unless ( $args{'Queue'} ) {
220         unless ( $self->CurrentUser->HasRight(Right =>'ModifyTemplate', Object => $RT::System) ) {
221             return ( undef, $self->loc('Permission Denied') );
222         }
223         $args{'Queue'} = 0;
224     }
225     else {
226         my $QueueObj = RT::Queue->new( $self->CurrentUser );
227         $QueueObj->Load( $args{'Queue'} ) || return ( undef, $self->loc('Invalid queue') );
228     
229         unless ( $QueueObj->CurrentUserHasRight('ModifyTemplate') ) {
230             return ( undef, $self->loc('Permission Denied') );
231         }
232         $args{'Queue'} = $QueueObj->Id;
233     }
234
235     my $result = $self->SUPER::Create(
236         Content     => $args{'Content'},
237         Queue       => $args{'Queue'},
238         Description => $args{'Description'},
239         Name        => $args{'Name'},
240         Type        => $args{'Type'},
241     );
242
243     return ($result);
244
245 }
246
247 =head2 Delete
248
249 Delete this template.
250
251 =cut
252
253 sub Delete {
254     my $self = shift;
255
256     unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
257         return ( 0, $self->loc('Permission Denied') );
258     }
259
260     return ( $self->SUPER::Delete(@_) );
261 }
262
263 =head2 IsEmpty
264
265 Returns true value if content of the template is empty, otherwise
266 returns false.
267
268 =cut
269
270 sub IsEmpty {
271     my $self = shift;
272     my $content = $self->Content;
273     return 0 if defined $content && length $content;
274     return 1;
275 }
276
277 =head2 MIMEObj
278
279 Returns L<MIME::Entity> object parsed using L</Parse> method. Returns
280 undef if last call to L</Parse> failed or never be called.
281
282 Note that content of the template is UTF-8, but L<MIME::Parser> is not
283 good at handling it and all data of the entity should be treated as
284 octets and converted to perl strings using Encode::decode_utf8 or
285 something else.
286
287 =cut
288
289 sub MIMEObj {
290     my $self = shift;
291     return ( $self->{'MIMEObj'} );
292 }
293
294 =head2 Parse
295
296 This routine performs L<Text::Template> parsing on the template and then
297 imports the results into a L<MIME::Entity> so we can really use it. Use
298 L</MIMEObj> method to get the L<MIME::Entity> object.
299
300 Takes a hash containing Argument, TicketObj, and TransactionObj and other
301 arguments that will be available in the template's code. TicketObj and
302 TransactionObj are not mandatory, but highly recommended.
303
304 It returns a tuple of (val, message). If val is false, the message contains
305 an error message.
306
307 =cut
308
309 sub Parse {
310     my $self = shift;
311     my ($rv, $msg);
312
313
314     if ($self->Content =~ m{^Content-Type:\s+text/html\b}im) {
315         local $RT::Transaction::PreferredContentType = 'text/html';
316         ($rv, $msg) = $self->_Parse(@_);
317     }
318     else {
319         ($rv, $msg) = $self->_Parse(@_);
320     }
321
322     return ($rv, $msg) unless $rv;
323
324     my $mime_type   = $self->MIMEObj->mime_type;
325     if (defined $mime_type and $mime_type eq 'text/html') {
326         $self->_DowngradeFromHTML(@_);
327     }
328
329     return ($rv, $msg);
330 }
331
332 sub _Parse {
333     my $self = shift;
334
335     # clear prev MIME object
336     $self->{'MIMEObj'} = undef;
337
338     #We're passing in whatever we were passed. it's destined for _ParseContent
339     my ($content, $msg) = $self->_ParseContent(@_);
340     return ( 0, $msg ) unless defined $content && length $content;
341
342     if ( $content =~ /^\S/s && $content !~ /^\S+:/ ) {
343         $RT::Logger->error(
344             "Template #". $self->id ." has leading line that doesn't"
345             ." look like header field, if you don't want to override"
346             ." any headers and don't want to see this error message"
347             ." then leave first line of the template empty"
348         );
349         $content = "\n".$content;
350     }
351
352     my $parser = MIME::Parser->new();
353     $parser->output_to_core(1);
354     $parser->tmp_to_core(1);
355     $parser->use_inner_files(1);
356
357     ### Should we forgive normally-fatal errors?
358     $parser->ignore_errors(1);
359     # MIME::Parser doesn't play well with perl strings
360     utf8::encode($content);
361     $self->{'MIMEObj'} = eval { $parser->parse_data( \$content ) };
362     if ( my $error = $@ || $parser->last_error ) {
363         $RT::Logger->error( "$error" );
364         return ( 0, $error );
365     }
366
367     # Unfold all headers
368     $self->{'MIMEObj'}->head->unfold;
369
370     return ( 1, $self->loc("Template parsed") );
371
372 }
373
374 # Perform Template substitutions on the template
375
376 sub _ParseContent {
377     my $self = shift;
378     my %args = (
379         Argument       => undef,
380         TicketObj      => undef,
381         TransactionObj => undef,
382         @_
383     );
384
385     unless ( $self->CurrentUserCanRead() ) {
386         return (undef, $self->loc("Permission Denied"));
387     }
388
389     if ( $self->IsEmpty ) {
390         return ( undef, $self->loc("Template is empty") );
391     }
392
393     my $content = $self->SUPER::_Value('Content');
394     # We need to untaint the content of the template, since we'll be working
395     # with it
396     $content =~ s/^(.*)$/$1/;
397
398     $args{'Ticket'} = delete $args{'TicketObj'} if $args{'TicketObj'};
399     $args{'Transaction'} = delete $args{'TransactionObj'} if $args{'TransactionObj'};
400     $args{'Requestor'} = eval { $args{'Ticket'}->Requestors->UserMembersObj->First->Name }
401         if $args{'Ticket'};
402     $args{'rtname'}    = RT->Config->Get('rtname');
403     if ( $args{'Ticket'} ) {
404         my $t = $args{'Ticket'}; # avoid memory leak
405         $args{'loc'} = sub { $t->loc(@_) };
406     } else {
407         $args{'loc'} = sub { $self->loc(@_) };
408     }
409
410     if ($self->Type eq 'Perl') {
411         return $self->_ParseContentPerl(
412             Content      => $content,
413             TemplateArgs => \%args,
414         );
415     }
416     else {
417         return $self->_ParseContentSimple(
418             Content      => $content,
419             TemplateArgs => \%args,
420         );
421     }
422 }
423
424 # uses Text::Template for Perl templates
425 sub _ParseContentPerl {
426     my $self = shift;
427     my %args = (
428         Content      => undef,
429         TemplateArgs => {},
430         @_,
431     );
432
433     foreach my $key ( keys %{ $args{TemplateArgs} } ) {
434         my $val = $args{TemplateArgs}{ $key };
435         next unless ref $val;
436         next if ref $val =~ /^(ARRAY|HASH|SCALAR|CODE)$/;
437         $args{TemplateArgs}{ $key } = \$val;
438     }
439
440     my $template = Text::Template->new(
441         TYPE   => 'STRING',
442         SOURCE => $args{Content},
443     );
444     my $is_broken = 0;
445     my $retval = $template->fill_in(
446         HASH => $args{TemplateArgs},
447         BROKEN => sub {
448             my (%args) = @_;
449             $RT::Logger->error("Template parsing error: $args{error}")
450                 unless $args{error} =~ /^Died at /; # ignore intentional die()
451             $is_broken++;
452             return undef;
453         },
454     );
455     return ( undef, $self->loc('Template parsing error') ) if $is_broken;
456
457     return ($retval);
458 }
459
460 sub _ParseContentSimple {
461     my $self = shift;
462     my %args = (
463         Content      => undef,
464         TemplateArgs => {},
465         @_,
466     );
467
468     $self->_MassageSimpleTemplateArgs(%args);
469
470     my $template = Text::Template->new(
471         TYPE   => 'STRING',
472         SOURCE => $args{Content},
473     );
474     my ($ok) = $template->compile;
475     return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok;
476
477     # copied from Text::Template::fill_in and refactored to be simple variable
478     # interpolation
479     my $fi_r = '';
480     foreach my $fi_item (@{$template->{SOURCE}}) {
481         my ($fi_type, $fi_text, $fi_lineno) = @$fi_item;
482         if ($fi_type eq 'TEXT') {
483             $fi_r .= $fi_text;
484         } elsif ($fi_type eq 'PROG') {
485             my $fi_res;
486             my $original_fi_text = $fi_text;
487
488             # strip surrounding whitespace for simpler regexes
489             $fi_text =~ s/^\s+//;
490             $fi_text =~ s/\s+$//;
491
492             # if the codeblock is a simple $Variable lookup, use the value from
493             # the TemplateArgs hash...
494             if (my ($var) = $fi_text =~ /^\$(\w+)$/) {
495                 if (exists $args{TemplateArgs}{$var}) {
496                     $fi_res = $args{TemplateArgs}{$var};
497                 }
498             }
499
500             # if there was no substitution then just reinsert the codeblock
501             if (!defined $fi_res) {
502                 $fi_res = "{$original_fi_text}";
503             }
504
505             # If the value of the filled-in text really was undef,
506             # change it to an explicit empty string to avoid undefined
507             # value warnings later.
508             $fi_res = '' unless defined $fi_res;
509
510             $fi_r .= $fi_res;
511         }
512     }
513
514     return $fi_r;
515 }
516
517 sub _MassageSimpleTemplateArgs {
518     my $self = shift;
519     my %args = (
520         TemplateArgs => {},
521         @_,
522     );
523
524     my $template_args = $args{TemplateArgs};
525
526     if (my $ticket = $template_args->{Ticket}) {
527         for my $column (qw/Id Subject Type InitialPriority FinalPriority Priority TimeEstimated TimeWorked Status TimeLeft Told Starts Started Due Resolved RequestorAddresses AdminCcAddresses CcAddresses/) {
528             $template_args->{"Ticket".$column} = $ticket->$column;
529         }
530
531         $template_args->{"TicketQueueId"}   = $ticket->Queue;
532         $template_args->{"TicketQueueName"} = $ticket->QueueObj->Name;
533
534         $template_args->{"TicketOwnerId"}    = $ticket->Owner;
535         $template_args->{"TicketOwnerName"}  = $ticket->OwnerObj->Name;
536         $template_args->{"TicketOwnerEmailAddress"} = $ticket->OwnerObj->EmailAddress;
537
538         my $cfs = $ticket->CustomFields;
539         while (my $cf = $cfs->Next) {
540             $template_args->{"TicketCF" . $cf->Name} = $ticket->CustomFieldValuesAsString($cf->Name);
541         }
542     }
543
544     if (my $txn = $template_args->{Transaction}) {
545         for my $column (qw/Id TimeTaken Type Field OldValue NewValue Data Content Subject Description BriefDescription/) {
546             $template_args->{"Transaction".$column} = $txn->$column;
547         }
548
549         my $cfs = $txn->CustomFields;
550         while (my $cf = $cfs->Next) {
551             $template_args->{"TransactionCF" . $cf->Name} = $txn->CustomFieldValuesAsString($cf->Name);
552         }
553     }
554 }
555
556 sub _DowngradeFromHTML {
557     my $self = shift;
558     my $orig_entity = $self->MIMEObj;
559
560     my $new_entity = $orig_entity->dup; # this will fail badly if we go away from InCore parsing
561     $new_entity->head->mime_attr( "Content-Type" => 'text/plain' );
562     $new_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
563
564     $orig_entity->head->mime_attr( "Content-Type" => 'text/html' );
565     $orig_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
566     $orig_entity->make_multipart('alternative', Force => 1);
567
568     require HTML::FormatText;
569     require HTML::TreeBuilder;
570     require Encode;
571     # need to decode_utf8, see the doc of MIMEObj method
572     my $tree = HTML::TreeBuilder->new_from_content(
573         Encode::decode_utf8($new_entity->bodyhandle->as_string)
574     );
575     $new_entity->bodyhandle(MIME::Body::InCore->new(
576         \(scalar HTML::FormatText->new(
577             leftmargin  => 0,
578             rightmargin => 78,
579         )->format( $tree ))
580     ));
581     $tree->delete;
582
583     $orig_entity->add_part($new_entity, 0); # plain comes before html
584     $self->{MIMEObj} = $orig_entity;
585
586     return;
587 }
588
589 =head2 CurrentUserHasQueueRight
590
591 Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args.
592
593 =cut
594
595 sub CurrentUserHasQueueRight {
596     my $self = shift;
597     return ( $self->QueueObj->CurrentUserHasRight(@_) );
598 }
599
600 =head2 SetType
601
602 If setting Type to Perl, require the ExecuteCode right.
603
604 =cut
605
606 sub SetType {
607     my $self    = shift;
608     my $NewType = shift;
609
610     if ($NewType eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
611         return ( undef, $self->loc('Permission Denied') );
612     }
613
614     return $self->_Set( Field => 'Type', Value => $NewType );
615 }
616
617 =head2 SetContent
618
619 If changing content and the type is Perl, require the ExecuteCode right.
620
621 =cut
622
623 sub SetContent {
624     my $self       = shift;
625     my $NewContent = shift;
626
627     if ($self->Type eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
628         return ( undef, $self->loc('Permission Denied') );
629     }
630
631     return $self->_Set( Field => 'Content', Value => $NewContent );
632 }
633
634 sub _UpdateAttributes {
635     my $self = shift;
636     my %args = (
637         NewValues => {},
638         @_,
639     );
640
641     my $type = $args{NewValues}{Type} || $self->Type;
642
643     # forbid updating content when the (possibly new) value of Type is Perl
644     if ($type eq 'Perl' && exists $args{NewValues}{Content}) {
645         if (!$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
646             return $self->loc('Permission Denied');
647         }
648     }
649
650     return $self->SUPER::_UpdateAttributes(%args);
651 }
652
653 =head2 CompileCheck
654
655 If the template's Type is Perl, then compile check all the codeblocks to see if
656 they are syntactically valid. We eval them in a codeblock to avoid actually
657 executing the code.
658
659 Returns an (ok, message) pair.
660
661 =cut
662
663 sub CompileCheck {
664     my $self = shift;
665
666     return (1, $self->loc("Template does not include Perl code"))
667         unless $self->Type eq 'Perl';
668
669     my $content = $self->Content;
670     $content = '' if !defined($content);
671
672     my $template = Text::Template->new(
673         TYPE   => 'STRING',
674         SOURCE => $content,
675     );
676     my ($ok) = $template->compile;
677     return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok;
678
679     # copied from Text::Template::fill_in and refactored to be compile checks
680     foreach my $fi_item (@{$template->{SOURCE}}) {
681         my ($fi_type, $fi_text, $fi_lineno) = @$fi_item;
682         next unless $fi_type eq 'PROG';
683
684         do {
685             no strict 'vars';
686             eval "sub { $fi_text }";
687         };
688         next if !$@;
689
690         my $error = $@;
691
692         # provide a (hopefully) useful line number for the error, but clean up
693         # all the other extraneous garbage
694         $error =~ s/\(eval \d+\) line (\d+).*/"template line " . ($1+$fi_lineno-1)/es;
695
696         return (0, $self->loc("Couldn't compile template codeblock '[_1]': [_2]", $fi_text, $error));
697     }
698
699     return (1, $self->loc("Template compiles"));
700 }
701
702 =head2 CurrentUserCanRead
703
704 =cut
705
706 sub CurrentUserCanRead {
707     my $self =shift;
708
709     return 1 if $self->CurrentUserHasQueueRight('ShowTemplate');
710
711     return $self->CurrentUser->HasRight( Right =>'ShowGlobalTemplates', Object => $RT::System )
712         if !$self->QueueObj->Id;
713
714     return;
715 }
716
717 1;
718
719 use RT::Queue;
720 use base 'RT::Record';
721
722 sub Table {'Templates'}
723
724
725
726
727
728
729 =head2 id
730
731 Returns the current value of id.
732 (In the database, id is stored as int(11).)
733
734
735 =cut
736
737
738 =head2 Queue
739
740 Returns the current value of Queue.
741 (In the database, Queue is stored as int(11).)
742
743
744
745 =head2 SetQueue VALUE
746
747
748 Set Queue to VALUE.
749 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
750 (In the database, Queue will be stored as a int(11).)
751
752
753 =cut
754
755
756 =head2 QueueObj
757
758 Returns the Queue Object which has the id returned by Queue
759
760
761 =cut
762
763 sub QueueObj {
764         my $self = shift;
765         my $Queue =  RT::Queue->new($self->CurrentUser);
766         $Queue->Load($self->__Value('Queue'));
767         return($Queue);
768 }
769
770 =head2 Name
771
772 Returns the current value of Name.
773 (In the database, Name is stored as varchar(200).)
774
775
776
777 =head2 SetName VALUE
778
779
780 Set Name to VALUE.
781 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
782 (In the database, Name will be stored as a varchar(200).)
783
784
785 =cut
786
787
788 =head2 Description
789
790 Returns the current value of Description.
791 (In the database, Description is stored as varchar(255).)
792
793
794
795 =head2 SetDescription VALUE
796
797
798 Set Description to VALUE.
799 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
800 (In the database, Description will be stored as a varchar(255).)
801
802
803 =cut
804
805
806 =head2 Type
807
808 Returns the current value of Type.
809 (In the database, Type is stored as varchar(16).)
810
811
812
813 =head2 SetType VALUE
814
815
816 Set Type to VALUE.
817 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
818 (In the database, Type will be stored as a varchar(16).)
819
820
821 =cut
822
823
824 =head2 Language
825
826 Returns the current value of Language.
827 (In the database, Language is stored as varchar(16).)
828
829
830
831 =head2 SetLanguage VALUE
832
833
834 Set Language to VALUE.
835 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
836 (In the database, Language will be stored as a varchar(16).)
837
838
839 =cut
840
841
842 =head2 TranslationOf
843
844 Returns the current value of TranslationOf.
845 (In the database, TranslationOf is stored as int(11).)
846
847
848
849 =head2 SetTranslationOf VALUE
850
851
852 Set TranslationOf to VALUE.
853 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
854 (In the database, TranslationOf will be stored as a int(11).)
855
856
857 =cut
858
859
860 =head2 Content
861
862 Returns the current value of Content.
863 (In the database, Content is stored as text.)
864
865
866
867 =head2 SetContent VALUE
868
869
870 Set Content to VALUE.
871 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
872 (In the database, Content will be stored as a text.)
873
874
875 =cut
876
877
878 =head2 LastUpdated
879
880 Returns the current value of LastUpdated.
881 (In the database, LastUpdated is stored as datetime.)
882
883
884 =cut
885
886
887 =head2 LastUpdatedBy
888
889 Returns the current value of LastUpdatedBy.
890 (In the database, LastUpdatedBy is stored as int(11).)
891
892
893 =cut
894
895
896 =head2 Creator
897
898 Returns the current value of Creator.
899 (In the database, Creator is stored as int(11).)
900
901
902 =cut
903
904
905 =head2 Created
906
907 Returns the current value of Created.
908 (In the database, Created is stored as datetime.)
909
910
911 =cut
912
913
914
915 sub _CoreAccessible {
916     {
917
918         id =>
919                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
920         Queue =>
921                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
922         Name =>
923                 {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
924         Description =>
925                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
926         Type =>
927                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
928         Language =>
929                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
930         TranslationOf =>
931                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
932         Content =>
933                 {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
934         LastUpdated =>
935                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
936         LastUpdatedBy =>
937                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
938         Creator =>
939                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
940         Created =>
941                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
942
943  }
944 };
945
946 RT::Base->_ImportOverlays();
947
948 1;