1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
49 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
53 RT::Template - RT's template object
72 no warnings qw(redefine);
83 Description => 'read/write',
84 Type => 'read/write', #Type is one of Action or Message
85 Content => 'read/write',
86 Queue => 'read/write',
87 Creator => 'read/auto',
88 Created => 'read/auto',
89 LastUpdatedBy => 'read/auto',
90 LastUpdated => 'read/auto'
92 return $self->SUPER::_Accessible( @_, %Cols );
98 unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
99 return ( 0, $self->loc('Permission Denied') );
101 return $self->SUPER::_Set( @_ );
106 Takes the name of a table column. Returns its value as a string,
107 if the user passes an ACL check, otherwise returns undef.
114 unless ( $self->CurrentUserHasQueueRight('ShowTemplate') ) {
117 return $self->__Value( @_ );
121 =head2 Load <identifer>
123 Load a template, either by number or by name.
125 Note that loading templates by name using this method B<is
126 ambiguous>. Several queues may have template with the same name
127 and as well global template with the same name may exist.
128 Use L</LoadGlobalTemplate> and/or L<LoadQueueTemplate> to get
135 my $identifier = shift;
136 return undef unless $identifier;
138 if ( $identifier =~ /\D/ ) {
139 return $self->LoadByCol( 'Name', $identifier );
141 return $self->LoadById( $identifier );
144 =head2 LoadGlobalTemplate NAME
146 Load the global template with the name NAME
150 sub LoadGlobalTemplate {
154 return ( $self->LoadQueueTemplate( Queue => 0, Name => $name ) );
157 =head2 LoadQueueTemplate (Queue => QUEUEID, Name => NAME)
159 Loads the Queue template named NAME for Queue QUEUE.
161 Note that this method doesn't load a global template with the same name
162 if template in the queue doesn't exist. THe following code can be used:
164 $template->LoadQueueTemplate( Queue => $queue_id, Name => $template_name );
165 unless ( $template->id ) {
166 $template->LoadGlobalTemplate( $template_name );
167 unless ( $template->id ) {
172 # ok, template either queue's or global
177 sub LoadQueueTemplate {
185 return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
191 Takes a paramhash of Content, Queue, Name and Description.
192 Name should be a unique string identifying this Template.
193 Description and Content should be the template's title and content.
194 Queue should be 0 for a global template and the queue # for a queue-specific
197 Returns the Template's id # if the create was successful. Returns undef for
198 unknown database failure.
207 Description => '[no description]',
208 Type => 'Action', #By default, template are 'Action' templates
213 unless ( $args{'Queue'} ) {
214 unless ( $self->CurrentUser->HasRight(Right =>'ModifyTemplate', Object => $RT::System) ) {
215 return ( undef, $self->loc('Permission Denied') );
220 my $QueueObj = new RT::Queue( $self->CurrentUser );
221 $QueueObj->Load( $args{'Queue'} ) || return ( undef, $self->loc('Invalid queue') );
223 unless ( $QueueObj->CurrentUserHasRight('ModifyTemplate') ) {
224 return ( undef, $self->loc('Permission Denied') );
226 $args{'Queue'} = $QueueObj->Id;
229 my $result = $self->SUPER::Create(
230 Content => $args{'Content'},
231 Queue => $args{'Queue'},
232 Description => $args{'Description'},
233 Name => $args{'Name'},
242 Delete this template.
249 unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
250 return ( 0, $self->loc('Permission Denied') );
253 return ( $self->SUPER::Delete(@_) );
258 Returns true value if content of the template is empty, otherwise
265 my $content = $self->Content;
266 return 0 if defined $content && length $content;
272 Returns L<MIME::Entity> object parsed using L</Parse> method. Returns
273 undef if last call to L</Parse> failed or never be called.
275 Note that content of the template is UTF-8, but L<MIME::Parser> is not
276 good at handling it and all data of the entity should be treated as
277 octets and converted to perl strings using Encode::decode_utf8 or
284 return ( $self->{'MIMEObj'} );
289 This routine performs L<Text::Template> parsing on the template and then
290 imports the results into a L<MIME::Entity> so we can really use it. Use
291 L</MIMEObj> method to get the L<MIME::Entity> object.
293 Takes a hash containing Argument, TicketObj, and TransactionObj and other
294 arguments that will be available in the template's code. TicketObj and
295 TransactionObj are not mandatory, but highly recommended.
297 It returns a tuple of (val, message). If val is false, the message contains
307 if ($self->Content =~ m{^Content-Type:\s+text/html\b}im) {
308 local $RT::Transaction::PreferredContentType = 'text/html';
309 ($rv, $msg) = $self->_Parse(@_);
312 ($rv, $msg) = $self->_Parse(@_);
315 return ($rv, $msg) unless $rv;
317 my $mime_type = $self->MIMEObj->mime_type;
318 if (defined $mime_type and $mime_type eq 'text/html') {
319 $self->_DowngradeFromHTML(@_);
328 # clear prev MIME object
329 $self->{'MIMEObj'} = undef;
331 #We're passing in whatever we were passed. it's destined for _ParseContent
332 my ($content, $msg) = $self->_ParseContent(@_);
333 return ( 0, $msg ) unless defined $content && length $content;
335 if ( $content =~ /^\S/s && $content !~ /^\S+:/ ) {
337 "Template #". $self->id ." has leading line that doesn't"
338 ." look like header field, if you don't want to override"
339 ." any headers and don't want to see this error message"
340 ." then leave first line of the template empty"
342 $content = "\n".$content;
345 my $parser = MIME::Parser->new();
346 $parser->output_to_core(1);
347 $parser->tmp_to_core(1);
348 $parser->use_inner_files(1);
350 ### Should we forgive normally-fatal errors?
351 $parser->ignore_errors(1);
352 # MIME::Parser doesn't play well with perl strings
353 utf8::encode($content);
354 $self->{'MIMEObj'} = eval { $parser->parse_data( \$content ) };
355 if ( my $error = $@ || $parser->last_error ) {
356 $RT::Logger->error( "$error" );
357 return ( 0, $error );
361 $self->{'MIMEObj'}->head->unfold;
363 return ( 1, $self->loc("Template parsed") );
367 # Perform Template substitutions on the template
374 TransactionObj => undef,
378 unless ( $self->CurrentUserHasQueueRight('ShowTemplate') ) {
379 return (undef, $self->loc("Permission Denied"));
382 if ( $self->IsEmpty ) {
383 return ( undef, $self->loc("Template is empty") );
386 my $content = $self->SUPER::_Value('Content');
387 # We need to untaint the content of the template, since we'll be working
389 $content =~ s/^(.*)$/$1/;
390 my $template = Text::Template->new(
395 $args{'Ticket'} = delete $args{'TicketObj'} if $args{'TicketObj'};
396 $args{'Transaction'} = delete $args{'TransactionObj'} if $args{'TransactionObj'};
397 $args{'Requestor'} = eval { $args{'Ticket'}->Requestors->UserMembersObj->First->Name }
399 $args{'rtname'} = RT->Config->Get('rtname');
400 if ( $args{'Ticket'} ) {
401 my $t = $args{'Ticket'}; # avoid memory leak
402 $args{'loc'} = sub { $t->loc(@_) };
404 $args{'loc'} = sub { $self->loc(@_) };
407 foreach my $key ( keys %args ) {
408 next unless ref $args{ $key };
409 next if ref $args{ $key } =~ /^(ARRAY|HASH|SCALAR|CODE)$/;
410 my $val = $args{ $key };
411 $args{ $key } = \$val;
416 my $retval = $template->fill_in(
420 $RT::Logger->error("Template parsing error: $args{error}")
421 unless $args{error} =~ /^Died at /; # ignore intentional die()
426 return ( undef, $self->loc('Template parsing error') ) if $is_broken;
431 sub _DowngradeFromHTML {
433 my $orig_entity = $self->MIMEObj;
435 my $new_entity = $orig_entity->dup; # this will fail badly if we go away from InCore parsing
436 $new_entity->head->mime_attr( "Content-Type" => 'text/plain' );
437 $new_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
439 $orig_entity->head->mime_attr( "Content-Type" => 'text/html' );
440 $orig_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
441 $orig_entity->make_multipart('alternative', Force => 1);
443 require HTML::FormatText;
444 require HTML::TreeBuilder;
445 my $tree = HTML::TreeBuilder->new_from_content(
446 $new_entity->bodyhandle->as_string
448 $new_entity->bodyhandle(MIME::Body::InCore->new(
449 \(scalar HTML::FormatText->new(
456 $orig_entity->add_part($new_entity, 0); # plain comes before html
457 $self->{MIMEObj} = $orig_entity;
462 =head2 CurrentUserHasQueueRight
464 Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args.
468 sub CurrentUserHasQueueRight {
470 return ( $self->QueueObj->CurrentUserHasRight(@_) );