]> git.uio.no Git - usit-rt.git/blobdiff - lib/RT/Template.pm
Upgrade to 4.2.8
[usit-rt.git] / lib / RT / Template.pm
index fd4b511e9fefd7999df54859db8c983a0d7b9ec2..ac81e24b0dcc397dfa3bba20c3ce1b816feb73ce 100644 (file)
@@ -2,7 +2,7 @@
 #
 # COPYRIGHT:
 #
-# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
 #                                          <sales@bestpractical.com>
 #
 # (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<is
 ambiguous>. Several queues may have template with the same name
 and as well global template with the same name may exist.
-Use L</LoadGlobalTemplate> and/or L<LoadQueueTemplate> to get
+Use L</LoadByName>, L</LoadGlobalTemplate> or L<LoadQueueTemplate> 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</LoadByName>.
 
 =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<RT::Scrips> 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<MIME::Entity> object parsed using L</Parse> method. Returns
 undef if last call to L</Parse> failed or never be called.
 
-Note that content of the template is UTF-8, but L<MIME::Parser> 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<MIME::Entity> objects (including the one returned by this function,
+are bytes in UTF-8.
 
 =cut
 
@@ -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" );
@@ -466,6 +537,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},
@@ -562,7 +639,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);
         }
     }
 
@@ -573,7 +653,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);
         }
     }
 }
@@ -588,23 +671,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;
 
@@ -622,6 +698,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.
@@ -741,9 +852,6 @@ sub CurrentUserCanRead {
 
 1;
 
-use RT::Queue;
-use base 'RT::Record';
-
 sub Table {'Templates'}
 
 
@@ -786,10 +894,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
@@ -846,42 +954,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.
@@ -941,33 +1013,59 @@ 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 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;