File Coverage

blib/lib/CGI/Application/Plugin/TT.pm
Criterion Covered Total %
statement 186 204 91.1
branch 72 96 75.0
condition 29 47 61.7
subroutine 25 29 86.2
pod 9 9 100.0
total 321 385 83.3


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::TT;
2             $CGI::Application::Plugin::TT::VERSION = '1.06';
3             # ABSTRACT: Plugin that adds Template Toolkit support to CGI::Application
4              
5 9     9   1436521 use Template 2.0;
  9         252697  
  9         487  
6 9     9   991 use CGI::Application 4.0;
  9         10434  
  9         271  
7 9     9   52 use Carp;
  9         27  
  9         597  
8 9     9   51 use File::Spec ();
  9         31  
  9         158  
9 9     9   42 use Scalar::Util ();
  9         47  
  9         188  
10              
11 9     9   37 use strict;
  9         15  
  9         265  
12 9     9   74 use vars qw($VERSION @EXPORT);
  9         22  
  9         3359  
13              
14             require Exporter;
15              
16             @EXPORT = qw(
17             tt_obj
18             tt_config
19             tt_params
20             tt_clear_params
21             tt_process
22             tt_include_path
23             tt_template_name
24             );
25             sub import {
26 9     9   62473 my $pkg = shift;
27 9         24 my $callpkg = caller;
28 9     9   63 no strict 'refs';
  9         52  
  9         3962  
29 9         28 foreach my $sym (@EXPORT) {
30 63         118 *{"${callpkg}::$sym"} = \&{$sym};
  63         328  
  63         148  
31             }
32 9 100       56 $callpkg->tt_config(@_) if @_;
33 9 50       114 if ($callpkg->isa('CGI::Application')) {
34 9         73 $callpkg->new_hook('tt_pre_process');
35 9         151 $callpkg->new_hook('tt_post_process');
36             } else {
37 0         0 warn "Calling package is not a CGI::Application module so not installing tt_pre_process and tt_post_process hooks. If you are using \@ISA instead of 'use base', make sure it is in a BEGIN { } block, and make sure these statements appear before the plugin is loaded";
38             }
39              
40             }
41              
42             ##############################################
43             ###
44             ### tt_obj
45             ###
46             ##############################################
47             #
48             # Get a Template Toolkit object. The same object
49             # will be returned every time this method is called
50             # during a request cycle.
51             #
52             sub tt_obj {
53 29     29 1 39545 my $self = shift;
54              
55 29         126 my ($tt, $options, $frompkg) = _get_object_or_options($self);
56              
57 29 100       135 if (!$tt) {
58 18         49 my $tt_options = $options->{TEMPLATE_OPTIONS};
59 18 50       34 if (keys %{$options->{TEMPLATE_OPTIONS}}) {
  18         87  
60 18   33     221 $tt = Template->new( $options->{TEMPLATE_OPTIONS} ) || carp "Can't load Template";
61             } else {
62 0   0     0 $tt = Template->new || carp "Can't load Template";
63             }
64 18   66     270944 _set_object($frompkg||$self, $tt);
65             }
66 29         134 return $tt;
67             }
68              
69             ##############################################
70             ###
71             ### tt_config
72             ###
73             ##############################################
74             #
75             # Configure the Template Toolkit object
76             #
77             sub tt_config {
78 18     18 1 1592021 my $self = shift;
79 18 100       131 my $class = ref $self ? ref $self : $self;
80              
81 18         129 my $tt_config;
82 18 100       101 if (ref $self) {
83 16 50 33     128 die "Calling tt_config after the tt object has already been created" if @_ && defined $self->{__TT};
84 16   50     105 $tt_config = $self->{__TT_CONFIG} ||= {};
85             } else {
86 9     9   74 no strict 'refs';
  9         21  
  9         16965  
87 2   50     4 ${$class.'::__TT_CONFIG'} ||= {};
  2         22  
88 2         3 $tt_config = ${$class.'::__TT_CONFIG'};
  2         6  
89             }
90              
91 18 50       92 if (@_) {
92 18         43 my $props;
93 18 50       78 if (ref($_[0]) eq 'HASH') {
94 0         0 my $rthash = %{$_[0]};
  0         0  
95 0         0 $props = CGI::Application->_cap_hash($_[0]);
96             } else {
97 18         165 $props = CGI::Application->_cap_hash({ @_ });
98             }
99              
100 18         575 my %options;
101             # Check for TEMPLATE_OPTIONS
102 18 50       81 if ($props->{TEMPLATE_OPTIONS}) {
103             carp "tt_config error: parameter TEMPLATE_OPTIONS is not a hash reference"
104 18 50       78 if Scalar::Util::reftype($props->{TEMPLATE_OPTIONS}) ne 'HASH';
105 18         60 $tt_config->{TEMPLATE_OPTIONS} = delete $props->{TEMPLATE_OPTIONS};
106             }
107              
108             # Check for TEMPLATE_NAME_GENERATOR
109 18 100       104 if ($props->{TEMPLATE_NAME_GENERATOR}) {
110             carp "tt_config error: parameter TEMPLATE_NAME_GENERATOR is not a subroutine reference"
111 1 50       6 if Scalar::Util::reftype($props->{TEMPLATE_NAME_GENERATOR}) ne 'CODE';
112 1         4 $tt_config->{TEMPLATE_NAME_GENERATOR} = delete $props->{TEMPLATE_NAME_GENERATOR};
113             }
114              
115             # Check for TEMPLATE_PRECOMPILE_FILETEST
116 18 100       63 if ($props->{TEMPLATE_PRECOMPILE_FILETEST}) {
117             carp "tt_config error: parameter TEMPLATE_PRECOMPILE_FILETEST is not a subroutine reference or regexp or string"
118             if defined Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_FILETEST})
119             && Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_FILETEST}) ne 'CODE'
120 6 50 100     46 && overload::StrVal($props->{TEMPLATE_PRECOMPILE_FILETEST}) !~ /^Regexp=/;
      66        
