File Coverage

blib/lib/RapidApp/Role/CatalystApplication.pm
Criterion Covered Total %
statement 132 252 52.3
branch 27 92 29.3
condition 21 85 24.7
subroutine 37 52 71.1
pod 0 24 0.0
total 217 505 42.9


line stmt bran cond sub pod time code
1             package RapidApp::Role::CatalystApplication;
2              
3 4     4   3145 use Moose::Role;
  4         16  
  4         35  
4 4     4   22412 use RapidApp::Util qw(:all);
  4         11  
  4         1995  
5 4     4   2093 use RapidApp::RapidApp;
  4         16  
  4         223  
6 4     4   41 use Scalar::Util 'blessed';
  4         8  
  4         297  
7 4     4   28 use CatalystX::InjectComponent;
  4         11  
  4         151  
8 4     4   29 use RapidApp::Util::Hash::Merge;
  4         12  
  4         270  
9 4     4   3095 use Text::SimpleTable::AutoWidth;
  4         21150  
  4         167  
10 4     4   31 use Catalyst::Utils;
  4         10  
  4         126  
11 4     4   24 use Path::Class qw(file dir);
  4         9  
  4         268  
12 4     4   26 use Time::HiRes qw(tv_interval);
  4         10  
  4         37  
13 4     4   631 use Clone qw(clone);
  4         14  
  4         192  
14 4     4   26 use Carp 'croak';
  4         10  
  4         220  
15             require Data::Dumper::Concise;
16 4     4   23 use URI::Escape;
  4         10  
  4         211  
17              
18 4     4   29 use RapidApp;
  4         8  
  4         122  
19 4     4   2248 use Template;
  4         73524  
  4         173  
20              
21 4     4   32 use Catalyst::Controller::SimpleCAS 1.001;
  4         149  
  4         5267  
