+# uses Text::Template for Perl templates
+sub _ParseContentPerl {
+ my $self = shift;
+ my %args = (
+ Content => undef,
+ TemplateArgs => {},
+ @_,
+ );
+
+ foreach my $key ( keys %{ $args{TemplateArgs} } ) {
+ my $val = $args{TemplateArgs}{ $key };
+ next unless ref $val;
+ next if ref($val) =~ /^(ARRAY|HASH|SCALAR|CODE)$/;
+ $args{TemplateArgs}{ $key } = \$val;
+ }
+
+ my $template = Text::Template->new(
+ 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},
+ BROKEN => sub {
+ my (%args) = @_;
+ $RT::Logger->error("Template parsing error: $args{error}")
+ unless $args{error} =~ /^Died at /; # ignore intentional die()
+ $is_broken++;
+ return undef;
+ },
+ );
+ return ( undef, $self->loc('Template parsing error') ) if $is_broken;
+
+ return ($retval);
+}
+
+sub _ParseContentSimple {
+ my $self = shift;
+ my %args = (
+ Content => undef,
+ TemplateArgs => {},
+ @_,
+ );
+
+ $self->_MassageSimpleTemplateArgs(%args);
+
+ my $template = Text::Template->new(
+ TYPE => 'STRING',
+ SOURCE => $args{Content},
+ );
+ my ($ok) = $template->compile;
+ return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok;
+
+ # copied from Text::Template::fill_in and refactored to be simple variable
+ # interpolation
+ my $fi_r = '';
+ foreach my $fi_item (@{$template->{SOURCE}}) {
+ my ($fi_type, $fi_text, $fi_lineno) = @$fi_item;
+ if ($fi_type eq 'TEXT') {
+ $fi_r .= $fi_text;
+ } elsif ($fi_type eq 'PROG') {
+ my $fi_res;
+ my $original_fi_text = $fi_text;
+
+ # strip surrounding whitespace for simpler regexes
+ $fi_text =~ s/^\s+//;
+ $fi_text =~ s/\s+$//;
+
+ # if the codeblock is a simple $Variable lookup, use the value from
+ # the TemplateArgs hash...
+ if (my ($var) = $fi_text =~ /^\$(\w+)$/) {
+ if (exists $args{TemplateArgs}{$var}) {
+ $fi_res = $args{TemplateArgs}{$var};
+ }
+ }
+
+ # if there was no substitution then just reinsert the codeblock
+ if (!defined $fi_res) {
+ $fi_res = "{$original_fi_text}";
+ }
+
+ # If the value of the filled-in text really was undef,
+ # change it to an explicit empty string to avoid undefined
+ # value warnings later.
+ $fi_res = '' unless defined $fi_res;
+
+ $fi_r .= $fi_res;
+ }
+ }
+
+ return $fi_r;
+}
+
+sub _MassageSimpleTemplateArgs {
+ my $self = shift;
+ my %args = (
+ TemplateArgs => {},
+ @_,
+ );
+
+ my $template_args = $args{TemplateArgs};
+
+ if (my $ticket = $template_args->{Ticket}) {
+ for my $column (qw/Id Subject Type InitialPriority FinalPriority Priority TimeEstimated TimeWorked Status TimeLeft Told Starts Started Due Resolved RequestorAddresses AdminCcAddresses CcAddresses/) {
+ $template_args->{"Ticket".$column} = $ticket->$column;
+ }
+
+ $template_args->{"TicketQueueId"} = $ticket->Queue;
+ $template_args->{"TicketQueueName"} = $ticket->QueueObj->Name;
+
+ $template_args->{"TicketOwnerId"} = $ticket->Owner;
+ $template_args->{"TicketOwnerName"} = $ticket->OwnerObj->Name;
+ $template_args->{"TicketOwnerEmailAddress"} = $ticket->OwnerObj->EmailAddress;
+
+ my $cfs = $ticket->CustomFields;
+ while (my $cf = $cfs->Next) {
+ $template_args->{"TicketCF" . $cf->Name} = $ticket->CustomFieldValuesAsString($cf->Name);
+ }
+ }
+
+ if (my $txn = $template_args->{Transaction}) {
+ for my $column (qw/Id TimeTaken Type Field OldValue NewValue Data Content Subject Description BriefDescription/) {
+ $template_args->{"Transaction".$column} = $txn->$column;
+ }
+
+ my $cfs = $txn->CustomFields;
+ while (my $cf = $cfs->Next) {
+ $template_args->{"TransactionCF" . $cf->Name} = $txn->CustomFieldValuesAsString($cf->Name);
+ }
+ }
+}
+
+sub _DowngradeFromHTML {
+ my $self = shift;
+ my $orig_entity = $self->MIMEObj;
+
+ my $new_entity = $orig_entity->dup; # this will fail badly if we go away from InCore parsing
+ $new_entity->head->mime_attr( "Content-Type" => 'text/plain' );
+ $new_entity->head->mime_attr( "Content-Type.charset" => 'utf-8' );
+
+ $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;
+ # MIME objects are always bytes, not characters
+ my $tree = HTML::TreeBuilder->new_from_content(
+ Encode::decode( 'UTF-8', $new_entity->bodyhandle->as_string)
+ );
+ my $text = HTML::FormatText->new(
+ leftmargin => 0,
+ rightmargin => 78,
+ )->format( $tree );
+ $text = Encode::encode( "UTF-8", $text );
+
+ $new_entity->bodyhandle(MIME::Body::InCore->new( \$text ));
+ $tree->delete;
+
+ $orig_entity->add_part($new_entity, 0); # plain comes before html
+ $self->{MIMEObj} = $orig_entity;
+
+ return;
+}
+
+=head2 CurrentUserHasQueueRight
+
+Helper function to call the template's queue's CurrentUserHasQueueRight with the passed in args.
+
+=cut
+
+sub CurrentUserHasQueueRight {
+ my $self = shift;
+ return ( $self->QueueObj->CurrentUserHasRight(@_) );
+}
+
+=head2 SetType
+
+If setting Type to Perl, require the ExecuteCode right.
+
+=cut
+
+sub SetType {
+ my $self = shift;
+ my $NewType = shift;
+
+ if ($NewType eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
+ return ( undef, $self->loc('Permission Denied') );
+ }
+
+ return $self->_Set( Field => 'Type', Value => $NewType );
+}
+
+=head2 SetContent
+
+If changing content and the type is Perl, require the ExecuteCode right.
+
+=cut
+
+sub SetContent {
+ my $self = shift;
+ my $NewContent = shift;
+
+ if ($self->Type eq 'Perl' && !$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
+ return ( undef, $self->loc('Permission Denied') );
+ }
+
+ return $self->_Set( Field => 'Content', Value => $NewContent );
+}
+
+sub _UpdateAttributes {
+ my $self = shift;
+ my %args = (
+ NewValues => {},
+ @_,
+ );
+
+ my $type = $args{NewValues}{Type} || $self->Type;
+
+ # forbid updating content when the (possibly new) value of Type is Perl
+ if ($type eq 'Perl' && exists $args{NewValues}{Content}) {
+ if (!$self->CurrentUser->HasRight(Right => 'ExecuteCode', Object => $RT::System)) {
+ return $self->loc('Permission Denied');
+ }
+ }
+
+ return $self->SUPER::_UpdateAttributes(%args);
+}
+
+=head2 CompileCheck
+
+If the template's Type is Perl, then compile check all the codeblocks to see if
+they are syntactically valid. We eval them in a codeblock to avoid actually
+executing the code.
+
+Returns an (ok, message) pair.
+
+=cut
+
+sub CompileCheck {
+ my $self = shift;
+
+ return (1, $self->loc("Template does not include Perl code"))
+ unless $self->Type eq 'Perl';
+
+ my $content = $self->Content;
+ $content = '' if !defined($content);
+
+ my $template = Text::Template->new(
+ TYPE => 'STRING',
+ SOURCE => $content,
+ );
+ my ($ok) = $template->compile;
+ return ( undef, $self->loc('Template parsing error: [_1]', $Text::Template::ERROR) ) if !$ok;
+
+ # copied from Text::Template::fill_in and refactored to be compile checks
+ foreach my $fi_item (@{$template->{SOURCE}}) {
+ my ($fi_type, $fi_text, $fi_lineno) = @$fi_item;
+ next unless $fi_type eq 'PROG';
+
+ do {
+ no strict 'vars';
+ eval "sub { $fi_text }";
+ };
+ next if !$@;
+
+ my $error = $@;
+
+ # provide a (hopefully) useful line number for the error, but clean up
+ # all the other extraneous garbage
+ $error =~ s/\(eval \d+\) line (\d+).*/"template line " . ($1+$fi_lineno-1)/es;
+
+ return (0, $self->loc("Couldn't compile template codeblock '[_1]': [_2]", $fi_text, $error));
+ }
+
+ return (1, $self->loc("Template compiles"));
+}
+
+=head2 CurrentUserCanRead
+
+=cut
+
+sub CurrentUserCanRead {
+ my $self =shift;
+
+ 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'}
+
+
+
+
+
+
+=head2 id
+
+Returns the current value of id.