File Coverage

blib/lib/RapidApp/Template/Controller.pm
Criterion Covered Total %
statement 114 278 41.0
branch 11 124 8.8
condition 4 69 5.8
subroutine 30 51 58.8
pod 0 18 0.0
total 159 540 29.4


line stmt bran cond sub pod time code
1             package RapidApp::Template::Controller;
2 4     4   2799 use strict;
  4         9  
  4         118  
3 4     4   18 use warnings;
  4         11  
  4         129  
4              
5 4     4   22 use RapidApp::Util qw(:all);
  4         7  
  4         1867  
6 4     4   27 use Try::Tiny;
  4         8  
  4         199  
7 4     4   27 use Template;
  4         7  
  4         136  
8 4     4   41 use Module::Runtime;
  4         9  
  4         29  
9 4     4   169 use Path::Class qw(file dir);
  4         9  
  4         203  
10 4     4   25 use URI::Escape;
  4         8  
  4         192  
11              
12             # New unified controller for displaying and editing TT templates on a site-wide
13             # basis. This is an experiment that breaks with the previous RapidApp 'Module'
14             # design. It also is breaking away from DataStore2 for editing in order to support
15             # nested templates (i.e. tree structure instead of table/row structure)
16              
17 4     4   22 use Moose;
  4         8  
  4         29  
18             with 'RapidApp::Role::AuthController';
19 4     4   22809 use namespace::autoclean;
  4         8  
  4         33  
20              
21 4     4   280 BEGIN { extends 'Catalyst::Controller' }
22              
23 4     4   32224 use RapidApp::Template::Context;
  4         13  
  4         123  
24 4     4   2253 use RapidApp::Template::Provider;
  4         15  
  4         154  
25 4     4   76 use RapidApp::Template::Access;
  4         13  
  4         5241  
