1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 # <sales@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 );
103 unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
104 return ( 0, $self->loc('Permission Denied') );
107 if (exists $args{Value}) {
108 if ($args{Field} eq 'Queue') {
110 # moving to another queue
111 my $queue = RT::Queue->new( $self->CurrentUser );
112 $queue->Load($args{Value});
113 unless ($queue->Id and $queue->CurrentUserHasRight('ModifyTemplate')) {
114 return ( 0, $self->loc('Permission Denied') );
118 unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyTemplate' )) {
119 return ( 0, $self->loc('Permission Denied') );
125 return $self->SUPER::_Set( @_ );
130 Takes the name of a table column. Returns its value as a string,
131 if the user passes an ACL check, otherwise returns undef.
138 unless ( $self->CurrentUserHasQueueRight('ShowTemplate') ) {
141 return $self->__Value( @_ );
145 =head2 Load <identifer>
147 Load a template, either by number or by name.
149 Note that loading templates by name using this method B<is
150 ambiguous>. Several queues may have template with the same name
151 and as well global template with the same name may exist.
152 Use L</LoadGlobalTemplate> and/or L<LoadQueueTemplate> to get
159 my $identifier = shift;
160 return undef unless $identifier;
162 if ( $identifier =~ /\D/ ) {
163 return $self->LoadByCol( 'Name', $identifier );
165 return $self->LoadById( $identifier );
168 =head2 LoadGlobalTemplate NAME
170 Load the global template with the name NAME
174 sub LoadGlobalTemplate {
178 return ( $self->LoadQueueTemplate( Queue => 0, Name => $name ) );
181 =head2 LoadQueueTemplate (Queue => QUEUEID, Name => NAME)
183 Loads the Queue template named NAME for Queue QUEUE.
185 Note that this method doesn't load a global template with the same name
186 if template in the queue doesn't exist. THe following code can be used:
188 $template->LoadQueueTemplate( Queue => $queue_id, Name => $template_name );
189 unless ( $template->id ) {
190 $template->LoadGlobalTemplate( $template_name );
191 unless ( $template->id ) {
196 # ok, template either queue's or global
201 sub LoadQueueTemplate {
209 return ( $self->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ) );
215 Takes a paramhash of Content, Queue, Name and Description.
216 Name should be a unique string identifying this Template.
217 Description and Content should be the template's title and content.
218 Queue should be 0 for a global template and the queue # for a queue-specific
221 Returns the Template's id # if the create was successful. Returns undef for
222 unknown database failure.
231 Description => '[no description]',
232 Type => 'Action', #By default, template are 'Action' templates
237 unless ( $args{'Queue'} ) {
238 unless ( $self->CurrentUser->HasRight(Right =>'ModifyTemplate', Object => $RT::System) ) {
239 return ( undef, $self->loc('Permission Denied') );
244 my $QueueObj = new RT::Queue( $self->CurrentUser );
245 $QueueObj->Load( $args{'Queue'} ) || return ( undef, $self->loc('Invalid queue') );
247 unless ( $QueueObj->CurrentUserHasRight('ModifyTemplate') ) {
248 return ( undef, $self->loc('Permission Denied') );
250 $args{'Queue'} = $QueueObj->Id;
253 my $result = $self->SUPER::Create(
254 Content => $args{'Content'},
255 Queue => $args{'Queue'},
256 Description => $args{'Description'},
257 Name => $args{'Name'},
266 Delete this template.
273 unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
274 return ( 0, $self->loc('Permission Denied') );
277 return ( $self->SUPER::Delete(@_) );
282 Returns true value if content of the template is empty, otherwise
289 my $content = $self->Content;
290 return 0 if defined $content && length $content;
296 Returns L<MIME::Entity> object parsed using L</Parse> method. Returns
297 undef if last call to L</Parse> failed or never be called.
299 Note that content of the template is UTF-8, but L<MIME::Parser> is not
300 good at handling it and all data of the entity should be treated as
301 octets and converted to perl strings using Encode::decode_utf8 or
308 return ( $self->{'MIMEObj'} );
313 This routine performs L<Text::Template> parsing on the template and then
314 imports the results into a L<MIME::Entity> so we can really use it. Use
315 L</MIMEObj> method to get the L<MIME::Entity> object.
317 Takes a hash containing Argument, TicketObj, and TransactionObj and other
318 arguments that will be available in the template's code. TicketObj and
319 TransactionObj are not mandatory, but highly recommended.
321 It returns a tuple of (val, message). If val is false, the message contains
331 if ($self->Content =~ m{^Content-Type:\s+text/html\b}im) {
332 local $RT::Transaction::PreferredContentType = 'text/html';
333 ($rv, $msg) = $self->_Parse(@_);
336 ($rv, $msg) = $self->_Parse(@_);
339 return ($rv, $msg) unless $rv;
341 my $mime_type = $self->MIMEObj->mime_type;
342 if (defined $mime_type and $mime_type eq 'text/html') {
343 $self->_DowngradeFromHTML(@_);
352 # clear prev MIME object
353 $self->{'MIMEObj'} = undef;
355 #We're passing in whatever we were passed. it's destined for _ParseContent
356 my ($content, $msg) = $self->_ParseContent(@_);
357 return ( 0, $msg ) unless defined $content && length $content;
359 if ( $content =~ /^\S/s && $content !~ /^\S+:/ ) {
361 "Template #". $self->id ." has leading line that doesn't"
362 ." look like header field, if you don't want to override"
363 ." any headers and don't want to see this error message"
364 ." then leave first line of the template empty"
366 $content = "\n".$content;
369 my $parser = MIME::Parser->new();
370 $parser->output_to_core(1);
371 $parser->tmp_to_core(1);
372 $parser->use_inner_files(1);
374 ### Should we forgive normally-fatal errors?
375 $parser->ignore_errors(1);
376 # MIME::Parser doesn't play well with perl strings
377 utf8::encode($content);
378 $self->{'MIMEObj'} = eval { $parser->parse_data( \$content ) };
379 if ( my $error = $@ || $parser->last_error ) {
380 $RT::Logger->error( "$error" );
381 return ( 0, $error );
385 $self->{'MIMEObj'}->head->unfold;
386 $self->{'MIMEObj'}->head->modify(1);
388 return ( 1, $self->loc("Template parsed") );
392 # Perform Template substitutions on the template
399 TransactionObj => undef,
403 unless ( $self->CurrentUserHasQueueRight('ShowTemplate') ) {
404 return (undef, $self->loc("Permission Denied"));
407 if ( $self->IsEmpty ) {
408 return ( undef, $self->loc("Template is empty") );
411 my $content = $self->SUPER::_Value('Content');
412 # We need to untaint the content of the template, since we'll be working
414 $content =~ s/^(.*)$/$1/;
415 my $template = Text::Template->new(
420 $args{'Ticket'} = delete $args{'TicketObj'} if $args{'TicketObj'};
421 $args{'Transaction'} = delete $args{'TransactionObj'} if $args{'TransactionObj'};
422 $args{'Requestor'} = eval { $args{'Ticket'}->Requestors->UserMembersObj->First->Name }
424 $args{'rtname'} = RT->Config->Get('rtname');
425 if ( $args{'Ticket'} ) {
426 my $t = $args{'Ticket'}; # avoid memory leak
427 $args{'loc'} = sub { $t->loc(@_) };
429 $args{'loc'} = sub { $self->loc(@_) };
432 foreach my $key ( keys %args ) {
433 next unless ref $args{ $key };
434 next if ref $args{ $key } =~ /^(ARRAY|HASH|SCALAR|CODE)$/;
435 my $val = $args{ $key };
436 $args{ $key } = \$val;
441 my $retval = $template->fill_in(
445 $RT::Logger->error("Template parsing error: $args{error}")
446 unless $args{error} =~ /^Died at /; # ignore intentional die()
451 return ( undef, $self->loc('Template parsing error') ) if $is_broken;
456 sub _DowngradeFromHTML {
458 my $orig_entity = $self->MIMEObj;
460 my $new_entity = $orig_entity->dup; # this will fail badly if we go away from InCore parsing
461 $new_entity->head->mime_attr( "Content-Type" => 'text/plain' );
462 $new_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
464 $orig_entity->head->mime_attr( "Content-Type" => 'text/html' );
465 $orig_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
466 $orig_entity->make_multipart('alternative', Force => 1);
468 require HTML::FormatText;
469 require HTML::TreeBuilder;
471 # need to decode_utf8, see the doc of MIMEObj method
472 my $tree = HTML::TreeBuilder->new_from_content(
473 Encode::decode_utf8($new_entity->bodyhandle->as_string)
475 $new_entity->bodyhandle(MIME::Body::InCore->new(
476 \(scalar HTML::FormatText->new(
483 $orig_entity->add_part($new_entity, 0); # plain comes before html
484 $self->{MIMEObj} = $orig_entity;
489 =head2 CurrentUserHasQueueRight
491 Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args.
495 sub CurrentUserHasQueueRight {
497 return ( $self->QueueObj->CurrentUserHasRight(@_) );