121 6         34 $tt_config->{TEMPLATE_PRECOMPILE_FILETEST} = delete $props->{TEMPLATE_PRECOMPILE_FILETEST};
122             }
123              
124             # This property must be tested last, since it creates the TT object in order to
125             # preload all the templates.
126             #
127             # Check for TEMPLATE_PRECOMPILE_DIR
128 18 100       68 if( $props->{TEMPLATE_PRECOMPILE_DIR} ) {
129 6         12 my $type = Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_DIR});
130 6 50 33     21 carp "tt_config error: parameter TEMPLATE_PRECOMPILE_DIR must be a SCALAR or an ARRAY ref"
131             unless( !defined($type) or $type eq 'ARRAY' );
132              
133             # now look at each file and
134 0         0 my @dirs = ($type && $type eq 'ARRAY') ? @{$props->{TEMPLATE_PRECOMPILE_DIR}}
135 6 50 33     26 : ($props->{TEMPLATE_PRECOMPILE_DIR});
136 6         12 delete $props->{TEMPLATE_PRECOMPILE_DIR};
137 6         24 my $tt = $self->tt_obj;
138 6         12 my $junk = '';
139 6     0   32 my $filetester = sub { 1 };
  0         0  
140 6 50       27 if ($tt_config->{TEMPLATE_PRECOMPILE_FILETEST}) {
141 6 100       33 if (! defined Scalar::Util::reftype($tt_config->{TEMPLATE_PRECOMPILE_FILETEST})) {
    100          
    50          
142 2     4   11 $filetester = sub { $_[0] =~ /\.$tt_config->{TEMPLATE_PRECOMPILE_FILETEST}$/ };
  4         409  
143             } elsif (Scalar::Util::reftype($tt_config->{TEMPLATE_PRECOMPILE_FILETEST}) eq 'CODE') {
144 2         5 $filetester = $tt_config->{TEMPLATE_PRECOMPILE_FILETEST};
145             } elsif (overload::StrVal($tt_config->{TEMPLATE_PRECOMPILE_FILETEST}) =~ /^Regexp=/) {
146 2     4   31 $filetester = sub { $_[0] =~ $tt_config->{TEMPLATE_PRECOMPILE_FILETEST} };
  4         327  
147             }
148             }
149 6         71 require File::Find;
150             File::Find::find(
151             sub {
152 12     12   304 my $file = $File::Find::name;
153 12 100       44 return unless $filetester->($file);
154 3 50       84 if( !-d $file ) {
155 3         26 $tt->process( $file, {}, \$junk );
156             }
157             },
158 6         34 map { File::Spec->rel2abs($_) } @dirs,
  6         726  
159             );
160              
161             }
162              
163             # If there are still entries left in $props then they are invalid
164 18 50       45918 carp "Invalid option(s) (".join(', ', keys %$props).") passed to tt_config" if %$props;
165             }
166              
167 18         82 $tt_config;
168             }
169              
170             ##############################################
171             ###
172             ### tt_params
173             ###
174             ##############################################
175             #
176             # Set some parameters that will be added to
177             # any template object we process in this
178             # request cycle.
179             #
180             sub tt_params {
181 29     29 1 325753 my $self = shift;
182 29         91 my @data = @_;
183              
184             # Define the params stash if it doesn't exist
185 29   100     183 $self->{__TT_PARAMS} ||= {};
186              
187 29 100       74 if (@data) {
188 16         33 my $params = $self->{__TT_PARAMS};
189 16         31 my $newparams = {};
190 16 100       68 if (ref $data[0] eq 'HASH') {
    50          
191             # hashref
192 8         17 %$newparams = %{ $data[0] };
  8         52  
193             } elsif ( (@data % 2) == 0 ) {
194 8         32 %$newparams = @data;
195             } else {
196 0         0 carp "tt_params requires a hash or hashref!";
197             }
198              
199             # merge the new values into our stash of parameters
200 16         76 @$params{keys %$newparams} = values %$newparams;
201             }
202              
203 29         154 return $self->{__TT_PARAMS};
204             }
205              
206             ##############################################
207             ###
208             ### tt_clear_params
209             ###
210             ##############################################
211             #
212             # Clear any template parameters that may have
213             # been set during this request cycle.
214             #
215             sub tt_clear_params {
216 0     0 1 0 my $self = shift;
217              
218 0         0 my $params = $self->{__TT_PARAMS};
219 0         0 $self->{__TT_PARAMS} = {};
220              
221 0         0 return $params;
222             }
223              
224             ##############################################
225             ###
226             ### tt_pre_process
227             ###
228             ##############################################
229             #
230             # Sample method that is called just before
231             # a Template is processed.
232             # Useful for setting global template params.
233             # It is passed the template filename and the hashref
234             # of template data
235             #
236             sub tt_pre_process {
237 0     0 1 0 my $self = shift;
238 0         0 my $file = shift;
239 0         0 my $vars = shift;
240              
241             # Do your pre-processing here
242             }
243              
244             ##############################################
245             ###
246             ### tt_post_process
247             ###
248             ##############################################
249             #
250             # Sample method that is called just after
251             # a Template is processed.
252             # Useful for post processing the HTML.
253             # It is passed a scalar reference to the HTML code.
254             #
255             # Note: This could also be accomplished using the
256             # cgiapp_postrun method, except that this
257             # method is called after every template is
258             # processed (you could process multiple
259             # templates in one request), whereas
260             # cgiapp_postrun is only called once after
261             # the runmode has completed.
262             #
263             sub tt_post_process {
264 0     0 1 0 my $self = shift;
265 0         0 my $htmlref = shift;
266              
267             # Do your post-processing here
268             }
269              
270             ##############################################
271             ###
272             ### tt_process
273             ###
274             ##############################################
275             #
276             # Process a Template Toolkit template and return
277             # the resulting html as a scalar ref
278             #
279             sub tt_process {
280 13     13 1 2390 my $self = shift;
281 13         32 my $file = shift;
282 13         29 my $vars = shift;
283 13         31 my $html = '';
284              
285 13 50       90 my $can_call_hook = UNIVERSAL::can($self, 'call_hook') ? 1 : 0;
286              
287 13 100 100     153 if (! defined($vars) && (Scalar::Util::reftype($file)||'') eq 'HASH') {
      100        
288 4         9 $vars = $file;
289 4         10 $file = undef;
290             }
291 13   66     71 $file ||= $self->tt_template_name(1);
292 13   100     98 $vars ||= {};
293 13         34 my $template_name = $file;
294              
295             # Call the load_tmpl hook that is part of CGI::Application
296 13 50       107 $self->call_hook(
297             'load_tmpl',
298             {}, # template options are ignored
299             $vars,
300             $file,
301             ) if $can_call_hook;
302              
303             # Call tt_pre_process hook
304 13 100       617 $self->tt_pre_process($file, $vars) if $self->can('tt_pre_process');
305 13 50       121 $self->call_hook('tt_pre_process', $file, $vars) if $can_call_hook;
306              
307             # Include any parameters that may have been
308             # set with tt_params
309 13         399 my %params = ( %{ $self->tt_params() }, %$vars );
  13         60  
310              
311             # Add c => $self in as a param for convenient access to sessions and such
312 13   33     165 $params{c} ||= $self;
313              
314 13 100       135 $self->tt_obj->process($file, \%params, \$html) || croak $self->tt_obj->error();
315              
316             # Call tt_post_process hook
317 12 100       279903 $self->tt_post_process(\$html) if $self->can('tt_post_process');
318 12 50       178 $self->call_hook('tt_post_process', \$html) if $can_call_hook;
319              
320 12         455 _tt_add_devpopup_info($self, $template_name, \%params);
321              
322 12         106 return \$html;
323             }
324              
325             ##############################################
326             ###
327             ### tt_include_path
328             ###
329             ##############################################
330             #
331             # Change the include path after the template object
332             # has already been created
333             #
334             sub tt_include_path {
335 3     3 1 305671 my $self = shift;
336              
337 3 100       16 return $self->tt_obj->context->load_templates->[0]->include_path unless(@_);
338 2 50       13 $self->tt_obj->context->load_templates->[0]->include_path(ref($_[0]) ? $_[0] : [@_]);
339              
340 2         113 return;
341             }
342              
343             ##############################################
344             ###
345             ### tt_template_name
346             ###
347             ##############################################
348             #
349             # Auto-generate the filename of a template based on
350             # the current module, and the name of the
351             # function that called us.
352             #
353             sub tt_template_name {
354 9     9 1 50 my $self = shift;
355              
356 9         28 my ($tt, $options, $frompkg) = _get_object_or_options($self);
357              
358 9   100     57 my $func = $options->{TEMPLATE_NAME_GENERATOR} || \&__tt_template_name;
359 9         32 return $self->$func(@_);
360             }
361              
362             ##############################################
363             ###
364             ### __tt_template_name
365             ###
366             ##############################################
367             #
368             # Generate the filename of a template based on
369             # the current module, and the name of the
370             # function that called us.
371             #
372             # example:
373             # module $self is blessed into: My::Module
374             # function name that called us: my_function
375             #
376             # generates: My/Module/my_function.tmpl
377             #
378             sub __tt_template_name {
379 7     7   12 my $self = shift;
380 7   100     23 my $uplevel = shift || 0;
381              
382             # the directory is based on the object's package name
383 7         119 my $dir = File::Spec->catdir(split(/::/, ref($self)));
384              
385             # the filename is the method name of the caller plus
386             # whatever offset the user asked for
387 7         86 (caller(2+$uplevel))[3] =~ /([^:]+)$/;
388 7         44 my $name = $1;
389              
390 7         177 return File::Spec->catfile($dir, $name.'.tmpl');
391             }
392              
393             ##
394             ## Private methods
395             ##
396             sub _set_object {
397 18     18   44 my $self = shift;
398 18         38 my $tt = shift;
399 18 100       74 my $class = ref $self ? ref $self : $self;
400              
401 18 100       70 if (ref $self) {
402 16         67 $self->{__TT_OBJECT} = $tt;
403             } else {
404 9     9   77 no strict 'refs';
  9         21  
  9         1579  
405 2         5 ${$class.'::__TT_OBJECT'} = $tt;
  2         15  
406             }
407             }
408              
409             sub _get_object_or_options {
410 38     38   66 my $self = shift;
411 38 50       130 my $class = ref $self ? ref $self : $self;
412              
413             # Handle the simple case by looking in the object first
414 38 50       106 if (ref $self) {
415 38 100       245 return ($self->{__TT_OBJECT}, $self->{__TT_CONFIG}) if $self->{__TT_OBJECT};
416 31 100       158 return (undef, $self->{__TT_CONFIG}) if $self->{__TT_CONFIG};
417             }
418              
419             # See if we can find them in the class hierarchy
420             # We look at each of the modules in the @ISA tree, and
421             # their parents as well until we find either a tt
422             # object or a set of configuration parameters
423 8         96 require Class::ISA;
424 8         35 foreach my $super ($class, Class::ISA::super_path($class)) {
425 9     9   95 no strict 'refs';
  9         26  
  9         4379  
426 8 100       447 return (${$super.'::__TT_OBJECT'}, ${$super.'::__TT_CONFIG'}, $super) if ${$super.'::__TT_OBJECT'};
  6         18  
  6         29  
  8         39  
427 2 50       16 return (undef, ${$super.'::__TT_CONFIG'}, $super) if ${$super.'::__TT_CONFIG'};
  2         14  
  2         13  
428             }
429 0         0 return;
430             }
431              
432             ##############################################
433             ###
434             ### _tt_add_devpopup_info
435             ###
436             ##############################################
437             #
438             # This method will look to see if the devpopup
439             # plugin is being used, and will display all the
440             # parameters that were passed to the template.
441             #
442             sub _tt_add_devpopup_info {
443 12     12   43 my $self = shift;
444 12         29 my $name = shift;
445 12         23 my $params = shift;
446              
447 12 100       129 return unless UNIVERSAL::can($self, 'devpopup');
448              
449 1         5 my %params = %$params;
450 1         3 foreach my $key (keys %params) {
451 3 100       7 if (my $class = Scalar::Util::blessed($params{$key})) {
452 1         3 $params{$key} = "Object:$class";
453             }
454             }
455              
456 1         525 require Data::Dumper;
457 1         6616 my $dumper = Data::Dumper->new([\%params]);
458 1         27 $dumper->Varname('Params');
459 1         12 $dumper->Indent(2);
460 1         10 my $dump = $dumper->Dump();
461              
462             # Entity encode the output since it will be displayed on a webpage and we
463             # want all HTML content rendered as text (borrowed from HTML::Entities)
464 1         34 $dump =~ s/([^\n\r\t !\#\$%\(-;=?-~])/sprintf "&#x%X;", ord($1)/ge;
  21         40  
465              
466 1         9 $self->devpopup->add_report(
467             title => "TT params for $name",
468             summary => "All template parameters passed to template $name",
469             report => qq{
$dump
},
470             );
471              
472 1         80 return;
473             }
474              
475              
476             1;
477              
478             __END__