26              
27             ## -----
28             ## Setup Top-level alias URL paths:
29             has 'read_alias_path', is => 'ro', default => '/tpl';
30             has 'edit_alias_path', is => 'ro', default => '/tple';
31              
32             sub BUILD {
33 4     4 0 9773 my $self = shift;
34            
35 4         29 my $c = $self->_app;
36 4         151 my $ns = $self->action_namespace($c);
37            
38 4 50       1140 if($self->read_alias_path) {
39 4         113 my $path = $self->read_alias_path;
40 4         19 $path =~ s/^\///;
41              
42 4         37 $c->dispatcher->register( $c => Catalyst::Action->new({
43             name => 'tpl',
44             code => $self->can('tpl'),
45             class => $self->meta->name,
46             namespace => $ns,
47             reverse => join('/',$ns,'tpl'),
48             attributes => {
49             Chained => ['/'],
50             PathPrefix => [''],
51             }
52             }));
53             }
54            
55 4 50       207930 if($self->edit_alias_path) {
56 4         100 my $path = $self->edit_alias_path;
57 4         21 $path =~ s/^\///;
58 4         45 $c->dispatcher->register( $c => Catalyst::Action->new({
59             name => 'tple',
60             code => $self->can('tple'),
61             class => $self->meta->name,
62             namespace => $ns,
63             reverse => join('/',$ns,'tple'),
64             attributes => {
65             Chained => ['/'],
66             PathPrefix => [''],
67             }
68             }));
69             }
70             }
71              
72              
73              
74             sub tpl_path {
75 0     0 0 0 my $self = shift;
76             # Return the edit alias patgh first or fall back to the read alias:
77 0   0     0 return $self->edit_alias_path || $self->read_alias_path;
78             }
79             ## -----
80              
81              
82             has 'context_class', is => 'ro', default => 'RapidApp::Template::Context';
83             has 'provider_class', is => 'ro', default => 'RapidApp::Template::Provider';
84             has 'access_class', is => 'ro', default => 'RapidApp::Template::Access';
85             has 'access_params', is => 'ro', isa => 'HashRef', default => sub {{}};
86             has 'include_paths', is => 'ro', isa => 'ArrayRef[Str]', default => sub {[]};
87             has 'store_class', is => 'ro', default => sub {undef}; # Will be 'RapidApp::Template::Store' if undef
88             has 'store_params', is => 'ro', default => sub {undef};
89              
90              
91             # --
92             # Only these TT plugins and filters will be allowed in Templates
93             # This is *very* important for security if non-admin users will
94             # have access to modify templates. These templates and filters
95             # are from the built-in list and are known to be safe. Examples
96             # of unsafe plugins are things like 'Datafile' and 'DBI', and
97             # examples of unsafe filters are things like 'eval', 'redirect', etc.
98             # It is critical that these types of things are never exposed
99             # to web interfaces. It is also important to note that TT was
100             # not originally designed with "limited" access in mind - it was
101             # only meant to be used by the programmer, not users. In hindsight,
102             # it might have been better to go with Text::Xslate for this reason
103             #
104             # (note: these get accessed/used within RapidApp::Template::Context)
105             has 'allowed_plugins', is => 'ro', default => sub {[qw(
106             Assert Date Dumper Format HTML Iterator
107             Scalar String Table URL Wrap
108             )]}, isa => 'ArrayRef';
109              
110             has 'allowed_filters', is => 'ro', default => sub {[qw(
111             format upper lower ucfirst lcfirst trim collapse
112             html html_entity xml html_para html_break
113             html_para_break html_line_break uri url indent
114             truncate repeat remove replace null
115             )]}, isa => 'ArrayRef';
116             # --
117              
118             has 'default_template_extension', is => 'ro', isa => 'Maybe[Str]', default => 'tt';
119              
120             # If true, mouse-over edit controls will always be available for editable
121             # templates. Otherwise, query string ?editable=1 is required. Note that
122             # editable controls are *only* available in the context of an AutoPanel tab
123             has 'auto_editable', is => 'ro', isa => 'Bool', default => 0;
124              
125             has 'Access', is => 'ro', lazy => 1, default => sub {
126             my $self = shift;
127             Module::Runtime::require_module($self->access_class);
128             return $self->access_class->new({
129             %{ $self->access_params },
130             Controller => $self
131             });
132             }, isa => 'RapidApp::Template::Access';
133              
134             # Maintain two separate Template instances - one that wraps divs and one that
135             # doesn't. Can't use the same one because compiled templates are cached
136             has 'Template_raw', is => 'ro', lazy => 1, default => sub {
137             my $self = shift;
138             return $self->_new_Template({ div_wrap => 0 });
139             }, isa => 'Template';
140              
141             has 'Template_wrap', is => 'ro', lazy => 1, default => sub {
142             my $self = shift;
143             return $self->_new_Template({ div_wrap => 1 });
144             }, isa => 'Template';
145              
146             sub _new_Template {
147 1     1   4 my ($self,$opt) = @_;
148 1         36 Module::Runtime::require_module($self->context_class);
149 1         69 Module::Runtime::require_module($self->provider_class);
150             return Template->new({
151             CONTEXT => $self->context_class->new({
152             Controller => $self,
153             Access => $self->Access,
154             # TODO: turn STRICT back on once I figure out how to make the errors useful:
155             #STRICT => 1,
156             LOAD_TEMPLATES => [
157             $self->provider_class->new({
158             Controller => $self,
159             Access => $self->Access,
160             store_class => $self->store_class,
161             store_params => $self->store_params,
162             #INCLUDE_PATH => $self->_app->default_tt_include_path,
163             INCLUDE_PATH => [
164 2         271 map { dir($_)->stringify } @{ $self->include_paths },
  1         28  
165             dir($self->_app->config->{home},'root/templates')->stringify,
166             dir(RapidApp->share_dir,'templates')->stringify
167             ],
168             CACHE_SIZE => 64,
169 1 50       44 %{ $opt || {} }
  1         61  
170             })
171             ]
172             })
173             })
174             }
175              
176             # hook for RapidApp::Role::AuthController:
177             # abort enforce_session for 'external' templates being accessed externally:
178             around 'enforce_rapidapp_session' => sub {
179             my ( $orig, $self, $c, @args ) = @_;
180             my $template = join('/',@{$c->req->args});
181             return $self->$orig($c,@args) unless (
182             ! $c->req->header('X-RapidApp-RequestContentType')
183             && $self->is_external_template($c,$template)
184             );
185             };
186              
187             sub get_Provider {
188 1     1 0 4 my $self = shift;
189 1         35 return $self->Template_raw->context->{LOAD_TEMPLATES}->[0];
190             }
191              
192             # request lifetime cached:
193             sub _template_exists {
194 0     0   0 my ($self, $c, $template) = @_;
195 0 0       0 die "missing template arg" unless ($template);
196             $c->stash->{_template_exists}{$template} =
197             $self->get_Provider->template_exists($template)
198 0 0       0 unless (exists $c->stash->{_template_exists}{$template});
199 0         0 return $c->stash->{_template_exists}{$template};
200             }
201              
202              
203             # Checks if the editable toggle/switch is on for this request. Note that
204             # this has *nothing* to do with actual editability of a given template,
205             # just whether or not edit controls should be available for templates that
206             # are allowed to be edited
207             sub is_editable_request {
208 0     0 0 0 my ($self, $c) = @_;
209            
210             # Never editable externally, unless this is an iframe request
211             return 0 unless (
212             $c->req->header('X-RapidApp-RequestContentType') ||
213 0 0 0     0 (exists $c->req->params->{iframe} && $c->req->params->{iframe} eq 'request')
      0        
214             );
215            
216             # check several mechanisms to turn on editing (mouse-over edit controls)
217             return (
218             $self->auto_editable ||
219             $c->req->params->{editable} ||
220             $c->req->params->{edit} ||
221             $c->stash->{editable}
222 0   0     0 );
223             }
224              
225             sub is_iframe_request {
226 0     0 0 0 my ($self, $c) = @_;
227            
228             return (
229             $c->req->params->{iframe} ||
230             $c->stash->{iframe}
231 0   0     0 );
232             }
233              
234             # request lifetime cached:
235             sub is_external_template {
236 0     0 0 0 my ($self, $c, $template) = @_;
237 0 0       0 die "missing template arg" unless ($template);
238            
239             $c->stash->{is_external_template}{$template} = do {
240             # Allow params/stash override:
241 0 0       0 return $c->req->params->{external} if (exists $c->req->params->{external});
242 0 0       0 return $c->stash->{external} if (exists $c->stash->{external});
243            
244 0 0 0     0 my $external = (
245             # hard-coded external templates:
246             $template =~ /^rapidapp\/public\// ||
247             $self->Access->template_external_tpl($template)
248             ) ? 1 : 0;
249              
250             return (
251 0 0 0     0 $external &&
252             # don't treat non-existing templates as external
253             $self->_template_exists($c,$template)
254             ) ? 1 : 0;
255 0 0       0 } unless (exists $c->stash->{is_external_template}{$template});
256            
257 0         0 return $c->stash->{is_external_template}{$template};
258             }
259              
260              
261             sub _resolve_template_name {
262 4     4   12 my ($self, @args) = @_;
263 4 50       13 return undef unless (defined $args[0]);
264 4         16 my $template = join('/',@args);
265            
266 4 50 33     124 $template .= '.' . $self->default_template_extension if (
267             $self->default_template_extension &&
268             ! ( $template =~ /\./ ) #<-- doesn't contain a dot '.'
269             );
270            
271 4         14 return $template;
272             }
273              
274 4     4 0 33 sub base :Chained :PathPrefix :CaptureArgs(0) {}
  4     0   8  
  4         39  
