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              
3 9     9   560282 use Template 2.0;
  9         329382  
  9         303  
4 9     9   2088 use CGI::Application 4.0;
  9         8690  
  9         197  
5 9     9   58 use Carp;
  9         24  
  9         730  
6 9     9   51 use File::Spec ();
  9         19  
  9         135  
7 9     9   55 use Scalar::Util ();
  9         18  
  9         196  
8              
9 9     9   42 use strict;
  9         16  
  9         324  
10 9     9   45 use vars qw($VERSION @EXPORT);
  9         15  
  9         961  
11              
12             $VERSION = '1.05';
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   17739 my $pkg = shift;
27 9         28 my $callpkg = caller;
28 9     9   44 no strict 'refs';
  9         14  
  9         3270  
29 9         37 foreach my $sym (@EXPORT) {
30 63         75 *{"${callpkg}::$sym"} = \&{$sym};
  63         307  
  63         128  
31             }
32 9 100       62 $callpkg->tt_config(@_) if @_;
33 9 50       94 if ($callpkg->isa('CGI::Application')) {
34 9         73 $callpkg->new_hook('tt_pre_process');
35 9         83 $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 everytime this method is called
50             # during a request cycle.
51             #
52             sub tt_obj {
53 29     29 1 44189 my $self = shift;
54              
55 29         85 my ($tt, $options, $frompkg) = _get_object_or_options($self);
56              
57 29 100       93 if (!$tt) {
58 18         43 my $tt_options = $options->{TEMPLATE_OPTIONS};
59 18 50       27 if (keys %{$options->{TEMPLATE_OPTIONS}}) {
  18         1072  
60 18   33     360 $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     299940 _set_object($frompkg||$self, $tt);
65             }
66 29         153 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 22887 my $self = shift;
79 18 100       77 my $class = ref $self ? ref $self : $self;
80              
81 18         33 my $tt_config;
82 18 100       54 if (ref $self) {
83 16 50 33     156 die "Calling tt_config after the tt object has already been created" if @_ && defined $self->{__TT};
84 16   50     121 $tt_config = $self->{__TT_CONFIG} ||= {};
85             } else {
86 9     9   57 no strict 'refs';
  9         17  
  9         14587  
87 2   50     2 ${$class.'::__TT_CONFIG'} ||= {};
  2         23  
88 2         2 $tt_config = ${$class.'::__TT_CONFIG'};
  2         6  
89             }
90              
91 18 50       69 if (@_) {
92 18         30 my $props;
93 18 50       59 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         134 $props = CGI::Application->_cap_hash({ @_ });
98             }
99              
100 18         331 my %options;
101             # Check for TEMPLATE_OPTIONS
102 18 50       198 if ($props->{TEMPLATE_OPTIONS}) {
103 18 50       138 carp "tt_config error: parameter TEMPLATE_OPTIONS is not a hash reference"
104             if Scalar::Util::reftype($props->{TEMPLATE_OPTIONS}) ne 'HASH';
105 18         65 $tt_config->{TEMPLATE_OPTIONS} = delete $props->{TEMPLATE_OPTIONS};
106             }
107              
108             # Check for TEMPLATE_NAME_GENERATOR
109 18 100       71 if ($props->{TEMPLATE_NAME_GENERATOR}) {
110 1 50       8 carp "tt_config error: parameter TEMPLATE_NAME_GENERATOR is not a subroutine reference"
111             if Scalar::Util::reftype($props->{TEMPLATE_NAME_GENERATOR}) ne 'CODE';
112 1         3 $tt_config->{TEMPLATE_NAME_GENERATOR} = delete $props->{TEMPLATE_NAME_GENERATOR};
113             }
114              
115             # Check for TEMPLATE_PRECOMPILE_FILETEST
116 18 100       58 if ($props->{TEMPLATE_PRECOMPILE_FILETEST}) {
117 6 50 100     56 carp "tt_config error: parameter TEMPLATE_PRECOMPILE_FILETEST is not a subroutine reference or regexp or string"
      66        
118             if defined Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_FILETEST})
119             && Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_FILETEST}) ne 'CODE'
120             && overload::StrVal($props->{TEMPLATE_PRECOMPILE_FILETEST}) !~ /^Regexp=/;
121 6         36 $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       58 if( $props->{TEMPLATE_PRECOMPILE_DIR} ) {
129 6         14 my $type = Scalar::Util::reftype($props->{TEMPLATE_PRECOMPILE_DIR});
130 6 50 33     24 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 6 50 33     26 my @dirs = ($type && $type eq 'ARRAY') ? @{$props->{TEMPLATE_PRECOMPILE_DIR}}
  0         0  
135             : ($props->{TEMPLATE_PRECOMPILE_DIR});
136 6         13 delete $props->{TEMPLATE_PRECOMPILE_DIR};
137 6         22 my $tt = $self->tt_obj;
138 6         12 my $junk = '';
139 6     0   25 my $filetester = sub { 1 };
  0         0  
140 6 50       21 if ($tt_config->{TEMPLATE_PRECOMPILE_FILETEST}) {
141 6 100       43 if (! defined Scalar::Util::reftype($tt_config->{TEMPLATE_PRECOMPILE_FILETEST})) {
    100          
    50          
142 2     4   12 $filetester = sub { $_[0] =~ /\.$tt_config->{TEMPLATE_PRECOMPILE_FILETEST}$/ };
  4         420  
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   25 $filetester = sub { $_[0] =~ $tt_config->{TEMPLATE_PRECOMPILE_FILETEST} };
  4         181  
147             }
148             }
149 6         56 require File::Find;
150             File::Find::find(
151             sub {
152 12     12   149 my $file = $File::Find::name;
153 12 100       28 return unless $filetester->($file);
154 3 50       93 if( !-d $file ) {
155 3         19 $tt->process( $file, {}, \$junk );
156             }
157             },
158 6         33 map { File::Spec->rel2abs($_) } @dirs,
  6         739  
159             );
160            
161             }
162            
163             # If there are still entries left in $props then they are invalid
164 18 50       55830 carp "Invalid option(s) (".join(', ', keys %$props).") passed to tt_config" if %$props;
165             }
166              
167 18         67 $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 73187 my $self = shift;
182 29         56 my @data = @_;
183              
184             # Define the params stash if it doesn't exist
185 29   100     333 $self->{__TT_PARAMS} ||= {};
186              
187 29 100       77 if (@data) {
188 16         57 my $params = $self->{__TT_PARAMS};
189 16         27 my $newparams = {};
190 16 100       67 if (ref $data[0] eq 'HASH') {
    50          
191             # hashref
192 8         13 %$newparams = %{ $data[0] };
  8         35  
193             } elsif ( (@data % 2) == 0 ) {
194 8         30 %$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         66 @$params{keys %$newparams} = values %$newparams;
201             }
202              
203 29         130 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 15521 my $self = shift;
281 13         29 my $file = shift;
282 13         24 my $vars = shift;
283 13         33 my $html = '';
284              
285 13 50       86 my $can_call_hook = UNIVERSAL::can($self, 'call_hook') ? 1 : 0;
286              
287 13 100 100     119 if (! defined($vars) && (Scalar::Util::reftype($file)||'') eq 'HASH') {
      100        
288 4         8 $vars = $file;
289 4         8 $file = undef;
290             }
291 13   66     62 $file ||= $self->tt_template_name(1);
292 13   100     52 $vars ||= {};
293 13         26 my $template_name = $file;
294              
295             # Call the load_tmpl hook that is part of CGI::Application
296 13 50       71 $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       461 $self->tt_pre_process($file, $vars) if $self->can('tt_pre_process');
305 13 50       125 $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         377 my %params = ( %{ $self->tt_params() }, %$vars );
  13         50  
310              
311             # Add c => $self in as a param for convenient access to sessions and such
312 13   33     93 $params{c} ||= $self;
313              
314 13 100       91 $self->tt_obj->process($file, \%params, \$html) || croak $self->tt_obj->error();
315              
316             # Call tt_post_process hook
317 12 100       403977 $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         592 _tt_add_devpopup_info($self, $template_name, \%params);
321              
322 12         99 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 25695 my $self = shift;
336              
337 3 100       20 return $self->tt_obj->context->load_templates->[0]->include_path unless(@_);
338 2 50       10 $self->tt_obj->context->load_templates->[0]->include_path(ref($_[0]) ? $_[0] : [@_]);
339              
340 2         98 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 42 my $self = shift;
355              
356 9         22 my ($tt, $options, $frompkg) = _get_object_or_options($self);
357              
358 9   100     44 my $func = $options->{TEMPLATE_NAME_GENERATOR} || \&__tt_template_name;
359 9         27 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   11 my $self = shift;
380 7   100     17 my $uplevel = shift || 0;
381              
382             # the directory is based on the object's package name
383 7         132 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         68 (caller(2+$uplevel))[3] =~ /([^:]+)$/;
388 7         20 my $name = $1;
389              
390 7         91 return File::Spec->catfile($dir, $name.'.tmpl');
391             }
392              
393             ##
394             ## Private methods
395             ##
396             sub _set_object {
397 18     18   60 my $self = shift;
398 18         39 my $tt = shift;
399 18 100       84 my $class = ref $self ? ref $self : $self;
400              
401 18 100       57 if (ref $self) {
402 16         62 $self->{__TT_OBJECT} = $tt;
403             } else {
404 9     9   156 no strict 'refs';
  9         19  
  9         1259  
405 2         3 ${$class.'::__TT_OBJECT'} = $tt;
  2         17  
406             }
407             }
408              
409             sub _get_object_or_options {
410 38     38   54 my $self = shift;
411 38 50       120 my $class = ref $self ? ref $self : $self;
412              
413             # Handle the simple case by looking in the object first
414 38 50       104 if (ref $self) {
415 38 100       131 return ($self->{__TT_OBJECT}, $self->{__TT_CONFIG}) if $self->{__TT_OBJECT};
416 31 100       140 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         59 require Class::ISA;
424 8         35 foreach my $super ($class, Class::ISA::super_path($class)) {
425 9     9   47 no strict 'refs';
  9         13  
  9         3870  
426 8 100       308 return (${$super.'::__TT_OBJECT'}, ${$super.'::__TT_CONFIG'}, $super) if ${$super.'::__TT_OBJECT'};
  6         15  
  6         26  
  8         45  
427 2 50       3 return (undef, ${$super.'::__TT_CONFIG'}, $super) if ${$super.'::__TT_CONFIG'};
  2         10  
  2         14  
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   28 my $self = shift;
444 12         30 my $name = shift;
445 12         28 my $params = shift;
446              
447 12 100       153 return unless UNIVERSAL::can($self, 'devpopup');
448              
449 1         5 my %params = %$params;
450 1         5 foreach my $key (keys %params) {
451 3 100       16 if (my $class = Scalar::Util::blessed($params{$key})) {
452 1         4 $params{$key} = "Object:$class";
453             }
454             }
455              
456 1         1131 require Data::Dumper;
457 1         7710 my $dumper = Data::Dumper->new([\%params]);
458 1         37 $dumper->Varname('Params');
459 1         16 $dumper->Indent(2);
460 1         14 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         79 $dump =~ s/([^\n\r\t !\#\$%\(-;=?-~])/sprintf "&#x%X;", ord($1)/ge;
  21         84  
465              
466 1         11 $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         104 return;
473             }
474              
475              
476             1;
477             __END__