File Coverage

blib/lib/RapidApp/Template/Context.pm
Criterion Covered Total %
statement 33 54 61.1
branch 7 26 26.9
condition 1 2 50.0
subroutine 11 18 61.1
pod 0 9 0.0
total 52 109 47.7


line stmt bran cond sub pod time code
1             package RapidApp::Template::Context;
2 4     4   25 use strict;
  4         8  
  4         108  
3 4     4   19 use warnings;
  4         10  
  4         94  
4 4     4   1635 use autodie;
  4         43053  
  4         19  
5              
6 4     4   23686 use RapidApp::Util qw(:all);
  4         10  
  4         1872  
7 4     4   28 use Try::Tiny;
  4         10  
  4         204  
8              
9 4     4   23 use Moo;
  4         9  
  4         40  
10             extends 'Template::Context';
11              
12 4     4   10630 use namespace::clean;
  4         9  
  4         40  
13              
14             =pod
15              
16             =head1 DESCRIPTION
17              
18             Base Template Context class with extended API for post-process parsing (i.e. markup rendering)
19             and template div wrapping for attaching metadata. Extends L<Template::Context>.
20             Designed specifically to work with RapidApp::Template::Controller.
21              
22             =cut
23              
24             # The RapidApp::Template::Controller instance
25             has 'Controller', is => 'ro', required => 1;
26              
27 0     0 0 0 sub catalyst_context { (shift)->Controller->{_current_context} }
28              
29             # The RapidApp::Template::Access instance:
30             # We need to be able to check certain template permissions for special markup
31             # Actual permission checks happen in the RapidApp::Template::Controller
32             has 'Access', is => 'ro', required => 1;
33              
34             sub get_Provider {
35 0     0 0 0 my $self = shift;
36 0         0 return $self->{LOAD_TEMPLATES}->[0];
37             }
38              
39             sub div_wrap {
40 2     2 0 4 my ($self,$template) = @_;
41 2 50       23 return 0 unless $self->Controller->{_div_wrap}; #<-- localized in RapidApp::Template::Controller
42 0         0 return $self->Access->template_writable($template);
43             }
44              
45 2 100   2 0 3 sub process_template_stack { @{(shift)->{_process_template_stack} || []} }
  2         20  