275              
276             # -------
277             # direct/navable dispatch to match the same pattern as Module DirectCmp
278             #
279             # We now have 'direct' and 'navable' controller actions to that the following
280             # URL paths will behave is a consistent manner:
281             #
282             # /rapidapp/module/direct/<module_path>
283             # /rapidapp/template/direct/tple/<template_path>
284             #
285             # We are simulating module viewport content with a simple autopanel cfg so
286             # that a template can be rendered full-screen with the same functionality
287             # as it has when rendered in a TabGui tab
288             sub _validate_args_template_viewable {
289 0     0   0 my ($self, @args) = @_;
290 0         0 my $template = $self->_resolve_template_name(@args);
291 0         0 $self->get_Provider->template_exists($template)
292             }
293              
294             sub _validate_enforce_arguments_path {
295 0     0   0 my ($self, $c, @args) = @_;
296            
297 0 0 0     0 shift @args if ($args[0] eq 'tpl' || $args[0] eq 'tple');
298 0 0       0 return 1 if ($self->_validate_args_template_viewable(@args));
299            
300 0         0 my @pre_args = ();
301 0         0 while(scalar(@args) > 1) {
302            
303 0         0 push @pre_args, shift @args;
304            
305             # Special handling for relative requests to special/reserved controller paths.
306             # this is based on the same logic/rules as in RapidApp::Module
307 0 0 0     0 $c->redispatch_public_path($c->mount_url,@args) && $c->detach if (
      0        
      0        
308             $self->_validate_args_template_viewable(@pre_args) && (
309             $args[0] eq 'simplecas'
310             || $args[0] eq 'assets'
311             || $args[0] eq 'rapidapp'
312             )
313             );
314             }
315            
316 0         0 $c->stash->{template} = 'rapidapp/http-404.html';
317 0         0 $c->stash->{current_view} = 'RapidApp::Template';
318 0         0 $c->res->status(404);
319 0         0 return $c->detach;
320             }
321              
322             sub direct :Chained('base') :Args {
323 0     0 0 0 my ($self, $c, @args) = @_;
324            
325 0         0 $self->_validate_enforce_arguments_path($c,@args);
326            
327             $c->stash->{panel_cfg} = {
328 0         0 xtype => 'autopanel',
329             layout => 'fit',
330             autoLoad => {
331             url => join('/','',@args),
332             params => $c->req->params
333             }
334             };
335            
336 0         0 return $c->detach( $c->view('RapidApp::Viewport') );
337 4     4   6305 }
  4         10  
  4         20  