22              
23 1     1 0 6 sub rapidapp_version { $RapidApp::VERSION }
24              
25 439     439 0 2239 sub rapidApp { (shift)->model("RapidApp"); }
26              
27             has 'request_id' => ( is => 'ro', default => sub { (shift)->rapidApp->requestCount; } );
28              
29             # This will be set if the app has been loaded by RapidApp::Builder:
30 0     0 0 0 sub ra_builder { (shift)->config->{_ra_builder} }
31              
32             sub mount_url {
33 114     114 0 261 my $c = shift;
34 114   50 114   676 my $pfx = try{ $c->req->env->{SCRIPT_NAME} } || '';
  114         3250  
35 114 50       9270 $pfx eq '/' ? '' : $pfx
36             }
37              
38             sub default_favicon_url {
39 2     2 0 6 my $c = shift;
40             my $path = $c->config->{'RapidApp'}{default_favicon_url}
41 2   50     8 || '/assets/rapidapp/misc/static/images/rapidapp_icon_small.ico';
42 2         176 join('',$c->mount_url,$path)
43             }
44              
45             sub favicon_head_tag {
46 2     2 0 9 my $c = shift;
47            
48             # allow the user to override via config if they really want to:
49 2         41 my $custom = $c->config->{'RapidApp'}{favicon_head_tag};
50 2 50       311 return $custom if ($custom);
51            
52 2         12 my $url = $c->default_favicon_url;
53 2 50       23 return $url ? join('','<link rel="icon" href="',$url,'" type="image/x-icon" />') : undef
54             }
55              
56             # This method comes from Catalyst::Plugin::AutoAssets
57             around 'all_html_head_tags' => sub {
58             my ($orig,$c,@args) = @_;
59            
60             my $html = $c->$orig(@args);
61             if(my $tag = $c->favicon_head_tag) {
62             $html = join("\r\n",'<!-- AUTO GENERATED favicon_head_tag -->',$tag,'',$html);
63             }
64             return $html
65             };
66              
67              
68             # ---
69             # Override dump_these to limit the depth of data structures which will get
70             # dumped. This is needed because RapidApp has a relatively large footprint
71             # and the dump can get excessive. This gets called from finalize_error
72             # when in debug mode.
73             around 'dump_these' => sub {
74             my ($orig,$c,@args) = @_;
75              
76             # strip and capture original 'Request' and 'Response'
77             my ($req_arr,$res_arr);
78             my $these = [ grep {
79             ! ($_->[0] eq 'Request' and $req_arr = $_) &&
80             ! ($_->[0] eq 'Response' and $res_arr = $_)
81             } $c->$orig(@args) ];
82              
83             my @new_these = ();
84             {
85             require Data::Dumper;
86             local $Data::Dumper::Maxdepth = 4;
87             my $VAR1; eval( Data::Dumper::Dumper($these) );
88             @new_these = (
89             # Put the original, non-depth-limited Request and Reponse data back in.
90             # We need to do this because there are other places in native Catalyst
91             # code (e.g. log_request_uploads) which rely on getting the the unaltered
92             # request/response objects out of 'dump_these'. Also, these objects aren't
93             # the ones which need to be limited anyway, so we preserve them as-is.
94             # Added for Github Issue #54, and to preserve the API as of Catalyst 5.90065.
95             # Note: the functioning of this stuff in Catalyst is legacy and may be
96             # refactored in a later version of Catalyst...
97             $req_arr,$res_arr,
98             @{$VAR1 || []}
99             );
100             }
101              
102             return @new_these;
103             };
104             # ---
105              
106              
107             before 'setup_middleware' => sub {
108             my $app = shift;
109            
110             $app->_normalize_catalyst_config;
111            
112             # Set the Encoding to UTF-8 unless one is already set:
113             $app->encoding('UTF-8') unless ($app->encoding);
114            
115             # Force this standard setting. When it is off, in certain cases, it
116             # can lead to bizzare regex exceptions. This setting is already automatically
117             # set for all new apps created by recent versions of catalyst.pl
118             $app->config( disable_component_resolution_regex_fallback => 1 );
119            
120             unshift @{ $app->config->{'psgi_middleware'} ||= [] },
121             '+RapidApp::Plack::Middleware'
122             };
123              
124             sub application_has_root_controller {
125 8     8 0 27 my $app = shift;
126 8         22 for (keys %{ $app->components }) {
  8         41  
127 106         1942 my $component = $app->components->{$_};
128 106 100       4238 if ($component->can('action_namespace')) {
129 12 100       69 return 1 if $component->action_namespace($app) eq '';
130             }
131             }
132 4         33 return 0;
133             }
134              
135             around 'setup_components' => sub {
136             my ($orig, $app, @args)= @_;
137              
138             $app->$orig(@args); # standard catalyst setup_components
139             $app->setupRapidApp; # our additional components needed for RapidApp
140             };
141              
142             sub setupRapidApp {
143 4     4 0 28 my $app = shift;
144            
145             my @inject = (
146 4 50       8 @{ $app->config->{ra_inject_components} || [] },
  4         19  
147             ['RapidApp::RapidApp' => 'RapidApp']
148             );
149              
150             # Views:
151 4         373 push @inject, (
152             ['Catalyst::View::TT' => 'View::RapidApp::TT' ],
153             ['RapidApp::View::Viewport' => 'View::RapidApp::Viewport' ],
154             ['RapidApp::View::Printview' => 'View::RapidApp::Printview' ],
155             ['RapidApp::View::JSON' => 'View::RapidApp::JSON' ],
156             ['RapidApp::View::Template' => 'View::RapidApp::Template' ]
157             );
158            
159             ## This code allowed for automatic detection of an alternate, locally-defined
160             ## 'ModuleDispatcher' controller to act as the root module controller. This
161             ## functionality is not used anyplace, has never been public, and is not worth
162             ## the maintenance cost
163             #my $log = $app->log;
164             #my @names= keys %{ $app->components };
165             #my @controllers= grep /[^:]+::Controller.*/, @names;
166             #my $haveRoot= 0;
167             #foreach my $ctlr (@controllers) {
168             # if ($ctlr->isa('RapidApp::ModuleDispatcher')) {
169             # $log->debug("RapidApp: Found $ctlr which implements ModuleDispatcher.");
170             # $haveRoot= 1;
171             # }
172             #}
173             #if (!$haveRoot) {
174             # #$log->debug("RapidApp: No Controller extending ModuleDispatcher found, using default")
175             # # if($app->debug);
176             # push @inject,['RapidApp::Controller::DefaultRoot', 'Controller::RapidApp::Root'];
177             #}
178              
179             croak "Please use module_root_namespace, if you install your own Root Controller"
180             if $app->application_has_root_controller
181 4 50 33     21 && !$app->config->{RapidApp}->{module_root_namespace};
182              
183             # Controllers:
184 4         29 push @inject, (
185             ['RapidApp::Controller::DefaultRoot' => 'Controller::RapidApp::Root' ],
186             ['RapidApp::Controller::DirectCmp' => 'Controller::RapidApp::Module' ],
187             ['RapidApp::Template::Controller' => 'Controller::RapidApp::Template' ],
188             ['RapidApp::Template::Controller::Dispatch' => 'Controller::RapidApp::TemplateDispatch' ],
189             );
190              
191 4         14 $app->injectUnlessExist( @{$_} ) for (@inject);
  40         156564  
192             };
193              
194             sub root_module_controller {
195 0     0 0 0 my $c = shift;
196 0         0 return $c->controller('RapidApp::Root');
197             }
198              
199             sub injectUnlessExist {
200 41     41 0 153 my ($app, $actual, $virtual)= @_;
201 41 50       164 if (!$app->components->{$virtual}) {
202 41 50       1149 $app->debug && $app->log->debug("RapidApp - Injecting Catalyst Component: $virtual");
203 41         387 CatalystX::InjectComponent->inject( into => $app, component => $actual, as => $virtual );
204             }
205             }
206              
207             after 'setup_finalize' => sub {
208             my $app = shift;
209             $app->rapidApp->_setup_finalize;
210             $app->log->info(sprintf(
211             " --- $app (RapidApp v$RapidApp::VERSION) Loaded in %0.3f seconds ---",
212             tv_interval($RapidApp::START)
213             ));
214             };
215              
216             # called once per request, in class-context
217             before 'handle_request' => sub {
218             my ($app, @arguments)= @_;
219             $app->rapidApp->incRequestCount;
220             };
221              
222             # called once per request, to dispatch the request on a newly constructed $c object
223             around 'dispatch' => \&_rapidapp_top_level_dispatch;
224              
225             sub _rapidapp_top_level_dispatch {
226 50     50   625 my ($orig, $c, @args)= @_;
227            
228             # New: simpler global to get $c in user code. can be accessed from
229             # anywhere with: 'RapidApp->active_request_context()'
230 50         153 local $RapidApp::ACTIVE_REQUEST_CONTEXT = $c;
231            
232             # put the debug flag into the stash, for easy access in templates
233 50         193 $c->stash->{debug} = $c->debug;
234            
235             # provide hints for our controllers on what contect type is expected
236             $c->stash->{requestContentType}=
237 50   100     3802 $c->req->header('X-RapidApp-RequestContentType')
238             || $c->req->param('RequestContentType')
239             || '';
240            
241 50         13388 $c->stash->{onrequest_time_elapsed}= 0;
242            
243             try {
244 50     50   2445 $orig->($c, @args);
245 50 50       44367 if(my ($err) = (@{ $c->error })) {
  50         219  
246 0 0 0     0 if (blessed($err) && $err->isa('RapidApp::Responder')) {
    0          
247 0         0 $c->clear_errors;
248 0         0 $c->forward($err->action);
249             }
250            
251             # ------
252             # New: support a custom app-wide error template:
253             elsif(my $template = $c->config->{RapidApp}{error_template}) {
254             try {
255 0         0 my $TC = $c->template_controller;
256            
257             # --------
258             # This is just a little fallback code to automatically dump the template 'error'
259             # variable in case it is an object/reference but being used directly in the error
260             # template. Exceptions are caught and put in the 'error' TT var. The exception
261             # could be a simple text message, OR it could be an object. If user code throws
262             # exception objects, their error template should know how to handle them, however,
263             # if they miss this detail and don't, we try to save them from shooting themselves
264             # in the foot by dumping the object rather than allowing it to be rendered as simply
265             # 'Some:Class=HASH(0x1046f198)' which is almost never useful -- BUT, we also must
266             # take into account whether or not the object already stringifies, and only do this
267             # override when it does not, which is exactly what this code does.
268             # Note that this is not full-proof, and currently this only works when the template
269             # stash class is Template::Stash::XS, which is most likely, but by no means
270             # guaranteed. But in that case this code just won't be called
271 0         0 my $orig_get = \&Template::Stash::XS::get;
272 4     4   43 no warnings 'redefine';
  4         12  
  4         11566  
273             local *Template::Stash::XS::get = sub {
274 0         0 my ($self, $var) = @_;
275 0         0 my $val = $self->$orig_get($var);
276 0         0 require SQL::Abstract;
277 0 0 0     0 return ($var eq 'error' && ! SQL::Abstract::is_plain_value($val))
278             ? join('',"$val - OBJECT DUMP: ",Dumper($val))
279             : $val
280 0         0 };
281             # --------
282            
283             # If the error is an object or HashRef with a 'status_code'
284             # method/key which returns a value that looks like an HTTP
285             # status code, use it, otherwise stick with the standard 500:
286 0   0     0 my $status = try{$err->status_code} || try{$err->{status_code}};
287 0 0 0     0 $status = 500 unless ($status && ($status =~ /^\d{3}$/));
288            
289 0         0 my $body = $TC->template_render($template,{
290             error => $err, error_status_code => $status
291             },$c);
292            
293 0         0 $c->response->status($status);
294 0         0 $c->response->body($body);
295 0         0 $c->clear_errors;
296             }
297             catch {
298 0         0 my $e = shift;
299 0         0 warn 'EXCEPTION TRYING TO RENDER WITH CUSTOM error_template: ' . $e;
300 0         0 };
301             }
302             # ------
303             }
304             }
305             catch {
306             # Fallback to handle uncaught exceptions during dispatch. This is
307             # known to happen when the client sends a garbled request, such as
308             # overly long Ajax requests that were truncated
309 0     0   0 my $err = shift;
310 0         0 warn $err;
311 0         0 $c->response->content_type('text/plain');
312 0         0 $c->response->body(" *** Uncaught Exception in Catalyst Engine ***\n\n\n$err");
313 0         0 $c->response->status(500);
314 50         3636 };
315            
316 50 50       2980 if (!defined $c->response->content_type) {
317 0         0 $c->log->error("Body was set, but content-type was not! This can lead to encoding errors!");
318             }
319             };
320              
321             sub module_root_namespace {
322 165     165 0 498 my $c = shift;
323 165   50     829 return $c->config->{'Model::RapidApp'}{module_root_namespace} || '';
324             }
325              
326             # This is ugly, but seems to be the best way to re-resolve a *public* URL
327             # path and dispatch it. It essentially starts over in handle_request at
328             # the 'prepare_action' phase with a different request path set, leaving
329             # all other details of the request the same. This is meant to be called
330             # during an existing request (dispatch phase). This is used internally in
331             # places like NavCore for saved searches:
332             sub redispatch_public_path {
333 0     0 0 0 my ($c, @args) = @_;
334              
335 0         0 my $path = join('/',@args);
336 0         0 $path =~ s/^\///; #<-- strip leading /
337 0         0 $path =~ s/\/$//; #<-- strip trailing leading /
338 0         0 $path =~ s/\/+/\//g; #<-- strip any double //
339 0   0     0 $path ||= '';
340              
341 0 0       0 $c->log->debug("Redispatching as path: $path") if ($c->debug);
342              
343             # Overwrite the 'path' in the request object:
344 0         0 $c->request->path($path);
345              
346             # Now call prepare_action again, now with the updated path:
347 0         0 $c->prepare_action;
348              
349             # Now forward to the new action. If there is no action,
350             # call $c->dispatch just for the sake of error handling
351 0 0       0 return $c->action ? $c->forward( $c->action ) : $c->dispatch;
352             }
353              
354              
355             sub auto_hashnav_redirect_current {
356 2     2 0 8 my ($c, @args) = @_;
357             return $c->hashnav_redirect_current(@args) if (
358             $c->req->method eq 'GET' && ! $c->is_ra_ajax_req
359             && ! $c->req->params->{__no_hashnav_redirect} #<-- new: check for special exclude param
360 2 50 33     10 );
      33        
361             }
362              
363             sub hashnav_redirect_current {
364 0     0 0 0 my ($c, @args) = @_;
365             # Redirects the current request back to itself as a hashnav:
366 0         0 return $c->hashnav_redirect($c->req->path,$c->req->params,@args);
367             }
368              
369             sub hashnav_redirect {
370 0     0 0 0 my ($c, $path, $params, $base) = @_;
371              
372 0 0       0 $path = [$path] unless (ref($path));
373              
374 0 0       0 unless(defined $base) {
375             # Use the module_root_namespace as the base, if set:
376 0         0 my $ns = $c->module_root_namespace;
377 0 0       0 $base = $ns ne '' ? join('','/',$ns,'/') : '/';
378             }
379              
380 0         0 my $url = join('/','',$base.'#!',@$path);
381 0         0 $url =~ s/\/+/\//g; #<-- strip any double //
382              
383 0 0 0     0 if($params && keys %$params > 0) {
384 0         0 my $qs = join('&',map { $_ . '=' . uri_escape($params->{$_}) } keys %$params);
  0         0  
385 0         0 $url .= '?' . $qs;
386             }
387              
388 0         0 $c->response->redirect($c->mount_url.$url);
389 0         0 return $c->detach;
390             }
391              
392             # This is very old, but was originally within the Module Controller role:
393             sub set_response_warning {
394 0     0 0 0 my ($c,$warn) = @_;
395              
396 0 0       0 $warn = {
397             title => 'Warning',
398             msg => $warn
399             } unless (ref $warn);
400              
401             die "Invalid argument passed to set_response_warning" unless (
402             ref($warn) eq 'HASH' &&
403             defined $warn->{msg}
404 0 0 0     0 );
405              
406 0         0 $c->res->header( 'X-RapidApp-Warning' => encode_json_utf8($warn) );
407             }
408              
409              
410             around 'finalize_error' => sub {
411             my ($orig, $c, @args) = @_;
412             if($c->is_ra_ajax_req) {
413             # If this is an Ajax request, send it back as raw text instead of
414             # the normal Catalyst::Engine's HTML error page
415             $c->res->content_type('text/plain; charset=utf-8');
416             my $error = join("\n", @{ $c->error }) || 'Unknown error';
417             if($c->debug) {
418             $error .= join("\n",
419             "\n\n",
420             "RapidApp v$RapidApp::VERSION\n",
421             # Stop dumping this altogether because it is almost never useful,
422             # and in big apps can be huge and cause the failed request to
423             # timeout.
424             #map { Data::Dumper::Concise::Dumper($_) } $c->dump_these
425             );
426             };
427             $c->res->body($error);
428             $c->res->status(500);
429             }
430             else {
431             return $c->$orig(@args);
432             }
433             };
434              
435             # called after the response is sent to the client, in object-context
436             after 'log_response' => sub {
437             my $c= shift;
438             $c->rapidApp->cleanupAfterRequest($c);
439             };
440              
441              
442             # reset stats for each request:
443             before 'dispatch' => sub { %$RapidApp::Util::debug_around_stats = (); };
444             after 'dispatch' => \&_report_debug_around_stats;
445              
446             sub _report_debug_around_stats {
447 50     50   10966 my $c = shift;
448 50   50     296 my $stats = $RapidApp::Util::debug_around_stats || return;
449 50 50 33     479 return unless (ref($stats) && keys %$stats > 0);
450            
451 0         0 my $total = $c->stats->elapsed;
452            
453 0         0 my $display = $c->_get_debug_around_stats_ascii($total,"Catalyst Request Elapsed");
454            
455 0         0 print STDERR "\n" . $display;
456             }
457              
458              
459             sub _get_debug_around_stats_ascii {
460 0     0   0 my $c = shift;
461 0 0       0 my $total = shift or die "missing total arg";
462 0   0     0 my $total_heading = shift || 'Total Elapsed';
463            
464 0   0     0 my $stats = $RapidApp::Util::debug_around_stats || return;
465 0 0 0     0 return unless (ref($stats) && keys %$stats > 0);
466            
467 0         0 my $auto_width = 'calls';
468 0         0 my @order = qw(class sub calls min/max/avg total pct);
469            
470 0         0 $_->{pct} = ($_->{total}/$total)*100 for (values %$stats);
471            
472 0         0 my $tsum = 0;
473 0         0 my $csum = 0;
474 0         0 my $count = 0;
475 0         0 my @rows = ();
476 0         0 foreach my $stat (sort {$b->{pct} <=> $a->{pct}} values %$stats) {
  0         0  
477 0         0 $tsum += $stat->{total};
478 0         0 $csum += $stat->{calls};
479 0         0 $count++;
480            
481 0         0 $stat->{$_} = sprintf('%.3f',$stat->{$_}) for(qw(min max avg total));
482 0         0 $stat->{'min/max/avg'} = $stat->{min} . '/' . $stat->{max} . '/' . $stat->{avg};
483 0         0 $stat->{pct} = sprintf('%.1f',$stat->{pct}) . '%';
484              
485 0         0 push @rows, [ map {$stat->{$_}} @order ];
  0         0  
486             }
487              
488 0         0 my $tpct = sprintf('%.1f',($tsum/$total)*100) . '%';
489 0         0 $tsum = sprintf('%.3f',$tsum);
490            
491 0         0 my $t = Text::SimpleTable::AutoWidth->new(
492             max_width => Catalyst::Utils::term_width(),
493             captions => \@order
494             );
495              
496 0         0 $t->row(@$_) for (@rows);
497 0         0 $t->row(' ',' ',' ',' ',' ',' ');
498 0         0 $t->row('(' . $count . ' Tracked Functions)','',$csum,'',$tsum,$tpct);
499            
500 0         0 my $table = $t->draw;
501            
502 0         0 my $display = BOLD . "Tracked Functions (debug_around) Stats (current request):\n" . CLEAR .
503             BOLD.MAGENTA . $table . CLEAR .
504             BOLD . "Catalyst Request Elapsed: " . YELLOW . sprintf('%.3f',$total) . CLEAR . "s\n\n";
505            
506 0         0 return $display;
507              
508             }
509              
510              
511             ## Moved from RapidApp::Catalyst:
512              
513              
514 0     0 0 0 sub app_version { eval '$' . (shift)->config->{name} . '::VERSION' }
515              
516             before 'setup_plugins' => sub {
517             my $c = shift;
518              
519             # -- override Static::Simple default config to ignore extensions like html.
520             my $config
521             = $c->config->{'Plugin::Static::Simple'}
522             = $c->config->{'static'}
523             = Catalyst::Utils::merge_hashes(
524             $c->config->{'Plugin::Static::Simple'} || {},
525             $c->config->{static} || {}
526             );
527            
528             $config->{ignore_extensions} ||= [];
529             $c->config->{'Plugin::Static::Simple'} = $config;
530             # --
531            
532             };
533             # --
534              
535             # Handy method returns true for requests which came from The RapidApp ajax client
536             sub is_ra_ajax_req {
537 8     8 0 161 my $c = shift;
538 8 50 33     277 return 0 unless ($c->can('request') && $c->request);
539 8 100       269 my $tp = $c->request->header('X-RapidApp-RequestContentType') or return 0;
540 6 50       719 return $tp eq 'JSON' ? 1 : 0;
541             }
542              
543             # Some some housework on the config for normalization/consistency:
544             sub _normalize_catalyst_config {
545 4     4   11 my $c = shift;
546            
547 4         23 my $cnf = $c->config;
548 4 0 33     357 $cnf->{name} ||= ref $c ? ref $c : $c;
549 4   50     38 $cnf->{'RapidApp'} ||= {};
550            
551             # New: allow root_template_prefix/root_template to be supplied
552             # in the Template Controller config instead of Model::RapidApp
553             # since it just makes better sense from the user standpoint:
554 4   50     26 my $tc_cfg = $cnf->{'Controller::RapidApp::Template'} || {};
555             $cnf->{'RapidApp'}{root_template_prefix} = $tc_cfg->{root_template_prefix}
556 4 50       17 if(exists $tc_cfg->{root_template_prefix});
557             $cnf->{'RapidApp'}{root_template} = $tc_cfg->{root_template}
558 4 50       20 if(exists $tc_cfg->{root_template});
559            
560             # ---
561             # We're going to transition away from the 'Model::RapidApp' config
562             # key because it is confusing, and in the future the current "model"
563             # class will probably go away (since it is not really a model).
564             # We're going to start by merging/aliasing the config key so users
565             # can use 'RapidApp' instead of 'Model::RapidApp';
566             $cnf->{'Model::RapidApp'} = Catalyst::Utils::merge_hashes(
567             $cnf->{'Model::RapidApp'} || {},
568 4   50     49 $cnf->{'RapidApp'} || {}
      50        
569             );
570 4         69 $cnf->{'RapidApp'} = $cnf->{'Model::RapidApp'};
571             # ---
572              
573             }
574              
575             # New: convenience method to get the main 'Template::Controller' which
576             # is being made into a core function of rapidapp:
577 1     1 0 7 sub template_controller { (shift)->controller('RapidApp::Template') }
578 0     0 0 0 sub template_dispatcher { (shift)->controller('RapidApp::TemplateDispatch') }
579              
580             my $share_dir = dir( RapidApp->share_dir );
581             sub default_tt_include_path {
582 12     12 0 1653 my $c = shift;
583 12 50       33 my $app = ref $c ? ref $c : $c;
584            
585 12         31 my @paths = ();
586 12         42 my $home = dir( Catalyst::Utils::home($app) );
587            
588 12 100 66     11302 if($home && -d $home) {
589 6         257 my $root = $home->subdir('root');
590 6 50 33     370 if($root && -d $root) {
591 0         0 my $tpl = $root->subdir('templates');
592 0 0 0     0 push @paths, "$tpl" if ($tpl && -d $tpl);
593 0         0 push @paths, "$root";
594             }
595             }
596            
597             # This should be redundant if share_dir is setup properly
598 12 50 33     500 if($share_dir && -d $share_dir) {
599 12         538 my $tpl = $share_dir->subdir('templates');
600 12 50 33     559 push @paths, "$tpl" if ($tpl && -d $tpl);
601 12         768 push @paths, "$share_dir";
602             }
603            
604 12         287 return join(':',@paths);
605             }
606              
607             # convenience util function
608             ## TODO: This is to be replaced with a call to template_render() within
609             ## the new Template::Controller (see template_controller() above)
610             my $TT;
611             sub template_render {
612 0     0 0 0 my $c = shift;
613 0         0 my $template = shift;
614 0   0     0 my $vars = shift || {};
615            
616 0   0     0 $TT ||= Template->new({
617             INCLUDE_PATH => $c->default_tt_include_path,
618             ABSOLUTE => 1
619             });
620            
621 0         0 my $out;
622 0 0       0 $TT->process($template,$vars,\$out) or die $TT->error;
623              
624 0         0 return $out;
625             }
626              
627             # Temp hack to set the include path for our TT Views. These Views will be
628             # totally refactored in RapidApp 2. This will remain until then:
629             before 'setup_components' => sub {
630             my $c = shift;
631             my @views = qw(
632             View::RapidApp::TT
633             View::RapidApp::Viewport
634             View::RapidApp::Printview
635             );
636            
637             $c->config( $_ => {
638             INCLUDE_PATH => $c->default_tt_include_path,
639             ABSOLUTE => 1
640             }) for (@views);
641             };
642              
643              
644             our $ON_FINALIZE_SUCCESS = [];
645              
646             ## -- 'on_finalize_success' provides a mechanism to call code at the end of the request
647             ## only if successful
648             sub add_on_finalize_success {
649 0     0 0 0 my $c = shift;
650             # make sure this is the CONTEXT object and not a class name
651 0 0       0 $c = RapidApp->active_request_context unless (ref $c);
652 0 0       0 my $code = shift or die "No CodeRef supplied";
653 0 0       0 die "add_on_finalize_success(): argument not a CodeRef"
654             unless (ref $code eq 'CODE');
655            
656 0 0   0   0 if(try{$c->stash}) {
  0         0  
657 0   0     0 $c->stash->{on_finalize_success} ||= [];
658 0         0 push @{$c->stash->{on_finalize_success}},$code;
  0         0  
659             }
660             else {
661 0         0 push @$ON_FINALIZE_SUCCESS,$code;
662             }
663 0         0 return 1;
664             }
665              
666             before 'finalize' => sub {
667             my $c = shift;
668             my $coderefs = try{$c->stash->{on_finalize_success}} or return;
669             return unless (scalar @$coderefs > 0);
670             my $status = $c->res->code;
671             return unless ($status =~ /^[23]\d{2}$/); # status code 2xx = success, also allow 3xx codes
672             $c->log->info(
673             "finalize_body(): calling " . (scalar @$coderefs) .
674             " CodeRefs added by 'add_on_finalize_success'"
675             );
676             $c->run_on_finalize_success_codes($coderefs);
677             };
678 4     4   9876 END { __PACKAGE__->run_on_finalize_success_codes($ON_FINALIZE_SUCCESS); }
679              
680             sub run_on_finalize_success_codes {
681 4     4 0 16 my $c = shift;
682 4         12 my $coderefs = shift;
683 4         11 my $num = 0;
684 4         57 foreach my $ref (@$coderefs) {
685             try {
686 0     0     $ref->($c);
687             }
688             catch {
689             # If we get here, we're screwed. Best we can do is log the error. (i.e. we can't tell the user)
690 0     0     my $err = shift;
691 0 0         my $errStr = RED.BOLD . "EXCEPTION IN CodeRefs added by 'add_on_finalize_success!! [coderef #" .
692             ++$num . "]:\n " . CLEAR . RED . (ref $err ? Dumper($err) : $err) . CLEAR;
693            
694 0 0         try{$c->log->error($errStr)} or warn $errStr;
  0            
695            
696             # TODO: handle exceptions here like any other. This might require a bit
697             # of work to achieve because by the time we get here we're already past the
698             # code that handles RapidApp exceptions, and the below commented out code doesn't work
699             #
700             # This doesn't work (Whenever this *concept* is able to work, handle in a single
701             # try/catch instead of a separate one as is currently done - which we're doing because
702             # we're not able to let the user know something went wrong, so we try our best to
703             # run each one):
704             #delete $c->stash->{on_finalize_success};
705             #my $view = $c->view('RapidApp::JSON') or die $err;
706             #$c->stash->{exception} = $err;
707             #$c->forward( $view );
708 0           };
709             }
710             };
711             ##
712             ## --
713              
714              
715              
716             1;