X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;ds=sidebyside;f=rt%2Flib%2FRT%2FTemplate.pm;h=69680166a0c9adbac1eb2d7c830d2f77bb1a342e;hb=44dd00a3ff974a17999e86e64488e996edc71e3c;hp=117cc3f1c52015d638d8f5181e4b7161edf89ad6;hpb=85e677b86fc37c54e6de2b06340351a28f5a5916;p=freeside.git diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm index 117cc3f1c..69680166a 100755 --- a/rt/lib/RT/Template.pm +++ b/rt/lib/RT/Template.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -70,7 +70,9 @@ package RT::Template; use strict; use warnings; +use base 'RT::Record'; +use RT::Queue; use Text::Template; use MIME::Entity; @@ -151,7 +153,7 @@ Load a template, either by number or by name. Note that loading templates by name using this method B. Several queues may have template with the same name and as well global template with the same name may exist. -Use L and/or L to get +Use L, L or L to get precise result. =cut @@ -167,6 +169,37 @@ sub Load { return $self->LoadById( $identifier ); } +=head2 LoadByName + +Takes Name and Queue arguments. Tries to load queue specific template +first, then global. If Queue argument is omitted then global template +is tried, not template with the name in any queue. + +=cut + +sub LoadByName { + my $self = shift; + my %args = ( + Queue => undef, + Name => undef, + @_ + ); + my $queue = $args{'Queue'}; + if ( blessed $queue ) { + $queue = $queue->id; + } elsif ( defined $queue and $queue =~ /\D/ ) { + my $tmp = RT::Queue->new( $self->CurrentUser ); + $tmp->Load($queue); + $queue = $tmp->id; + } + + return $self->LoadGlobalTemplate( $args{'Name'} ) unless $queue; + + $self->LoadQueueTemplate( Queue => $queue, Name => $args{'Name'} ); + return $self->id if $self->id; + return $self->LoadGlobalTemplate( $args{'Name'} ); +} + =head2 LoadGlobalTemplate NAME Load the global template with the name NAME @@ -185,18 +218,7 @@ sub LoadGlobalTemplate { Loads the Queue template named NAME for Queue QUEUE. Note that this method doesn't load a global template with the same name -if template in the queue doesn't exist. THe following code can be used: - - $template->LoadQueueTemplate( Queue => $queue_id, Name => $template_name ); - unless ( $template->id ) { - $template->LoadGlobalTemplate( $template_name ); - unless ( $template->id ) { - # no template - ... - } - } - # ok, template either queue's or global - ... +if template in the queue doesn't exist. Use L. =cut @@ -256,7 +278,17 @@ sub Create { $args{'Queue'} = $QueueObj->Id; } - my $result = $self->SUPER::Create( + return ( undef, $self->loc('Name is required') ) + unless $args{Name}; + + { + my $tmp = $self->new( RT->SystemUser ); + $tmp->LoadByCols( Name => $args{'Name'}, Queue => $args{'Queue'} ); + return ( undef, $self->loc('A Template with that name already exists') ) + if $tmp->id; + } + + my ( $result, $msg ) = $self->SUPER::Create( Content => $args{'Content'}, Queue => $args{'Queue'}, Description => $args{'Description'}, @@ -264,7 +296,11 @@ sub Create { Type => $args{'Type'}, ); - return ($result); + if ( wantarray ) { + return ( $result, $msg ); + } else { + return ( $result ); + } } @@ -281,9 +317,28 @@ sub Delete { return ( 0, $self->loc('Permission Denied') ); } + if ( !$self->IsOverride && $self->UsedBy->Count ) { + return ( 0, $self->loc('Template is in use') ); + } + return ( $self->SUPER::Delete(@_) ); } +=head2 UsedBy + +Returns L limitted to scrips that use this template. Takes +into account that template can be overriden in a queue. + +=cut + +sub UsedBy { + my $self = shift; + + my $scrips = RT::Scrips->new( $self->CurrentUser ); + $scrips->LimitByTemplate( $self ); + return $scrips; +} + =head2 IsEmpty Returns true value if content of the template is empty, otherwise @@ -298,15 +353,31 @@ sub IsEmpty { return 1; } +=head2 IsOverride + +Returns true if it's queue specific template and there is global +template with the same name. + +=cut + +sub IsOverride { + my $self = shift; + return 0 unless $self->Queue; + + my $template = RT::Template->new( $self->CurrentUser ); + $template->LoadGlobalTemplate( $self->Name ); + return $template->id; +} + + =head2 MIMEObj Returns L object parsed using L method. Returns undef if last call to L failed or never be called. -Note that content of the template is UTF-8, but L is not -good at handling it and all data of the entity should be treated as -octets and converted to perl strings using Encode::decode_utf8 or -something else. +Note that content of the template is characters, but the contents of all +L objects (including the one returned by this function, +are bytes in UTF-8. =cut @@ -335,7 +406,7 @@ sub Parse { my ($rv, $msg); - if ($self->Content =~ m{^Content-Type:\s+text/html\b}im) { + if (not $self->IsEmpty and $self->Content =~ m{^Content-Type:\s+text/html\b}im) { local $RT::Transaction::PreferredContentType = 'text/html'; ($rv, $msg) = $self->_Parse(@_); } @@ -380,8 +451,8 @@ sub _Parse { ### Should we forgive normally-fatal errors? $parser->ignore_errors(1); - # MIME::Parser doesn't play well with perl strings - utf8::encode($content); + # Always provide bytes, not characters, to MIME objects + $content = Encode::encode( 'UTF-8', $content ); $self->{'MIMEObj'} = eval { $parser->parse_data( \$content ) }; if ( my $error = $@ || $parser->last_error ) { $RT::Logger->error( "$error" ); @@ -390,6 +461,7 @@ sub _Parse { # Unfold all headers $self->{'MIMEObj'}->head->unfold; + $self->{'MIMEObj'}->head->modify(1); return ( 1, $self->loc("Template parsed") ); @@ -415,9 +487,6 @@ sub _ParseContent { } my $content = $self->SUPER::_Value('Content'); - # We need to untaint the content of the template, since we'll be working - # with it - $content =~ s/^(.*)$/$1/; $args{'Ticket'} = delete $args{'TicketObj'} if $args{'TicketObj'}; $args{'Transaction'} = delete $args{'TransactionObj'} if $args{'TransactionObj'}; @@ -457,7 +526,7 @@ sub _ParseContentPerl { foreach my $key ( keys %{ $args{TemplateArgs} } ) { my $val = $args{TemplateArgs}{ $key }; next unless ref $val; - next if ref $val =~ /^(ARRAY|HASH|SCALAR|CODE)$/; + next if ref($val) =~ /^(ARRAY|HASH|SCALAR|CODE)$/; $args{TemplateArgs}{ $key } = \$val; } @@ -465,6 +534,12 @@ sub _ParseContentPerl { TYPE => 'STRING', SOURCE => $args{Content}, ); + my ($ok) = $template->compile; + unless ($ok) { + $RT::Logger->error("Template parsing error in @{[$self->Name]} (#@{[$self->id]}): $Text::Template::ERROR"); + return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ); + } + my $is_broken = 0; my $retval = $template->fill_in( HASH => $args{TemplateArgs}, @@ -561,7 +636,10 @@ sub _MassageSimpleTemplateArgs { my $cfs = $ticket->CustomFields; while (my $cf = $cfs->Next) { - $template_args->{"TicketCF" . $cf->Name} = $ticket->CustomFieldValuesAsString($cf->Name); + my $simple = $cf->Name; + $simple =~ s/\W//g; + $template_args->{"TicketCF" . $simple} + = $ticket->CustomFieldValuesAsString($cf->Name); } } @@ -572,7 +650,10 @@ sub _MassageSimpleTemplateArgs { my $cfs = $txn->CustomFields; while (my $cf = $cfs->Next) { - $template_args->{"TransactionCF" . $cf->Name} = $txn->CustomFieldValuesAsString($cf->Name); + my $simple = $cf->Name; + $simple =~ s/\W//g; + $template_args->{"TransactionCF" . $simple} + = $txn->CustomFieldValuesAsString($cf->Name); } } } @@ -587,23 +668,16 @@ sub _DowngradeFromHTML { $orig_entity->head->mime_attr( "Content-Type" => 'text/html' ); $orig_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' ); - $orig_entity->make_multipart('alternative', Force => 1); - require HTML::FormatText; - require HTML::TreeBuilder; - require Encode; - # need to decode_utf8, see the doc of MIMEObj method - my $tree = HTML::TreeBuilder->new_from_content( - Encode::decode_utf8($new_entity->bodyhandle->as_string) - ); - $new_entity->bodyhandle(MIME::Body::InCore->new( - \(scalar HTML::FormatText->new( - leftmargin => 0, - rightmargin => 78, - )->format( $tree )) - )); - $tree->delete; + my $body = $new_entity->bodyhandle->as_string; + $body = Encode::decode( "UTF-8", $body ); + my $html = RT::Interface::Email::ConvertHTMLToText( $body ); + $html = Encode::encode( "UTF-8", $html ); + return unless defined $html; + + $new_entity->bodyhandle(MIME::Body::InCore->new( \$html )); + $orig_entity->make_multipart('alternative', Force => 1); $orig_entity->add_part($new_entity, 0); # plain comes before html $self->{MIMEObj} = $orig_entity; @@ -621,6 +695,41 @@ sub CurrentUserHasQueueRight { return ( $self->QueueObj->CurrentUserHasRight(@_) ); } +=head2 SetQueue + +Changing queue is not implemented. + +=cut + +sub SetQueue { + my $self = shift; + return ( undef, $self->loc('Changing queue is not implemented') ); +} + +=head2 SetName + +Change name of the template. + +=cut + +sub SetName { + my $self = shift; + my $value = shift; + + return ( undef, $self->loc('Name is required') ) + unless $value; + + return $self->_Set( Field => 'Name', Value => $value ) + if lc($self->Name) eq lc($value); + + my $tmp = $self->new( RT->SystemUser ); + $tmp->LoadByCols( Name => $value, Queue => $self->Queue ); + return ( undef, $self->loc('A Template with that name already exists') ) + if $tmp->id; + + return $self->_Set( Field => 'Name', Value => $value ); +} + =head2 SetType If setting Type to Perl, require the ExecuteCode right. @@ -730,19 +839,20 @@ sub CompileCheck { sub CurrentUserCanRead { my $self =shift; - return 1 if $self->CurrentUserHasQueueRight('ShowTemplate'); - - return $self->CurrentUser->HasRight( Right =>'ShowGlobalTemplates', Object => $RT::System ) - if !$self->QueueObj->Id; + if ($self->__Value('Queue')) { + my $queue = RT::Queue->new( RT->SystemUser ); + $queue->Load( $self->__Value('Queue')); + return 1 if $self->CurrentUser->HasRight( Right => 'ShowTemplate', Object => $queue ); + } else { + return 1 if $self->CurrentUser->HasRight( Right => 'ShowGlobalTemplates', Object => $RT::System ); + return 1 if $self->CurrentUser->HasRight( Right => 'ShowTemplate', Object => $RT::System ); + } return; } 1; -use RT::Queue; -use base 'RT::Record'; - sub Table {'Templates'} @@ -785,10 +895,10 @@ Returns the Queue Object which has the id returned by Queue =cut sub QueueObj { - my $self = shift; - my $Queue = RT::Queue->new($self->CurrentUser); - $Queue->Load($self->__Value('Queue')); - return($Queue); + my $self = shift; + my $Queue = RT::Queue->new($self->CurrentUser); + $Queue->Load($self->__Value('Queue')); + return($Queue); } =head2 Name @@ -845,42 +955,6 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure. =cut -=head2 Language - -Returns the current value of Language. -(In the database, Language is stored as varchar(16).) - - - -=head2 SetLanguage VALUE - - -Set Language to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, Language will be stored as a varchar(16).) - - -=cut - - -=head2 TranslationOf - -Returns the current value of TranslationOf. -(In the database, TranslationOf is stored as int(11).) - - - -=head2 SetTranslationOf VALUE - - -Set TranslationOf to VALUE. -Returns (1, 'Status message') on success and (0, 'Error Message') on failure. -(In the database, TranslationOf will be stored as a int(11).) - - -=cut - - =head2 Content Returns the current value of Content. @@ -940,33 +1014,82 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Queue => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Name => - {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, Description => - {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Type => - {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, - Language => - {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, - TranslationOf => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, Content => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, LastUpdated => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + $deps->Add( out => $self->QueueObj ) if $self->QueueObj->Id; +} + +sub __DependsOn { + my $self = shift; + my %args = ( + Shredder => undef, + Dependencies => undef, + @_, + ); + my $deps = $args{'Dependencies'}; + my $list = []; + +# Scrips + push( @$list, $self->UsedBy ); + + $deps->_PushDependencies( + BaseObject => $self, + Flags => RT::Shredder::Constants::DEPENDS_ON, + TargetObjects => $list, + Shredder => $args{'Shredder'}, + ); + + return $self->SUPER::__DependsOn( %args ); +} + +sub PreInflate { + my $class = shift; + my ($importer, $uid, $data) = @_; + + $class->SUPER::PreInflate( $importer, $uid, $data ); + + my $obj = RT::Template->new( RT->SystemUser ); + if ($data->{Queue} == 0) { + $obj->LoadGlobalTemplate( $data->{Name} ); + } else { + $obj->LoadQueueTemplate( Queue => $data->{Queue}, Name => $data->{Name} ); + } + + if ($obj->Id) { + $importer->Resolve( $uid => ref($obj) => $obj->Id ); + return; + } + + return 1; +} + RT::Base->_ImportOverlays(); 1;