338              
339             sub navable :Chained('base') :Args {
340 0     0 0 0 my ($self, $c, @args) = @_;
341 0         0 $self->_validate_enforce_arguments_path($c,@args);
342 0         0 return $c->controller('RapidApp::Module')->navable($c,@args);
343 4     4   3485 }
  4         9  
  4         32  
344             # --------
345              
346             sub tpl :Chained('base') :Args {
347 0     0 0 0 my ($self, $c, @args) = @_;
348 0         0 $self->view($c,@args)
349 4     4   3411 }
  4         10  
  4         16  
350              
351             # Edit alias
352             sub tple :Chained('base') :Args {
353 0     0 0 0 my ($self, $c, @args) = @_;
354            
355 0         0 $c->stash->{editable} = 1;
356 0         0 $self->view($c,@args)
357 4     4   3428 }
  4         13  
  4         17  
358              
359             # TODO: see about rendering with Catalyst::View::TT or a custom View
360             sub view :Chained('base') :Args {
361 0     0 0 0 my ($self, $c, @args) = @_;
362 0 0       0 my $template = $self->_resolve_template_name(@args)
363             or die "No template specified";
364            
365 0         0 local $self->Access->{_local_cache} = {};
366            
367 0 0       0 if(my $psgi_response = $self->Access->template_psgi_response($template,$c)) {
368 0         0 $c->res->from_psgi_response( $psgi_response );
369 0         0 return $c->detach;
370             }
371              
372 0         0 my $ra_client = $c->is_ra_ajax_req;
373              
374             # Honor the existing status, if set, except for Ajax requests
375 0   0     0 my $status = $c->res->status || 200;
376 0 0       0 $status = 200 if ($ra_client);
377              
378 0         0 local $self->{_current_context} = $c;
379            
380             # Track the top-level template that is being viewed, in case the Access class
381             # wants to treat top-level templates differently from nested templates
382             # -- see currently_viewing_template() in RapidApp::Template::Access
383 0         0 local $self->{_viewing_template} = $template;
384            
385 0 0       0 $self->Access->template_viewable($template)
386             or die "Permission denied - template '$template'";
387              
388 0         0 my $external = $self->is_external_template($c,$template);
389 0         0 my $editable = $self->is_editable_request($c);
390            
391             # ---
392             # New: for non-external templates which are being accessed externally,
393             # (i.e. directly from browser) redirect to internal hashnav path:
394 0 0 0     0 unless ($external || $ra_client) {
395 0         0 return $c->auto_hashnav_redirect_current;
396             }
397             #---
398            
399 0   0     0 my $iframe = $external || $self->is_iframe_request($c); # <-- external must use iframe
400 0         0 my ($output,$content_type);
401            
402 0         0 my @cls = ('ra-scoped-reset');
403 0         0 my $tpl_cls = $self->Access->template_css_class($template);
404 0 0       0 push @cls, $tpl_cls if ($tpl_cls);
405            
406 0 0       0 if($ra_client) {
407             # This is a call from within ExtJS, wrap divs to id the templates from javascript
408            
409 0         0 my $cnf = {};
410            
411 0 0       0 if($iframe) {
412             # This is an iframe request. Build an iframe panel which will make the request
413             # again but without the X-RapidApp-RequestContentType header which will be
414             # handled as a direct browser request (see logic further down)
415            
416 0         0 my %params = ( %{$c->req->params}, editable => $editable, iframe => 'request' );
  0         0  
417 0         0 my $qs = join('&',map { $_ . '=' . uri_escape($params{$_}) } keys %params);
  0         0  
418 0         0 my $iframe_src = join('/','',$self->action_namespace($c),'view',$template) . '?' . $qs;
419              
420 0         0 $cnf = {
421             xtype => 'iframepanel',
422             plugins => ['ra-link-click-catcher'],
423             tabTitle => join('',
424             '<span style="color:purple;">',
425             '[' . join('/',@args) . ']', #<-- not using $template to preserve the orig req name
426             '</span>'
427             ),
428             tabIconCls => 'ra-icon-page-white',
429             style => 'top: 0; left: 0; bottom: 0; right: 0;',
430             autoScroll => \1,
431             bodyStyle => 'border: 1px solid #D0D0D0;background-color:white;',
432             loadMask => \1,
433             defaultSrc => $iframe_src
434             };
435             }
436             else {
437            
438 0 0       0 my $html = $self->_render_template(
439             $editable ? 'Template_wrap' : 'Template_raw',
440             $template, $c
441             );
442            
443             $cnf = {
444             xtype => 'panel',
445             autoScroll => \1,
446             bodyCssClass => join(' ',@cls),
447            
448             # try to set the title/icon by finding/parsing <title> in the 'html'
449             autopanel_parse_title => \1,
450            
451             # These will only be the title/icon if there is no parsable <title>
452             tabTitle => join('/',@args), #<-- not using $template to preserve the orig req name
453             tabIconCls => 'ra-icon-page-white-world',
454            
455             html => $html,
456            
457             # Load any extra, template-specific configs from the Access class:
458 0 0       0 %{ $self->Access->template_autopanel_cnf($template) || {} }
  0         0  
459             };
460             }
461            
462             # No reason to load the plugin unless we're editable:
463 0 0       0 if ($editable) {
464 0   0     0 $cnf->{plugins} ||= [];
465 0         0 push @{$cnf->{plugins}}, 'template-controller-panel';
  0         0  
466 0         0 $cnf->{template_controller_url} = '/' . $self->action_namespace($c);
467             }
468            
469             # This is doing the same thing that the overly complex 'Module' controller does:
470 0         0 $content_type = 'text/javascript; charset=utf-8';
471 0         0 $output = encode_json_utf8($cnf);
472             }
473             else {
474             # This is a direct browser call:
475            
476 0 0       0 my $html = $self->_render_template(
477             $editable ? 'Template_wrap' : 'Template_raw',
478             $template, $c
479             );
480            
481 0         0 my @head = ();
482            
483             # If we're in an iframe tab, we want to make sure we set the base target
484             # to prevent the chance of trying to load a link inside the frame (even
485             # though local links are already hanlded/converted - we still need to
486             # protect against external/global links).
487             push @head, '<base target="_blank" />' if (
488             exists $c->req->params->{iframe} &&
489 0 0 0     0 $c->req->params->{iframe} eq 'request'
490             );
491            
492 0 0       0 if($external) {
493            
494             # Ask the Access class for custom headers for this external template, and
495             # if it has them, set them in the response object now. And pull out $content_type
496             # if this operation set it (i.e. the template provides its own Content-Type)
497 0 0       0 my $headers = $external ? $self->Access->get_external_tpl_headers($template) : undef;
498 0 0       0 if($headers) {
499 0         0 $c->res->header( $_ => $headers->{$_} ) for (keys %$headers);
500 0         0 $content_type = $c->res->content_type;
501             }
502            
503             # If this external template provides its own headers, including Content-Type, and that is
504             # *not* text/html, don't populate @head, even if it is $editable (which is rare - or maybe
505             # even impossible - here anyway)
506 0 0 0     0 unless($content_type && ! ($content_type =~ /^text\/html/)){
507             # If we're editable and external we need to include CSS for template edit controls:
508             # TODO: basically including everything but ExtJS CSS. This is ugly and should
509             # be generalized/available in the Asset system as a simpler function call:
510 0 0 0     0 push @head, (
511             $c->favicon_head_tag||'',
512             $c->controller('Assets::RapidApp::CSS')->html_head_tags,
513             $c->controller('Assets::RapidApp::Icons')->html_head_tags,
514             $c->controller('Assets::ExtJS')->html_head_tags( js => [
515             'adapter/ext/ext-base.js',
516             'ext-all-debug.js',
517             'src/debug.js'
518             ]),
519             $c->controller('Assets::RapidApp::JS')->html_head_tags
520             ) if $editable; # $editable is rarely true here, so @header will be empty here anyway
521             }
522             }
523             else {
524             # Include all the ExtJS, RapidApp and local app CSS/JS
525 0         0 push @head, $c->all_html_head_tags;
526             }
527            
528             # Only include the RapidApp/ExtJS assets and wrap 'ra-scoped-reset' if
529             # this is *not* an external template. If it is an external template,
530             # ignore @head entirely if its empty:
531 0 0       0 $output = $external
    0          
532             ? ( scalar(@head) == 0 ? $html : join("\n",@head,$html) )
533             : join("\n",
534             '<head>', @head, '</head>',
535             '<div class="' . join(' ',@cls) . '">', $html, '</div>'
536             )
537             ;
538            
539 0   0     0 $content_type ||= 'text/html; charset=utf-8';
540             }
541            
542             # Decode as UTF-8 for user consumption:
543 0         0 utf8::decode($output); # FIXME?? This probably shouldn't be always called anymore
544              
545 0         0 return $self->_detach_response($c,$status,$output,$content_type);
546 4     4   5951 }
  4         10  
  4         20  