46 0     0 0 0 sub process_nest_level { scalar( (shift)->process_template_stack ) }
47              
48             around 'process' => sub {
49             my ($orig, $self, @args) = @_;
50            
51             local $self->Access->{_process_Context} = $self;
52            
53             # This is probably a Template::Document object:
54             my $template = blessed $args[0] ? $args[0]->name : $args[0];
55             $template = $self->Controller->_resolve_template_name($template);
56            
57             # Localize to track a stack of templates, when templates include other templates:
58             local $self->{_process_template_stack} = [ $template, $self->process_template_stack ];
59            
60             my $output;
61             try {
62             if($self->Access->template_static_tpl($template) && $self->get_Provider->template_exists($template)) {
63             # If this is a 'static' template, do not process it through TT,
64             # instead return the raw template content directly
65             ($output) = $self->get_Provider->load($template);
66             }
67             else {
68             $output = $self->$orig(@args);
69             }
70            
71             $output = $self->post_process_output($template,\$output);
72             }
73             catch {
74             my $err = shift;
75            
76             # Rethrow if this flag is set. This is needed specifically for
77             # the special _get_template_error() case in RapidApp::Template::Controller
78             die $err if ($self->Controller->{_no_exception_error_content});
79            
80             $output = $self->_template_error_content(
81             $template, $err,
82             $self->Access->template_writable($template)
83             );
84             };
85            
86             return $output;
87             };
88              
89              
90             sub get_template_post_processor {
91 2     2 0 6 my ($self, $template) = @_;
92            
93 2 50       9 if(my $class = $self->Access->template_post_processor_class($template)) {
94 0 0       0 unless ($self->{_loaded_post_processor_classes}{$class}) {
95 0         0 Module::Runtime::require_module($class);
96 0 0       0 $class->can('process') or die "FATAL: Postprocessor '$class' has no 'process' method!";
97 0         0 $self->{_loaded_post_processor_classes}{$class}++;
98             }
99 0         0 return $class
100             }
101             return undef
102 2         10 }
103              
104 0     0 0 0 sub next_template { ((shift)->process_template_stack)[1] }
105              
106              
107             # Returns the post-processor class of the *next* template (i.e. the second element)
108             # in the process_template_stack. This is intended to be called from a post-processor
109             # so it can apply logic according to the template it is nested within. For example,
110             # if a markdown template includes another markdown template, we only want to convert
111             # the markdown when post-processing the
112             sub next_template_post_processor {
113 0     0 0 0 my $self = shift;
114            
115 0 0       0 my $next_template = $self->next_template or return undef;
116 0         0 $self->get_template_post_processor($next_template)
117             }
118              
119              
120             # New/extended API:
121             sub post_process_output {
122 2     2 0 6 my ($self, $template, $output_ref) = @_;
123              
124 2   50     31 my $meta = $self->{_cached_template_metadata}{$template} //= {
125             format => $self->Access->get_template_format($template),
126             postprocessor => $self->get_template_post_processor($template)
127             };
128            
129 2 50       5 die "Access object didn't return a format string" unless ($meta->{format});
130            
131 2 50       6 $$output_ref = $meta->{postprocessor}->process($output_ref,$self) if ($meta->{postprocessor});
132            
133             return $self->div_wrap($template)
134 2 50       6 ? $self->_div_wrap_content($template,$meta->{format},$$output_ref)
135             : $$output_ref;
136             }
137              
138              
139             sub _div_wrap_content {
140 0     0     my ($self, $template, $format, $content) = @_;
141            
142 0           my $exists = $self->get_Provider->template_exists($template);
143 0 0         my $meta = {
144             name => $template,
145             format => $format,
146             deletable => $exists ? $self->Access->template_deletable($template) : 0
147             };
148            
149 0 0         join("\n",
150             '<div class="ra-template">',
151            
152             '<div class="meta" style="display:none;">',
153             #'<div class="template-name">' . $template . '</div>',
154             #'<div class="template-format">' . $format . '</div>',
155             encode_json_utf8($meta),
156             '</div>',
157            
158             (
159             $exists ?
160             '<div title="Edit \'' . $template . '\'" class="edit ra-icon-edit-pictogram"></div>' :
161             ''
162             ),
163            
164             '<div class="content">', $content, '</div>',
165            
166             '</div>'
167             );
168             }
169              
170             sub _template_error_content {
171 0     0     my ($self, $template, $error, $editable) = @_;
172            
173             # Editable override: don't allow edit unless the actual request is
174             # in a editable context. This is a bit ugly as it violates the
175             # separation of concerns, but this needs to be here to support
176             # nested template errors
177 0 0         $editable = 0 unless (
178             $self->Controller->is_editable_request($self->catalyst_context)
179             );
180            
181 0 0         join("\n",
182             '<div class="ra-template">',
183            
184             '<div class="meta" style="display:none;">',
185             #'<div class="template-name">', $template, '</div>',
186             encode_json_utf8({
187             name => $template,
188             format => $self->Access->get_template_format($template),
189             deletable => $self->Access->template_deletable($template)
190             }),
191             '</div>',
192            
193             ( $editable
194             ? '<div title="Edit \'' . $template . '\'" class="edit ra-icon-edit-pictogram"></div>'
195             : ''
196             ),
197            
198             '<div class="tpl-error">',
199             'Template error &nbsp;&ndash; <span class="tpl-name">' . $template . '</span>',
200             '<div class="error-msg">',$error,'</div>',
201             '</div>',
202            
203             '</div>'
204             );
205             }
206              
207              
208             ##################################################
209             # -- NEEDED FOR SECURITY FOR NON-PRIV USERS --
210             # DISABLE ALL PLUGINS AND FILTERS EXCEPT THOSE
211             # SPECIFICALLY CONFIGURED TO BE ALLOWED
212             has '_allowed_plugins_hash', default => sub {
213             my $self = shift;
214             return { map {lc($_)=>1} @{$self->Controller->allowed_plugins} };
215             }, is => 'ro', lazy => 1;
216              
217             has '_allowed_filters_hash', default => sub {
218             my $self = shift;
219             return { map {lc($_)=>1} @{$self->Controller->allowed_filters} };
220             }, is => 'ro', lazy => 1;
221              
222             around 'plugin' => sub {
223             my ($orig, $self, $name, @args) = @_;
224            
225             return $self->throw(
226             Template::Constants::ERROR_PLUGIN,
227             "USE '$name' - permission denied"
228             ) unless ($self->_allowed_plugins_hash->{lc($name)});
229            
230             return $self->$orig($name,@args);
231             };
232              
233             around 'filter' => sub {
234             my ($orig, $self, $name, @args) = @_;
235            
236             return $self->throw(
237             Template::Constants::ERROR_FILTER,
238             "Load Filter '$name' - permission denied"
239             ) unless ($self->_allowed_filters_hash->{lc($name)});
240            
241             return $self->$orig($name,@args);
242             };
243             ##################################################
244              
245              
246              
247             1;