547              
548              
549             # Read (not compiled/rendered) raw templates:
550             sub get :Chained('base') :Args {
551 0     0 0 0 my ($self, $c, @args) = @_;
552 0 0       0 my $template = $self->_resolve_template_name(@args)
553             or die "No template specified";
554            
555 0         0 local $self->{_current_context} = $c;
556            
557 0 0       0 $self->Access->template_readable($template)
558             or return $self->_detach_response($c,403,"Permission denied - template '$template'");
559            
560 0         0 my ($data, $error) = $self->get_Provider->load($template);
561            
562 0 0       0 return $self->_detach_response($c,400,"Failed to get template '$template'")
563             unless (defined $data);
564            
565             # Decode as UTF-8 for user consumption:
566 0         0 utf8::decode($data);
567            
568 0         0 return $self->_detach_response($c,200,$data);
569 4     4   3963 }
  4         10  
  4         21  
570              
571             # Update raw templates:
572             sub set :Chained('base') :Args {
573 0     0 0 0 my ($self, $c, @args) = @_;
574 0 0       0 my $template = $self->_resolve_template_name(@args)
575             or die "No template specified";
576            
577 0         0 local $self->{_current_context} = $c;
578            
579             exists $c->req->params->{content}
580 0 0       0 or return $self->_detach_response($c,400,"Template 'content' required");
581            
582 0 0       0 $self->Access->template_writable($template)
583             or return $self->_detach_response($c,403,"Modify template '$template' - Permission denied");
584            
585 0         0 my $content = $c->req->params->{content};
586            
587             # Special status 418 means the supplied content is a bad template
588 0 0       0 unless ($c->req->params->{skip_validate}) {
589 0         0 my $err = $self->_get_template_error('Template_raw',\$content,$c);
590 0 0       0 return $self->_detach_response($c,418,$err) if ($err);
591             }
592            
593             # Encode the template content in UTF-8
594 0         0 utf8::encode($content);
595            
596 0         0 $self->get_Provider->update_template($template,$content);
597            
598 0         0 return $self->_detach_response($c,200,'Template Updated');
599 4     4   3914 }
  4         13  
  4         21  
600              
601             sub create :Chained('base') :Args {
602 0     0 0 0 my ($self, $c, @args) = @_;
603 0 0       0 my $template = $self->_resolve_template_name(@args)
604             or die "No template specified";
605            
606 0         0 local $self->{_current_context} = $c;
607            
608 0 0       0 $self->Access->template_creatable($template)
609             or return $self->_detach_response($c,403,"Create template '$template' - Permission denied");
610            
611 0 0       0 die "Create template '$template' - already exists"
612             if $self->_template_exists($c,$template);
613            
614 0 0       0 $self->get_Provider->create_template($template)
615             or die "Failed to create template '$template'";
616              
617 0         0 return $self->_detach_response($c,200,"Created template '$template'");
618 4     4   3798 }
  4         9  
  4         17  
619              
620             sub delete :Chained('base') :Args {
621 0     0 0 0 my ($self, $c, @args) = @_;
622 0 0       0 my $template = $self->_resolve_template_name(@args)
623             or die "No template specified";
624            
625 0         0 local $self->{_current_context} = $c;
626            
627 0 0       0 $self->Access->template_deletable($template)
628             or return $self->_detach_response($c,403,"Delete template '$template' - Permission denied");
629            
630 0 0       0 die "Delete template '$template' - doesn't exists"
631             unless $self->_template_exists($c,$template);;
632            
633 0 0       0 $self->get_Provider->delete_template($template)
634             or die "Failed to delete template '$template'";
635              
636 0         0 return $self->_detach_response($c,200,"Deleted template '$template'");
637 4     4   3702 }
  4         10  
  4         17  
638              
639             sub _detach_response {
640 0     0   0 my ($self, $c, $status, $body, $content_type) = @_;
641 0   0     0 $content_type ||= 'text/plain; charset=utf-8';
642 0         0 $c->response->content_type($content_type);
643 0         0 $c->response->status($status);
644 0         0 $c->response->body($body);
645 0         0 return $c->detach;
646             }
647              
648             sub _render_template {
649 0     0   0 my ($self, $meth, $template, $c) = @_;
650            
651 0         0 my $TT = $self->$meth;
652 0         0 local $self->{_current_context} = $c;
653 0 0       0 local $self->{_div_wrap} = 1 if ($meth eq 'Template_wrap');
654 0         0 my $vars = $self->get_wrapped_tt_vars($template);
655 0         0 my $output;
656            
657             # TODO/FIXME: this is duplicate logic that has to be handled for the
658             # top-level template which doesn't seem to go through process() in Context:
659 0 0 0     0 $output = $self->Template_raw->context->_template_error_content(
660             $template, $TT->error, (
661             $self->is_editable_request($c) &&
662             $self->Access->template_writable($template)
663             )
664             ) unless $TT->process( $template, $vars, \$output );
665            
666 0         0 return $output;
667             }
668              
669             # Returns undef if the template is valid or the error
670             sub _get_template_error {
671 0     0   0 my ($self, $meth, $template, $c) = @_;
672 0         0 my $TT = $self->$meth;
673 0         0 local $self->{_current_context} = $c;
674 0 0       0 local $self->{_div_wrap} = 1 if ($meth eq 'Template_wrap');
675 0         0 my $vars = $self->get_wrapped_tt_vars($template);
676 0         0 my $output;
677 0         0 local $self->{_no_exception_error_content} = 1;
678 0 0       0 return $TT->process( $template, $vars, \$output ) ? undef : $TT->error;
679             }
680              
681             # Internal render function - designed to be called interactively
682             # from other parts of the application to render a template (i.e.
683             # not associated with a Template::Controller request)
684             #
685             # TODO: This function will replace/merge with $c->template_render
686             # in RapidApp::Role::CatalystApplication
687             sub template_render {
688 1     1 0 115 my ($self, $template, $vars, $c) = @_;
689 1   50     5 $vars ||= {};
690 1   33     6 $c ||= RapidApp->active_request_context;
691            
692             # The current context may not be available:
693             # see DummyAccess in RapidApp::Template::Access:
694 1 50       4 local $self->{_dummy_access} = 1 unless ($c);
695 1   33     7 local $self->{_current_context} = $c || $self->_app;
696            
697             # The get_template_vars() API in the Access class expects
698             # to have access to the catalyst context (i.e. request) so
699             # we only call it and merge it in if we have $c, which is
700             # optional in this method
701 1 50       4 %$vars = (%{ $self->get_wrapped_tt_vars($template) }, %$vars)
  1         7  
702             if ($c);
703            
704 1         33 my $TT = $self->Template_raw;
705              
706 1         3 my $out;
707 1 50       7 $TT->process($template,$vars,\$out) or die $TT->error;
708              
709 1         92 return $out;
710             }
711              
712              
713             # Wraps all CodeRef vars to cleanly catch exceptions that may be
714             # thrown by them. TT isn't able to handle them properly...
715             sub get_wrapped_tt_vars {
716 1     1 0 4 my ($self,$template) = @_;
717 1         37 my $vars = $self->Access->get_template_vars($template);
718            
719 1 50       5 die "Access class method 'get_template_vars()' didn't return a HashRef!"
720             unless (ref($vars) eq 'HASH');
721            
722 1         4 for my $var (keys %$vars) {
723 7 100       19 next unless (ref($vars->{$var}) eq 'CODE');
724 4         7 my $coderef = delete $vars->{$var};
725             $vars->{$var} = sub {
726 0     0   0 my @args = @_;
727 0         0 my $ret;
728             try {
729 0         0 $ret = $coderef->(@args);
730             }
731             catch {
732 0         0 my $err_msg = "!! EXCEPTION IN CODEREF TEMPLATE VARIABLE '$var': $_";
733            
734             # TODO/FIXME:
735             # We set the return value with the exception as a string (i.e. as content)
736             # instead of re-throwing because TT will display a useless and confusing
737             # error message, something like: "...Useless bare catch()..."
738 0         0 $ret = $err_msg;
739            
740             # We may not actually be able to see the error in the template rendering
741             # but at least it will be printed on the console (an exception here isn't
742             # actually a *Template* error, per-se ... its an error in the perl code
743             # that is called by this CodeRef)
744 0         0 warn RED.BOLD . $err_msg . CLEAR;
745 0         0 };
746 0         0 return $ret;
747 4         24 };
748             }
749            
750 1         12 return $vars;
751             }
752              
753             1;