File Coverage

blib/lib/WebDyne.pm
Criterion Covered Total %
statement 733 1132 64.7
branch 142 402 35.3
condition 104 378 27.5
subroutine 115 155 74.1
pod 0 46 0.0
total 1094 2113 51.7


line stmt bran cond sub pod time code
1             #
2             # This file is part of WebDyne.
3             #
4             # This software is Copyright (c) 2017 by Andrew Speer .
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU General Public License, Version 2, June 1991
9             #
10             # Full license text is available at:
11             #
12             #
13             #
14             package WebDyne;
15              
16              
17             # Packace init, attempt to load optional Time::HiRes module
18             sub BEGIN {
19 2     2   55076 local $SIG{__DIE__};
20 2         5 $^W=0;
21 2 50   2   106 eval("use Time::HiRes qw(time)") || eval {undef};
  2         343  
  2         571  
  2         2038  
  2         7  
22             }
23              
24              
25             # Pragma
26             #
27 2     2   14 use strict qw(vars);
  2         3  
  2         49  
28 2     2   9 use vars qw($VERSION %CGI_TAG_WEBDYNE @ISA $AUTOLOAD);
  2         2  
  2         116  
29 2     2   9 use warnings;
  2         3  
  2         58  
30 2     2   8 no warnings qw(uninitialized redefine once);
  2         2  
  2         65  
31              
32              
33             # WebDyne constants, base modules
34             #
35 2     2   638 use WebDyne::Constant;
  2         7  
  2         585  
36 2     2   12 use WebDyne::Base;
  2         3  
  2         12  
37              
38              
39             # External Modules
40             #
41 2     2   808 use Storable;
  2         4498  
  2         106  
42 2     2   271 use HTTP::Status qw(is_success is_error is_redirect RC_OK RC_FOUND RC_NOT_FOUND);
  2         3013  
  2         148  
43 2     2   10 use Fcntl;
  2         4  
  2         347  
44 2     2   572 use Tie::IxHash;
  2         5760  
  2         54  
45 2     2   16 use Digest::MD5 qw(md5_hex);
  2         3  
  2         85  
46 2     2   10 use File::Spec::Unix;
  2         4  
  2         40  
47 2     2   8 use Data::Dumper;
  2         3  
  2         70  
48 2     2   750 use overload;
  2         668  
  2         12  
49              
50              
51             # Inherit from the Compile module, not loaded until needed though.
52             #
53             @ISA=qw(WebDyne::Compile);
54              
55              
56             # Version information
57             #
58             $VERSION='1.247';
59              
60              
61             # Debug load
62             #
63             0 && debug("%s loaded, version $VERSION", __PACKAGE__);
64              
65              
66             # Shortcut error handler, save using ISA;
67             #
68             require WebDyne::Err;
69             *err_html=\&WebDyne::Err::err_html || *err_html;
70             *err_eval=\&WebDyne::Err::err_eval || *err_eval;
71              
72              
73             # Our webdyne "special" tags
74             #
75             %CGI_TAG_WEBDYNE=map {$_ => 1} (
76              
77             'block',
78             'perl',
79             'subst',
80             'dump',
81             'include',
82              
83             );
84              
85              
86             # Var to hold package wide hash, for data shared across package
87             #
88             my %Package;
89              
90              
91             # Do some class wide initialisation
92             #
93             &init_class();
94              
95              
96             # Eval safe not effective - die if turned on
97             #
98             if ($WEBDYNE_EVAL_SAFE) {die "WEBDYNE_EVAL_SAFE disabled in this version\n"}
99              
100              
101             # All done. Positive return
102             #
103             1;
104              
105              
106             #==================================================================================================
107              
108              
109             sub handler : method {
110              
111              
112             # Get self ref/class, request ref
113             #
114 10     10 0 2818 my ($self, $r, $param_hr)=@_;
115 10         39 0 && debug("handler called with self $self, r $r, MP2 $MP2");
116              
117              
118             # Start timer so we can optionally keep stats on how long handler takes to run
119             #
120 10         43 my $time=time();
121              
122              
123             # Work out class and correct self ref
124             #
125 10   33     71 my $class=ref($self) || do {
126              
127              
128             # Need new self ref, as self is actually class. Do inline so quicker than -> new
129             #
130             my %self=(
131              
132             _time => $time,
133             _r => $r,
134             %{delete $self->{'_self'}},
135              
136             );
137             $self=bless \%self, $self;
138             ref($self);
139              
140              
141             };
142              
143              
144             # Setup error handlers
145             #
146             local $SIG{'__DIE__'}=sub {
147 0     0   0 0 && debug('in __DIE__ sig handler, caller %s', join(',', (caller(0))[0..3]));
148 0         0 return err (@_)
149 10         74 };
150             local $SIG{'__WARN__'}=sub {
151 0     0   0 0 && debug('in __WARN__ sig handler, caller %s', join(',', (caller(0))[0..3]));
152 0         0 return err (@_)
153             }
154 10 50       41 if $WEBDYNE_WARNINGS_FATAL;
155              
156              
157             # Debug
158             #
159 10         15 0 && debug(
160             "in WebDyne::handler. class $class, self $self, r $r, param_hr %s",
161             Dumper($param_hr));
162              
163              
164             # Skip all processing if header request only
165             #
166 10 50       42 if ($r->header_only()) {return &head_request($r)}
  0         0  
167              
168              
169             # Debug
170             #
171 10         16 0 && debug(
172             "enter handler, r $r, location %s file %s, param %s",
173             $r->location(), $r->filename(), Dumper($param_hr));
174              
175              
176             # Get full path, mtime of source file, check file exists
177             #
178 10   50     38 my $srce_pn=$r->filename() ||
179             return $self->err_html('unable to get request filename');
180 10   33     292 my $srce_mtime=(-f $srce_pn && (stat(_))[9]) || do {
181              
182             # File not found, we don't want to handle this anymore ..
183             #
184             0 && debug("srce_mtime for file '$srce_pn' not found, could not stat !");
185             return &Apache::DECLINED;
186              
187             };
188 10         22 0 && debug("srce_pn $srce_pn, srce_mtime (real) $srce_mtime");
189              
190              
191             # Used to use inode as unique identifier for file in cache, but that
192             # did not take into account the fact that the same file may have diff
193             # Apache locations (and thus WebDyne::Chain) handlers for the same
194             # physical file. So we now use an md5 hash of handler, location and
195             # file name, but the var name is still "inode";
196             #
197             RENDER_BEGIN:
198             my $srce_inode=(
199 10   50     205 $self->{'_inode'} ||= md5_hex(ref($self), $r->location, $srce_pn)
      33        
200             ||
201             return $self->err_html("could not get md5 for file $srce_pn, $!"));
202 10         25 0 && debug("srce_inode $srce_inode");
203              
204              
205             # Var to hold pointer to cached metadata area, so we are not constantly
206             # dereferencing $Package{'_cache'}{$srce_inode};
207             #
208             my $cache_inode_hr=(
209 10   50     190 $Package{'_cache'}{$srce_inode} ||= {
210              
211             data => undef, # holds compiled representation of html/psp file
212             mtime => undef, # last modified time of the Storable disk cache file
213             nrun => undef, # number of times this page run by this mod_perl child
214             lrun => undef, # last run time of this page by this mod_perl child
215              
216             # Created if needed
217             #
218             # meta => undef, # page meta data, held in meta section or supplied by add-on modules
219             # eval_cr => undef, # where anonymous sub's representing eval'd perl code within this page are held
220             # perl_init => undef, # flags that perl code in __PERL__ block has been init'd (run once at page load)
221              
222             }) || return $self->err_html('unable to initialize cache_inode_hr ref');
223              
224              
225             # Get "effective" source mtime, as may be a combination of things including
226             # template (eg menu) mtime. Here so can be subclassed by other handler like
227             # menu systems
228             #
229 10         20 0 && debug("about to call source_mtime, self $self");
230             $srce_mtime=${
231 10   33     19 $self->source_mtime($srce_mtime) || return $self->err_html()}
232             || $srce_mtime;
233 10         19 0 && debug("srce_pn $srce_pn, srce_mtime (computed) $srce_mtime");
234              
235              
236             # Need to stat cache file mtime in case another process has updated it (ie via self->cache_compile(1)) call,
237             # which will make our memory cache stale. Would like to not have to do this stat one day, perhaps via shmem
238             # or similar check
239             #
240             # Only do if cache directory defined
241             #
242 10         29 my ($cache_pn, $cache_mtime);
243 10 50       32 if ($WEBDYNE_CACHE_DN) {
244 0         0 0 && debug("webdyne_cache_dn $WEBDYNE_CACHE_DN");
245 0         0 $cache_pn=File::Spec->catfile($WEBDYNE_CACHE_DN, $srce_inode);
246 0   0     0 $cache_mtime=((-f $cache_pn) && (stat(_))[9]);
247             }
248             else {
249 10         17 0 && debug('no webdyne_cache_dn');
250             }
251              
252              
253             # Test if compile/reload needed
254             #
255 10 50 33     134 if ($WEBDYNE_RELOAD || $self->{'_compile'} || ($cache_inode_hr->{'mtime'} < $srce_mtime) || ($cache_mtime > $cache_inode_hr->{'mtime'})) {
      33        
      33        
256              
257              
258             # Debug
259             #
260             0 && debug(
261             "compile/reload needed _compile %s, cache_inode_hr mtime %s, srce_mtime $srce_mtime, WEBDYNE::RELOAD $WEBDYNE::RELOAD",
262 10         19 $self->{'_compile'}, $cache_inode_hr->{'mtime'});
263              
264              
265             # use Module::Reload to reload modules
266             #
267 10 50       22 if ($WEBDYNE_RELOAD) {
268 0         0 local $SIG{'__DIE__'};
269 0 0       0 unless ($INC{'Module/Reload.pm'}) {
270 0         0 0 && debug('loading Module::Reload');
271 0         0 eval {require Module::Reload};
  0         0  
272 0 0       0 return $self->err_html('unable to load Module::Reload - is it installed ?') if $@;
273             }
274 0         0 0 && debug('running Module::Reload->check');
275 0         0 Module::Reload->check();
276             }
277              
278              
279             # Null out cache_inode to clear any flags
280             #
281 10         13 foreach my $key (keys %{$cache_inode_hr}) {
  10         77  
282 40         67 $cache_inode_hr->{$key}=undef;
283             }
284              
285              
286             # Try to clear/reset package name space if possible
287             #
288             eval {
289 10         98 require Symbol;
290 10         93 &Symbol::delete_package("WebDyne::${srce_inode}");
291 10 50       28 } || do {
292 10 50       448 eval {undef} if $@; #clear $@ after error above
  0         0  
293 10         16 my $stash_hr=*{"WebDyne::${srce_inode}::"}{HASH};
  10         161  
294 10         24 foreach (keys %{$stash_hr}) {
  10         37  
295 0         0 undef *{"WebDyne::${srce_inode}::${_}"};
  0         0  
296             }
297 10         24 %{$stash_hr}=();
  10         20  
298 10         34 delete *WebDyne::{'HASH'}->{$srce_inode};
299             };
300              
301              
302             # Debug
303             #
304 10         16 0 && debug("srce_pn $srce_pn, cache_pn $cache_pn, mtime $cache_mtime");
305              
306              
307 10         27 my $container_ar;
308 10 50 33     83 if ($self->{'_compile'} || ($cache_mtime < $srce_mtime)) {
309              
310              
311             # Debug
312             #
313 10         19 0 && debug("compiling srce: $srce_pn, dest $cache_pn");
314              
315              
316             # Recompile from source
317             #
318 10 50 0     18 eval {require WebDyne::Compile}
  10         830  
319             || return $self->err_html(
320             errsubst('unable to load WebDyne:Compile, %s', $@ || 'undefined error'));
321              
322              
323             # Source newer than compiled version, must recompile file
324             #
325 10   50     115 $container_ar=$self->compile(
326             {
327              
328             srce => $srce_pn,
329             dest => $cache_pn,
330              
331             }) || return $self->err_html();
332              
333              
334             # Check for any unhandled errors during compile
335             #
336 10 50       79 errstr() && return $self->err_html();
337              
338              
339             # Update mtime flag, or use current time if we were not able to read
340             # cache file (probably because temp dir was not writable - which would
341             # generated a warning in the logs from the Compile module, so no point
342             # making a fuss about it here anymore.
343             #
344 10 50       22 $cache_mtime=(stat($cache_pn))[9] if $cache_pn; # ||
345             #return $self->err_html("could not stat cache file '$cache_pn'");
346 10   33     73 $cache_inode_hr->{'mtime'}=$cache_mtime || time();
347              
348              
349             }
350             else {
351              
352             # Debug
353             #
354 0         0 0 && debug("loading from disk cache");
355              
356              
357             # Load from storeable file
358             #
359 0   0     0 $container_ar=Storable::lock_retrieve($cache_pn) ||
360             return $self->err_html("Storable error when retreiveing cached file '$cache_pn', $!");
361              
362              
363             # Update mtime flag
364             #
365 0         0 $cache_inode_hr->{'mtime'}=$cache_mtime;
366              
367              
368             # Re-run perl-init for this node. Not done above because handled in compile if needed
369             #
370 0 0       0 if (my $meta_hr=$container_ar->[0]) {
371 0 0       0 if (my $perl_ar=$meta_hr->{'perl'}) {
372 0   0     0 my $perl_debug_ar=$meta_hr->{'perl_debug'} ||
373             return err ('unable to load perl_debug array reference');
374 0 0       0 $self->perl_init($perl_ar, $perl_debug_ar) || return $self->err_html();
375             }
376             }
377             }
378              
379              
380             # Done, install into memory cache
381             #
382 10 50 33     77 if (my $meta_hr=$container_ar->[0] and $cache_inode_hr->{'meta'}) {
    50          
383              
384             # Need to merge meta info
385             #
386 0   0     0 foreach (keys %{$meta_hr}) {$cache_inode_hr->{'meta'}{$_} ||= $meta_hr->{$_}}
  0         0  
  0         0  
387              
388             }
389             elsif ($meta_hr) {
390              
391             # No merge - just use from container
392             #
393 10         35 $cache_inode_hr->{'meta'}=$meta_hr;
394              
395             }
396 10         19 $cache_inode_hr->{'data'}=$container_ar->[1];
397              
398             # Corner case. Delete _CGI if WEBDYNE_CGI_EXPAND_PARAM set to force re-read of
399             # CGI params in case was set in section - which means would not be seen
400             # early enough. Will only happen after first compile, so no major performance
401             # impact on CGI object recreation
402             #
403             # Update: Re-init rather than delete or WebDyne::State worn't work
404             #
405             #delete $self->{'_CGI'} if $WEBDYNE_CGI_PARAM_EXPAND;
406 10 50 33     47 if ((my $cgi_or=$self->{'_CGI'}) && $WEBDYNE_CGI_PARAM_EXPAND) {
407 10         48 $cgi_or->init();
408             }
409              
410              
411             }
412             else {
413              
414 0         0 0 && debug('no compile or disk cache fetch needed - getting from memory cache');
415              
416             }
417              
418              
419             # Separate meta and actual data into separate vars for ease of use
420             #
421 10         2666 my ($meta_hr, $data_ar)=@{$cache_inode_hr}{qw(meta data)};
  10         34  
422 10         17 0 && debug('meta_hr %s, ', Dumper($meta_hr));
423              
424              
425             # Custom handler ?
426             #
427 10 50 33     65 if (my $handler_ar=$meta_hr->{'handler'} || $r->dir_config('WebDyneHandler')) {
428 0 0       0 my ($handler, $handler_param_hr)=ref($handler_ar) ? @{$handler_ar} : $handler_ar;
  0         0  
429 0 0       0 if (ref($self) ne $handler) {
430 0         0 0 && debug("passing to custom handler '$handler', param %s", Dumper($handler_param_hr));
431 0 0       0 unless ($Package{'_handler_load'}{$handler}) {
432 0         0 0 && debug("need to load handler '$handler' - trying");
433 0         0 (my $handler_fn=$handler)=~s/::/\//g;
434 0         0 $handler_fn.='.pm';
435 0 0       0 eval {require $handler_fn} ||
  0         0  
436             return $self->err_html("unable to load custom handler '$handler', $@");
437 0 0       0 UNIVERSAL::can($handler, 'handler') ||
438             return $self->err_html("custom handler '$handler' does not seem to have a 'handler' method to call");
439 0         0 0 && debug('loaded OK');
440 0         0 $Package{'_handler_load'}{$handler}++;
441             }
442 0         0 my %handler_param_hr=(%{$param_hr}, %{$handler_param_hr}, meta => $meta_hr);
  0         0  
  0         0  
443 0         0 bless $self, $handler;
444              
445             # Force recalc of inode in next handler so recompile done
446 0         0 delete $self->{'_inode'};
447              
448             # Add meta-data. Something inefficient here, why supplying as handler param and
449             # self attrib ? If don't do it Fake/FastCGI request handler breaks but Apache does
450             # not ?
451 0         0 $self->{'_meta_hr'}=$meta_hr;
452 0         0 return &{"${handler}::handler"}($self, $r, \%handler_param_hr);
  0         0  
453             }
454             }
455              
456              
457             # Contain cache code ?
458             #
459 10 50 33     62 if ((my $cache=($self->{'_cache'} || $meta_hr->{'cache'})) && !$self->{'_cache_run_fg'}++) {
      33        
460 0         0 0 && debug("found cache routine $cache, adding to inode $srce_inode");
461 0         0 my $cache_inode;
462 0         0 my $eval_cr=$Package{'_eval_cr'}{'!'};
463 0 0       0 if (ref($cache) eq 'CODE') {
464 0         0 my %param=(
465             cache_cr => $cache,
466             srce_inode => $srce_inode
467             );
468             $cache_inode=${
469 0 0 0     0 $eval_cr->($self, undef, \%param, q[$_[1]->{'cache_cr'}->($_[0], $_[1]->{'srce_inode'})], 0) ||
  0         0  
470             return $self->err_html(
471             errsubst(
472             'error in cache code: %s', errstr() || $@ || 'no inode returned'
473             ));
474             }
475             }
476             else {
477             $cache_inode=${
478 0 0 0     0 $eval_cr->($self, undef, $srce_inode, $cache, 0) ||
  0         0  
479             return $self->err_html(
480             errsubst(
481             'error in cache code: %s', errstr() || $@ || 'no inode returned'
482             ));
483             }
484             }
485 0 0       0 $cache_inode=$cache_inode ? md5_hex($srce_inode, $cache_inode) : $self->{'_inode'};
486              
487             # Will probably make inodes with algorithm below some day so we can implement a "maxfiles type limit on
488             # the number of cache files generated. Not today though ..
489             #
490             #$cache_inode=$cache_inode ? $srce_inode .'_'. md5_hex($cache_inode) : $self->{'_inode'};
491 0         0 0 && debug("cache inode $cache_inode, compile %s", $self->{'_compile'});
492              
493 0 0 0     0 if (($cache_inode ne $srce_inode) || $self->{'_compile'}) {
494              
495             # Using a cache file, different inode.
496             #
497 0         0 0 && debug("goto RENDER_BEGIN, inode node was $srce_inode, now $cache_inode");
498 0         0 $self->{'_inode'}=$cache_inode;
499 0         0 goto RENDER_BEGIN;
500              
501             #return &handler($self,$r,$param_hr); #should work instead of goto for pendants
502             }
503              
504             }
505              
506              
507             # Is it plain HTML which can be/is pre-rendered and stored on disk ? Note to self, leave here - should
508             # run after any cache code is run, as that may change inode.
509             #
510 10         17 my $html_sr;
511 10 100 66     78 if ($self->{'_static'} || ($meta_hr && ($meta_hr->{'html'} || $meta_hr->{'static'}))) {
      33        
      33        
512              
513             #my $cache_pn=File::Spec->catfile($WEBDYNE_CACHE_DN, $srce_inode);
514 1 50 33     12 if ($cache_pn && (-f (my $fn="${cache_pn}.html")) && ((stat(_))[9] >= $srce_mtime) && !$self->{'_compile'}) {
    50 33        
      0        
515              
516             # Cache file exists, and is not stale, and user/cache code does not want a recompile. Tell Apache or FCGI
517             # to serve it up directly.
518             #
519 0         0 0 && debug("returning pre-rendered file ${cache_pn}.html");
520 0 0 0     0 if ($MP2 || $ENV{'FCGI_ROLE'}) {
521              
522             # Do this way for mod_perl2, FCGI. Note to self need r->output_filter or
523             # Apache 2 seems to add junk characters at end of output
524             #
525 0         0 my $r_child=$r->lookup_file($fn, $r->output_filters);
526 0         0 $r_child->handler('default-handler');
527 0         0 $r_child->content_type($WEBDYNE_CONTENT_TYPE_HTML);
528              
529             # Apache bug ? Need to set content type on r also
530 0         0 $r->content_type($WEBDYNE_CONTENT_TYPE_HTML);
531 0         0 return $r_child->run();
532              
533             }
534             else {
535              
536             # This way for older versions of Apache, other request handlers
537             #
538 0         0 $r->filename($fn);
539 0         0 $r->handler('default-handler');
540 0         0 $r->content_type($WEBDYNE_CONTENT_TYPE_HTML);
541 0         0 return &Apache::DECLINED;
542             }
543             }
544             elsif ($cache_pn) {
545              
546             # Cache file defined, but out of date of non-existant. Register callback handler to write HTML output
547             # after render complete
548             #
549 0         0 0 && debug('storing to disk cache html %s', \$data_ar->[0]);
550             my $cr=sub {
551             &cache_html(
552 0 0 0 0   0 "${cache_pn}.html", ($meta_hr->{'static'} || $self->{'_static'}) ? $html_sr : \$data_ar->[0])
553 0         0 };
554 0 0       0 $MP2 ? $r->pool->cleanup_register($cr) : $r->register_cleanup($cr);
555             }
556             else {
557              
558             # No cache directory, store in memory cache. Each apache process will get a different version, but will
559             # at least still be only compiled once for each version.
560             #
561 1         3 0 && debug('storing to memory cache html %s', \$data_ar->[0]);
562             my $cr=sub {
563             $cache_inode_hr->{'data'}=[
564 1 50 33 1   51 ($meta_hr->{'static'} || $self->{'_static'}) ? ${$html_sr} : $data_ar->[0]]
  0         0  
565 1         6 };
566 1 50       7 $MP2 ? $r->pool->cleanup_register($cr) : $r->register_cleanup($cr);
567             }
568              
569             }
570              
571              
572             # Debug
573             #
574             #0 && debug('about to render');
575              
576              
577             # Set default content type to text/html, can be overridden by render code if needed
578             #
579             #$r->content_type('text/html');
580 10         78 $r->content_type($WEBDYNE_CONTENT_TYPE_HTML);
581              
582              
583             # Redirect 'print' function to our own routine for later output
584             #
585 10   33     82 my $select=($self->{'_select'} ||= CORE::select());
586 10         14 0 && debug("select handle is currently $select, changing to *WEBDYNE");
587 10 50       123 tie(*WEBDYNE, 'WebDyne::TieHandle', $self) ||
588             return $self->err_html("unable to tie output to 'WebDyne::TieHandle', $!");
589 10 50       41 CORE::select WEBDYNE if $select;
590              
591              
592             # Get the actual html. The main event - convert data_ar to html
593             #
594 10   33     80 $html_sr=$self->render({data => $data_ar, param => $param_hr}) || do {
595              
596              
597             # Our render routine returned an error. Debug
598             #
599             RENDER_ERROR:
600             0 && debug("render error $r, select $select");
601              
602              
603             # Return error
604             #
605             0 && debug("selecting back to $select for error");
606             CORE::select $select if $select;
607             untie *WEBDYNE;
608             return $self->err_html();
609              
610              
611             };
612              
613              
614             # Done with STDOUT redirect
615             #
616 10         22 0 && debug("selecting back to $select");
617 10 50       84 CORE::select $select if $select;
618 10         54 untie *WEBDYNE;
619              
620              
621             # Check for any unhandled errors during render - render may have returned OK, but
622             # maybe an error occurred along the way that was not passed back ..
623             #
624 10         18 0 && debug('errstr after render %s', errstr());
625 10 50       36 errstr() && return $self->err_html();
626 10 50       35 &CGI::cgi_error() && return $self->err_html(&CGI::cgi_error());
627              
628              
629             # Check for any blocks that user wanted rendered but were
630             # not present anywhere
631             #
632             #if ($WEBDYNE_DELAYED_BLOCK_RENDER && (my $block_param_hr=delete $self->{'_block_param'})) {
633 10 100       472 if (my $block_param_hr=delete $self->{'_block_param'}) {
634 1         2 my @block_error;
635 1         2 foreach my $block_name (keys %{$block_param_hr}) {
  1         5  
636 4 50       9 unless (exists $self->{'_block_render'}{$block_name}) {
637 0         0 push @block_error, $block_name;
638             }
639             }
640 1 50       3 if (@block_error) {
641 0         0 0 && debug('found un-rendered blocks %s', Dumper(\@block_error));
642             return $self->err_html(
643 0         0 err ('unable to locate block(s) %s for render', join(', ', map {"'$_'"} @block_error)))
  0         0  
644             }
645             }
646              
647              
648             # If no error, status must be ok unless otherwise set
649             #
650 10 50       68 $r->status(RC_OK) unless $r->status();
651 10         18 0 && debug('r status set, %s', $r->status());
652              
653              
654             # Formulate header, calc length of return.
655             #
656             # Modify to remove error checking - WebDyne::FakeRequest does not supply
657             # hash ref, so error generated. No real need to check
658             #
659 10         47 my $header_out_hr=$r->headers_out(); # || return err();
660             my %header_out=(
661              
662 10         149 'Content-Length' => length ${$html_sr},
663              
664 10   33     22 ($meta_hr->{'no_cache'} || $WEBDYNE_NO_CACHE) && (
665             'Cache-Control' => 'no-cache',
666             'Pragma' => 'no-cache',
667             'Expires' => '-5'
668             )
669              
670             );
671 10         38 foreach (keys %header_out) {$header_out_hr->{$_}=$header_out{$_}}
  40         67  
672              
673              
674             # Debug
675             #
676 10         15 0 && debug('sending header');
677              
678              
679             # Send header
680             #
681 10 50       53 $r->send_http_header() if !$MP2;
682              
683              
684             # Print. Commented out version only seems to work in Apache 1/mod_perl1
685             #
686             #$r->print($html_sr);
687 10 50       78 $MP2 ? $r->print(${$html_sr}) : $r->print($html_sr);
  0         0  
688              
689              
690             # Work out the form render time, log
691             #
692 10         102 RENDER_COMPLETE:
693             my $time_render=sprintf('%0.4f', time()-$time);
694 10         17 0 && debug("form $srce_pn render time $time_render");
695              
696              
697             # Do we need to do house cleaning on cache after this run ? If so
698             # add a perl handler to do it after we finish
699             #
700 10 50 33     65 if (
    50 33        
      33        
701             $WEBDYNE_CACHE_CHECK_FREQ
702             &&
703             ($r eq ($r->main() || $r)) &&
704             !((my $nrun=++$Package{'_nrun'}) % $WEBDYNE_CACHE_CHECK_FREQ)
705             ) {
706              
707              
708             # Debug
709             #
710 0         0 0 && debug("run $nrun times, scheduling cache clean");
711              
712              
713             # Yes, we need to clean cache after finished
714             #
715 0     0   0 my $cr=sub {&cache_clean($Package{'_cache'})};
  0         0  
716 0 0       0 $MP2 ? $r->pool->cleanup_register($cr) : $r->register_cleanup($cr);
717              
718              
719             # Used to be sub { $self->cache_clean() }, but for some reason this
720             # made httpd peg at 100% CPU usage after cleanup. Removing $self ref
721             # fixed.
722             #
723              
724              
725             }
726             elsif ($WEBDYNE_CACHE_CHECK_FREQ) {
727              
728             # Only bother to update counters if we are checking cache periodically
729             #
730              
731              
732             # Update cache script frequency used, time used indicators, nrun=number
733             # of runs, lrun=last run time
734             #
735 10         24 $cache_inode_hr->{'nrun'}++;
736 10         28 $cache_inode_hr->{'lrun'}=time();
737              
738             }
739             else {
740              
741              
742             # Debug
743             #
744 0         0 0 && debug("run $nrun times, no cache check needed");
745              
746             }
747              
748              
749             # Debug exit
750             #
751 10         12 0 && debug("handler $r exit status %s, leaving with Apache::OK", $r->status); #, Dumper($self));
752              
753              
754             # Complete
755             #
756 10         35 HANDLER_COMPLETE:
757             return &Apache::OK;
758              
759              
760             }
761              
762              
763             sub eval_cr {
764              
765              
766             # Return eval subroutine ref for inode ($_[0]) and eval code ref ($_[1]). Avoid using
767             # var names so not available in eval code
768             #
769 43     43 0 151 eval("package WebDyne::$_[0]; $WebDyne::WEBDYNE_EVAL_USE_STRICT;\n" . "#line $_[2]\n" . "sub{${$_[1]}\n}");
  43     1   2873  
  1     1   6  
  1     1   2  
  1     1   37  
  1     1   6  
  1     1   2  
  1     1   33  
  1     1   6  
  1     1   2  
  1     1   41  
  1     1   6  
  1     1   1  
  1     1   34  
  1     1   9  
  1     1   1  
  1     1   33  
  1     1   7  
  1     1   2  
  1     1   35  
  1     1   6  
  1     1   2  
  1     1   36  
  1     1   6  
  1     1   2  
  1     1   33  
  1     1   10  
  1     1   2  
  1     1   49  
  1     1   6  
  1     1   2  
  1     1   33  
  1     1   9  
  1     1   26  
  1     1   68  
  1     1   10  
  1     1   2  
  1     1   33  
  1     1   11  
  1     1   1  
  1     1   49  
  1     1   10  
  1     1   2  
  1     1   28  
  1     1   7  
  1     1   2  
  1         37  
  1         5  
  1         2  
  1         26  
  1         5  
  1         2  
  1         24  
  1         7  
  1         1  
  1         35  
  1         5  
  1         2  
  1         25  
  1         6  
  1         2  
  1         27  
  1         5  
  1         2  
  1         26  
  1         6  
  1         1  
  1         30  
  1         6  
  1         2  
  1         28  
  1         10  
  1         3  
  1         40  
  1         7  
  1         1  
  1         28  
  1         9  
  1         2  
  1         33  
  1         8  
  1         2  
  1         31  
  1         7  
  1         4  
  1         37  
  1         6  
  1         2  
  1         27  
  1         9  
  1         3  
  1         30  
  1         5  
  1         3  
  1         20  
  1         9  
  1         5  
  1         38  
  1         10  
  1         3  
  1         44  
  1         8  
  1         3  
  1         37  
  1         9  
  1         3  
  1         32  
  1         6  
  1         5  
  1         33  
  1         8  
  1         3  
  1         38  
  1         8  
  1         3  
  1         42  
  1         14  
  1         4  
  1         60  
  1         7  
  1         2  
  1         41  
  1         8  
  1         2  
  1         51  
  1         7  
  1         2  
  1         36  
  1         5  
  1         2  
  1         33  
  1         6  
  1         4  
  1         33  
  1         6  
  1         2  
  1         27  
770              
771              
772             }
773              
774              
775             sub perl_init_cr {
776              
777 10     10 0 81 eval("package WebDyne::$_[0]; $WebDyne::WEBDYNE_EVAL_USE_STRICT;\n" . "#line $_[2]\n" . "${$_[1]}");
  10     1   1251  
  1     1   10  
  1     1   2  
  1     1   209  
  1     1   13  
  1     1   4  
  1     1   144  
  1     1   13  
  1     1   4  
  1         110  
  1         14  
  1         3  
  1         74  
  1         14  
  1         4  
  1         65  
  1         14  
  1         3  
  1         142  
  1         12  
  1         5  
  1         62  
  1         11  
  1         4  
  1         114  
  1         13  
  1         3  
  1         395  
778              
779             }
780              
781              
782             sub init_class {
783              
784              
785             # Try to load correct modules depending on Apache ver, taking special care
786             # with constants. This mess will disappear if we only support MP2
787             #
788 4 50   4 0 25 if ($MP2) {
    50          
789              
790 1         2 local $SIG{'__DIE__'};
791             eval {
792             #require Apache2;
793 1         2 require Apache::Log;
794 1         5 require Apache::Response;
795 1         3 require Apache::SubRequest;
796 1         6 require Apache::Const; Apache::Const->import(-compile => qw(OK DECLINED));
  2         8  
797 1         4 require APR::Table;
798 1 50       5 } || eval {
799 2         117 require Apache2::Log;
800 2         8 require Apache2::Response;
801 1         13 require Apache2::SubRequest;
802 1         4 require Apache2::Const; Apache2::Const->import(-compile => qw(OK DECLINED));
  1         6  
803 1         4 require APR::Table;
804             };
805 1 0       2 eval {undef} if $@;
  3         11  
806 1 0       4 unless (UNIVERSAL::can('Apache', 'OK')) {
807 1 0       3 if (UNIVERSAL::can('Apache2::Const', 'OK')) {
    0          
808 1         3 *Apache::OK=\&Apache2::Const::OK;
809 3         8 *Apache::DECLINED=\&Apache2::Const::DECLINED;
810             }
811             elsif (UNIVERSAL::can('Apache::Const', 'OK')) {
812 1         3 *Apache::OK=\&Apache::Const::OK;
813 1         2 *Apache::DECLINED=\&Apache::Const::DECLINED;
814             }
815             else {
816 1     2   6 *Apache::OK=sub {0}
817 1 0       4 unless defined &Apache::OK;
818 1     2   3 *Apache::DECLINED=sub {-1}
819 1 0       3 unless defined &Apache::DECLINED;
820             }
821             }
822             }
823             elsif ($ENV{'MOD_PERL'}) {
824              
825 1         4 local $SIG{'__DIE__'};
826             eval {
827 1         3 require Apache::Constants; Apache::Constants->import(qw(OK DECLINED));
  1         3  
828 1         2 *Apache::OK=\&Apache::Constants::OK;
829 1         6 *Apache::DECLINED=\&Apache::Constants::DECLINED;
830 1 0       2 } || do {
831 1     1   16 *Apache::OK=sub {0}
832 1         14 };
833 1 0       5 eval {undef} if $@;
  1         4  
834             }
835             else {
836              
837 5     11   26 *Apache::OK=sub {0};
  11         230  
838 2     1   8 *Apache::DECLINED=sub {-1};
  0         0  
839              
840             }
841              
842              
843             # If set, delete all old cache files at startup
844             #
845 2 50 33     24 if ($WEBDYNE_STARTUP_CACHE_FLUSH && (-d $WEBDYNE_CACHE_DN)) {
846 0         0 my @file_cn=glob(File::Spec->catfile($WEBDYNE_CACHE_DN, '*'));
847 0         0 foreach my $fn (grep {/\w{32}(\.html)?$/} @file_cn) {
  0         0  
848 0         0 unlink $fn; #don't error here if problems, user will never see it
849             }
850             }
851              
852              
853             # Pre-compile some of the CGI functions we will need. Do here rather than in init
854             # so that can be executed at module load, and thus shared in memory between Apache
855             # children. Force run of start_ and end_ functions because CGI seems to lose them
856             # if not called at least once after compilation
857             #
858 2         1148 require CGI;
859              
860             # CGI::->method is needed because perl 5.6.0 will use WebDyne::CGI->method instead of
861             # CGI->method. CGI::->method makes it happy
862 2         44089 CGI::->import('-no_xhtml', '-no_sticky');
863 2         222 my @cgi_compile=qw(:all area map unescapeHTML form col colgroup spacer nobr);
864 2         15 CGI::->compile(@cgi_compile);
865              
866             # Broken under CGI 4.28. Use different method
867 2 50       24 if (CGI::->can('_tag_func')) {
868              
869             # 4.28
870 2         6 foreach my $tag (grep {!/^:/} @cgi_compile) {
  18         33  
871 8     1   18 *{"CGI::${tag}"}=sub {return &CGI::_tag_func($tag, @_)}
  0         0  
872 16 100       60 unless CGI::->can($tag);
873 16         25 foreach my $start_end (qw(start end)) {
874 32         46 my $start_end_function="${start_end}_${tag}";
875 16     2   40 *{"CGI::${start_end_function}"}=sub {return &CGI::_tag_func($start_end_function, @_)}
  0         0  
876 32 100       155 unless CGI::->can($start_end_function);
877             }
878             }
879             }
880             else {
881             # Original flavour
882 0         0 foreach (grep {!/^:/} @cgi_compile) {
  0         0  
883 0         0 map {CGI::->$_} ("start_${_}", "end_${_}")
  0         0  
884             }
885             }
886              
887              
888             # Make all errors non-fatal
889             #
890 2         14 errnofatal(1);
891              
892              
893             # Turn off XHTML in CGI. -no_xhtml should do it above, but this makes sure
894             #
895 2         4 $CGI::XHTML=0;
896 2         4 $CGI::NOSTICKY=1;
897              
898              
899             # CGI good practice
900             #
901 2         4 $CGI::DISABLE_UPLOADS=$WEBDYNE_CGI_DISABLE_UPLOADS;
902 2         5 $CGI::POST_MAX=$WEBDYNE_CGI_POST_MAX;
903              
904              
905             # Apparently not such good practice - but needed.
906             # Update. Now done via local() closer to method.
907             #
908             #$CGI::LIST_CONTEXT_WARN=0;
909              
910              
911             # Alias request method to just 'r' also
912             #
913 2   33     7 *WebDyne::r=\&WebDyne::request || *WebDyne::r;
914              
915              
916             # Add comment function to CGI, only called if user has commented out some
917             # HTML that includes a susbst type section, eg
918             #
919 2     1   7 *{'CGI::~comment'}=sub {""};
  2         6  
  0         0  
920              
921              
922             # Eval routine for eval'ing perl code in a non-safe way (ie hostile
923             # code could probably easily subvert us, as all operations are
924             # allowed, including redefining our subroutines etc).
925             #
926             my $eval_cr=sub {
927              
928              
929             # Get self ref
930             #
931 43     44   133 my ($self, $data_ar, $eval_param_hr, $eval_text, $index, $tag_fg)=@_;
932              
933              
934             # Debug
935             #
936 43   50     92 my $inode=$self->{'_inode'} || 'ANON'; # Anon used when no inode present, eg wdcompile
937 43         64 my $html_line_no=$data_ar->[$WEBDYNE_NODE_LINE_IX];
938              
939              
940             # Get CGI vars
941             #
942             my $param_hr=(
943 43   66     131 $self->{'_eval_cgi_hr'} ||= do {
944              
945 9   33     27 my $cgi_or=$self->{'_CGI'} || $self->CGI();
946 9         54 $cgi_or->Vars();
947              
948             }
949             );
950              
951              
952             # Only eval subroutine if we have not done already, if need to eval store in
953             # cache so only done once.
954             #
955 43   33     456 my $eval_cr=$Package{'_cache'}{$inode}{'eval_cr'}{$data_ar}{$index} ||= do {
956 43   66     139 $Package{'_cache'}{$inode}{'perl_init'}{+undef} ||= $self->perl_init();
957 2     2   6147 no strict;
  2         4  
  2         51  
958 2     2   10 no integer;
  2         3  
  2         11  
959 43         49 0 && debug("calling eval sub: $eval_text");
960 43 50       108 &eval_cr($inode, \$eval_text, $html_line_no) || return
961             $self->err_eval("$@", \$eval_text);
962             };
963              
964             #0 && debug("eval done, eval_cr $eval_cr");
965              
966              
967             # Run eval
968             #
969 43         96 my @eval;
970 43         58 eval {
971              
972             # The following line puts all CGI params in %_ during the eval so they are easy to
973             # get to ..
974 43         87 local *_=$param_hr;
975 43         52 0 && debug('eval call starting');
976 43         850 @eval=$eval_cr->($self, $eval_param_hr);
977 43         98 0 && debug("eval call complete, $@, %s", Dumper(\@eval));
978              
979             };
980 43 50 33     236 if (!@eval || $@ || !$eval[0]) {
      33        
981              
982             # An error occurred - handle it and return.
983             #
984 0 0 0     0 if (errstr() || $@) {
985              
986             # Eval error or err() called during routine.
987             #
988 0 0       0 return $self->err_eval($@ ? $@ : undef, \$eval_text);
989              
990             }
991             else {
992              
993             # Some other problem
994             #
995 0         0 return err ('code did not return a true value: %s', $eval_text);
996              
997             }
998              
999             }
1000              
1001              
1002             # Done
1003             #
1004 43         156 \@eval;
1005              
1006 2         12 };
1007              
1008              
1009             # The code ref for the eval statement if using Safe module
1010             #
1011             my $eval_safe_cr=sub {
1012              
1013              
1014             # Get self ref
1015             #
1016 0     1   0 my ($self, $data_ar, $eval_param_hr, $eval_text, $index)=@_;
1017              
1018              
1019             # Inode
1020             #
1021 0   0     0 my $inode=$self->{'_inode'} || 'ANON'; # Anon used when no inode present, eg wdcompile
1022              
1023              
1024             # Get CGI vars
1025             #
1026             my $param_hr=(
1027 0   0     0 $self->{'_eval_cgi_hr'} ||= do {
1028              
1029 0   0     0 my $cgi_or=$self->{'_CGI'} || $self->CGI();
1030 0         0 $cgi_or->Vars();
1031              
1032             }
1033             );
1034              
1035             # Init Safe mode environment space
1036             #
1037 0   0     0 my $safe_or=$self->{'_eval_safe'} || do {
1038             0 && debug('safe init (eval_init)');
1039             require Safe;
1040             require Opcode;
1041              
1042             # Used to use Safe->new($inode), but bug in Safe (actually Opcode) is Safe root namespace too long
1043             #
1044             Safe->new();
1045             };
1046 0   0     0 $self->{'_eval_safe'} ||= do {
1047 0         0 $safe_or->permit_only(@{$WEBDYNE_EVAL_SAFE_OPCODE_AR});
  0         0  
1048 0         0 $safe_or;
1049             };
1050              
1051              
1052             # Only eval subroutine if we have not done already, if need to eval store in
1053             # cache so only done once
1054             #
1055 0         0 local *_=$param_hr;
1056 0         0 ${$safe_or->varglob('_self')}=$self;
  0         0  
1057 0         0 ${$safe_or->varglob('_eval_param_hr')}=$eval_param_hr;
  0         0  
1058 0   0     0 my $html_sr=$safe_or->reval("sub{$eval_text}->(\$::_self, \$::_eval_param_hr)", $WebDyne::WEBDYNE_EVAL_USE_STRICT) ||
1059             return errstr() ? err () : err ($@ || 'undefined return from Safe->reval()');
1060              
1061              
1062             # Run through the same sequence as non-safe routine
1063             #
1064 0 0 0     0 if (!defined($html_sr) || $@) {
1065              
1066              
1067             # An error occurred - handle it and return.
1068             #
1069 0 0 0     0 if (errstr() || $@) {
1070              
1071             # Eval error or err() called during routine.
1072             #
1073 0 0       0 return $self->err_eval($@ ? $@ : undef, \$eval_text);
1074              
1075             }
1076             else {
1077              
1078             # Some other problem
1079             #
1080 0         0 return err ('code did not return a true value: %s', $eval_text);
1081             }
1082              
1083              
1084             }
1085              
1086              
1087             # Array returned ? Convert if so
1088             #
1089 0 0       0 (ref($html_sr) eq 'ARRAY') && do {
1090 0 0       0 $html_sr=\join(undef, map {ref($_) ? ${$_} : $_} @{$html_sr})
  0         0  
  0         0  
  0         0  
1091             };
1092              
1093              
1094             # Any 'printed data ? Prepend to output
1095             #
1096 0 0       0 if (my $print_ar=delete $self->{'_print_ar'}{$data_ar}) {
1097 0 0       0 my $print_html=join(undef, grep {$_} map {(ref($_) eq 'SCALAR') ? ${$_} : $_} @{$print_ar});
  0         0  
  0         0  
  0         0  
  0         0  
1098 0 0       0 $html_sr=ref($html_sr) ? \(${$html_sr} . $print_html) : $html_sr . $print_html;
  0         0  
1099             }
1100              
1101              
1102             # Make sure we return a ref
1103             #
1104 0 0       0 return ref($html_sr) ? $html_sr : \$html_sr;
1105              
1106              
1107 2         10 };
1108              
1109              
1110             # Hash eval routine, works similar to the above, but returns a hash ref
1111             #
1112             my $eval_hash_cr=sub {
1113              
1114              
1115             # Run eval and turn into tied hash
1116             #
1117 1 50   2   3 tie(my %hr, 'Tie::IxHash', @{$eval_cr->(@_) || return err ()});
  1         3  
1118 1         53 return \%hr;
1119              
1120              
1121 2         7 };
1122              
1123              
1124             # Array eval routine, works similar to the above, but returns an array ref
1125             #
1126             my $eval_array_cr=sub {
1127              
1128              
1129             # Run eval and return default - which is an array ref
1130             #
1131 5   33 6   17 return $eval_cr->(@_) || err ();
1132              
1133 2         6 };
1134              
1135              
1136             # Code ref eval routine
1137             #
1138             my $eval_code_cr=sub {
1139              
1140 37     38   109 my ($self, $data_ar, $eval_param_hr, $eval_text, $index, $tag_fg)=@_;
1141 37         41 0 && debug("eval code start $eval_text");
1142 37   50     93 my $html_ar=$eval_cr->(@_) || return err ();
1143 37         43 0 && debug("eval code finish %s", Dumper($html_ar));
1144 37         55 my $html_sr=$html_ar->[0];
1145              
1146              
1147             # If array ref returned and not rendering a tag convert to string. If in tag CGI.pm can
1148             # use array ref so leave alone
1149             #
1150 37 100 100     121 if ((ref($html_sr) eq 'ARRAY') && !$tag_fg) {
1151 4   50     6 $html_sr=\join(undef, map {(ref($_) eq 'SCALAR') ? ${$_} : $_} @{$html_sr}) ||
1152             return err ('unable to generate scalar from %s', Dumper($html_sr));
1153             }
1154              
1155              
1156             # Any 'printed data ? Prepend to output
1157             #
1158 37 100       111 if (my $print_ar=delete $self->{'_print_ar'}{$data_ar}) {
1159 3 50       4 my $print_html=join(undef, grep {$_} map {(ref($_) eq 'SCALAR') ? ${$_} : $_} @{$print_ar});
  3         10  
  3         9  
  0         0  
  3         7  
1160 3 50       7 $html_sr=ref($html_sr) ? \(${$html_sr} . $print_html) : $html_sr . $print_html;
  3         9  
1161             }
1162              
1163             # Make sure we return a ref
1164             #
1165 37 100       150 return ref($html_sr) ? $html_sr : \$html_sr;
1166              
1167 2         8 };
1168              
1169              
1170             # Scalar (${foo}) routine
1171             #
1172             my $eval_scalar_cr=sub {
1173              
1174 26     27   57 my $value=$_[2]->{$_[3]};
1175 26 100       52 unless ($value) {
1176 1 0 33     5 if (!exists($_[2]->{$_[3]}) && $WEBDYNE_STRICT_VARS) {
1177 0         0 return err ("no '$_[3]' parameter value supplied, parameters are: %s", join(',', map {"'$_'"} keys %{$_[2]}))
  0         0  
  0         0  
1178             }
1179             }
1180              
1181             # Get rid of any overloading
1182 26 50 66     71 if (ref($value) && overload::Overloaded($value)) {$value="$value"}
  0         0  
1183 26 100       190 return ref($value) ? $value : \$value
1184              
1185 2         6 };
1186              
1187              
1188             # Init anon text and attr evaluation subroutines, store in class space
1189             # for quick retrieval when needed, save redefining all the time
1190             #
1191             my %eval_cr=(
1192              
1193             '$' => $eval_scalar_cr,
1194             '@' => $eval_array_cr,
1195             '%' => $eval_hash_cr,
1196             '!' => $eval_code_cr,
1197 0     0   0 '+' => sub {return \($_[0]->{'_CGI'}->param($_[3]))},
1198 2     2   7 '*' => sub {return \$ENV{$_[3]}},
1199             '^' => sub {
1200 0     0   0 my $m=$_[3]; my $r=$_[0]->{'_r'};
  0         0  
1201 0 0       0 UNIVERSAL::can($r, $m) ? \$r->$m : err ("unknown request method '$m'")
1202             }
1203              
1204 2         27 );
1205              
1206              
1207             # Store in class name space
1208             #
1209 2         45 $Package{'_eval_cr'}=\%eval_cr;
1210              
1211             }
1212              
1213              
1214             sub cache_clean {
1215              
1216              
1217             # Get cache_hr, only param supplied
1218             #
1219 0     0 0 0 my $cache_hr=shift();
1220 0         0 0 && debug('in cache_clean');
1221              
1222              
1223             # Values we want, either last run time (lrun) or number of times run
1224             # (nrun)
1225             #
1226 0 0       0 my $clean_method=$WEBDYNE_CACHE_CLEAN_METHOD ? 'nrun' : 'lrun';
1227              
1228              
1229             # Sort into array of inode values, sorted descending by clean attr
1230             #
1231 0         0 my @cache=sort {$cache_hr->{$b}{$clean_method} <=> $cache_hr->{$a}{$clean_method}}
1232 0         0 keys %{$cache_hr};
  0         0  
1233 0         0 0 && debug('cache clean array %s', Dumper(\@cache));
1234              
1235              
1236             # If > high watermark entries, we need to clean
1237             #
1238 0 0       0 if (@cache > $WEBDYNE_CACHE_HIGH_WATER) {
1239              
1240              
1241             # Yes, clean
1242             #
1243 0         0 0 && debug('cleaning cache');
1244              
1245              
1246             # Delete excess entries
1247             #
1248 0         0 my @clean=map {delete $cache_hr->{$_}} @cache[$WEBDYNE_CACHE_LOW_WATER..$#cache];
  0         0  
1249              
1250              
1251             # Debug
1252             #
1253 0         0 0 && debug('removed %s entries from cache', scalar @clean);
1254              
1255             }
1256             else {
1257              
1258             # Nothing to do
1259             #
1260 0         0 0 && debug(
1261             'no cleanup needed, cache size %s less than high watermark %s',
1262             scalar @cache, $WEBDYNE_CACHE_HIGH_WATER
1263             );
1264              
1265             }
1266              
1267              
1268             # Done
1269             #
1270 0         0 return \undef;
1271              
1272             }
1273              
1274              
1275             sub head_request {
1276              
1277              
1278             # Head request only
1279             #
1280 0     0 0 0 my $r=shift();
1281              
1282              
1283             # Clear any handlers
1284             #
1285 0         0 $r->set_handlers(PerlHandler => undef);
1286              
1287              
1288             # Send the request
1289             #
1290 0 0       0 $r->send_http_header() if !$MP2;
1291              
1292              
1293             # Done
1294             #
1295 0         0 return &Apache::OK;
1296              
1297             }
1298              
1299              
1300             sub render_reset {
1301              
1302 0     0 0 0 my ($self, $data_ar)=@_;
1303 0 0       0 $data_ar ? $self->{'_perl'}[0]=$data_ar : delete $self->{'_perl'};
1304              
1305             }
1306              
1307              
1308             sub render {
1309              
1310              
1311             # Convert data array structure into HTML
1312             #
1313 43     43 0 96 my ($self, $param_hr)=@_;
1314              
1315              
1316             # If not supplied param as hash ref assume all vars are params to be subs't when
1317             # rendering this data block
1318             #
1319 43 100 100     148 ref($param_hr) || ($param_hr={param => {@_[1..$#_]}}) if $param_hr;
1320              
1321              
1322             # Debug
1323             #
1324 43         63 0 && debug('in render');
1325              
1326              
1327             # Get node array ref
1328             #
1329 43   50     152 my $data_ar=$param_hr->{'data'} || $self->{'_perl'}[0][$WEBDYNE_NODE_CHLD_IX] ||
1330             return err ('unable to get HTML data array');
1331              
1332             #$self->{'_perl'}[0] ||= $data_ar;
1333              
1334              
1335             # Debug
1336             #
1337 43         53 0 && debug("render data_ar $data_ar %s", Dumper($data_ar));
1338              
1339              
1340             # If block name spec'd register it now
1341             #
1342 43 50 0     75 $param_hr->{'block'} && (
1343             $self->render_block($param_hr) || return err ());
1344              
1345              
1346             # Get CGI object
1347             #
1348 43   50     96 my $cgi_or=$self->{'_CGI'} || $self->CGI() ||
1349             return err ("unable to get CGI object from self ref");
1350              
1351              
1352             # Any data params for this render
1353             #
1354 43         97 my $param_data_hr=$param_hr->{'param'};
1355              
1356              
1357             # Recursive anon sub to do the render, init and store in class space
1358             # if not already done, saves a small amount of time if doing many
1359             # iterations
1360             #
1361             my $render_cr=$Package{'_render_cr'} ||= sub {
1362              
1363              
1364             # Get self ref, node array etc
1365             #
1366 77     77   153 my ($render_cr, $self, $cgi_or, $data_ar, $param_data_hr)=@_;
1367              
1368              
1369             # Get tag
1370             #
1371             my ($html_tag, $html_line_no)=
1372 77         103 @{$data_ar}[$WEBDYNE_NODE_NAME_IX, $WEBDYNE_NODE_LINE_IX];
  77         162  
1373 77         95 my $html_chld;
1374              
1375              
1376             # Save current data block away for reference by error handler if something goes
1377             # wrong
1378             #
1379 77         125 $self->{'_data_ar'}=$data_ar;
1380              
1381              
1382             # Debug
1383             #
1384 77         115 0 && debug("render tag $html_tag, line $html_line_no");
1385              
1386              
1387             # Get attr hash ref
1388             #
1389 77         157 my $attr_hr=$data_ar->[$WEBDYNE_NODE_ATTR_IX];
1390              
1391              
1392             # If subst flag present, means we need to process attr values
1393             #
1394 77 100       164 if ($data_ar->[$WEBDYNE_NODE_SBST_IX]) {
1395 18   50     58 $attr_hr=$self->subst_attr($data_ar, $attr_hr, $param_data_hr) ||
1396             return err ();
1397             }
1398              
1399              
1400             # If param present, use for sub-render
1401             #
1402 77 50       148 $attr_hr->{'param'} && ($param_data_hr=$attr_hr->{'param'});
1403              
1404              
1405             # Process sub nodes to get child html data, only if not a perl tag or block tag
1406             # though - they will choose when to render sub data. Subst is OK
1407             #
1408 77 100 100     255 if (!$CGI_TAG_WEBDYNE{$html_tag} || ($html_tag eq 'subst')) {
1409              
1410              
1411             # Not a perl tag, recurse through children and render them, building
1412             # up HTML from inside out
1413             #
1414 37 100       94 my @data_child_ar=$data_ar->[$WEBDYNE_NODE_CHLD_IX] ? @{$data_ar->[$WEBDYNE_NODE_CHLD_IX]} : undef;
  21         48  
1415 37         68 foreach my $data_chld_ar (@data_child_ar) {
1416              
1417              
1418             # Debug
1419             #
1420 37         44 0 && debug('data_chld_ar %s', Dumper($data_chld_ar));
1421              
1422              
1423             # Only recurse on children which are are refs, as these are sub nodes. A
1424             # child that is not a ref is merely HTML text
1425             #
1426 37 100       64 if (ref($data_chld_ar)) {
1427              
1428              
1429             # It is a sub node, render recursively
1430             #
1431             $html_chld.=${
1432 2 50       7 ( $render_cr->($render_cr, $self, $cgi_or, $data_chld_ar, $param_data_hr)
  2         13  
1433             ||
1434             return err ())};
1435              
1436             #$html_chld.="\n";
1437              
1438             }
1439             else {
1440              
1441              
1442             # Text node only, add text to child html string
1443             #
1444 35         91 $html_chld.=$data_chld_ar;
1445              
1446             }
1447              
1448             }
1449              
1450             }
1451             else {
1452              
1453 40         44 0 && debug("skip child render, under $html_tag tag");
1454              
1455             }
1456              
1457              
1458             # Debug
1459             #
1460 77         89 0 && debug("html_chld $html_chld");
1461              
1462              
1463             # Render *our* node now, trying to use most efficient/appropriated method depending on a number
1464             # of factors
1465             #
1466 77 100       145 if ($CGI_TAG_WEBDYNE{$html_tag}) {
    50          
    0          
1467              
1468              
1469             # Debug
1470             #
1471 56         58 0 && debug("rendering webdyne tag $html_tag");
1472              
1473              
1474             # Special WebDyne tag, render using our self ref, not CGI object
1475             #
1476 56   50     165 my $html_sr=(
1477             $self->$html_tag($data_ar, $attr_hr, $param_data_hr, $html_chld)
1478             ||
1479             return err ());
1480              
1481              
1482             # Debug
1483             #
1484 56         76 0 && debug("CGI tag $html_tag render return $html_sr (%s)", Dumper($html_sr));
1485              
1486              
1487             # Return
1488             #
1489 56         218 return $html_sr;
1490              
1491              
1492             }
1493             elsif ($attr_hr) {
1494              
1495              
1496             # Normal CGI tag, with attributes and perhaps child text
1497             #
1498             return \(
1499 21   50     41 $cgi_or->$html_tag(grep {$_} $attr_hr, $html_chld)
1500             ||
1501             return err (
1502             "CGI tag '<$html_tag>' " .
1503             'did not return any text'
1504             ));
1505              
1506             }
1507             elsif ($html_chld) {
1508              
1509              
1510             # Normal CGI tag, no attributes but with child text
1511             #
1512             return \(
1513 0   0     0 $cgi_or->$html_tag($html_chld)
1514             ||
1515             return err (
1516             "CGI tag '<$html_tag>' " .
1517             'did not return any text'
1518             ));
1519              
1520             }
1521             else {
1522              
1523              
1524             # Empty CGI object, eg
1525             #
1526             return \(
1527 0   0     0 $cgi_or->$html_tag()
1528             ||
1529             return err (
1530             "CGI tag '<$html_tag>' " .
1531             'did not return any text'
1532             ));
1533              
1534             }
1535              
1536              
1537 43   100     95 };
1538              
1539              
1540             # At the top level the array may have completly text nodes, and no children, so
1541             # need to take care to only render children if present.
1542             #
1543 43         59 my @html;
1544 43         55 foreach my $data_ar (@{$data_ar}) {
  43         90  
1545              
1546              
1547             # Is this a sub node, or only text (ref means sub-node)
1548             #
1549 154 100       6631 if (ref($data_ar)) {
1550              
1551              
1552             # Sub node, we call call render routine
1553             #
1554             push @html,
1555 75 50       107 ${$render_cr->($render_cr, $self, $cgi_or, $data_ar, $param_data_hr) || return err ()};
  75         153  
1556              
1557              
1558             }
1559             else {
1560              
1561              
1562             # Text only, do not render just push onto return array
1563             #
1564 79         136 push @html, $data_ar;
1565              
1566             }
1567             }
1568              
1569              
1570             # Return scalar ref of completed HTML string
1571             #
1572 43         1219 0 && debug('render exit, html %s', Dumper(\@html));
1573 43         483 return \join(undef, @html);
1574              
1575              
1576             }
1577              
1578              
1579             sub redirect {
1580              
1581              
1582             # Redirect render to different location
1583             #
1584 0     0 0 0 my ($self, $param_hr)=@_;
1585              
1586              
1587             # Debug
1588             #
1589 0         0 0 && debug('in redirect, param %s', Dumper($param_hr));
1590              
1591              
1592             # Restore select handler before anything else so all output goes
1593             # to main::STDOUT;
1594             #
1595 0 0       0 if (my $select=$self->{'_select'}) {
1596 0         0 0 && debug("restoring select handle to $select");
1597 0         0 CORE::select $select;
1598             }
1599              
1600              
1601             # If redirecting to a different uri, run its handler
1602             #
1603 0 0 0     0 if ($param_hr->{'uri'} || $param_hr->{'file'} || $param_hr->{'location'}) {
      0        
1604              
1605              
1606             # Get HTML from subrequest
1607             #
1608 0   0     0 my $status=$self->subrequest($param_hr) ||
1609             return err ();
1610 0         0 0 && debug("redirect status was $status");
1611              
1612              
1613             # GOTOs considered harmful - except here ! Speed things up significantly, removes uneeded checks
1614             # for redirects in render code etc.
1615             #
1616 0   0     0 my $r=$self->r() || return err ();
1617 0         0 $r->status($status);
1618 0 0 0     0 if (my $errstr=errstr()) {
    0 0        
    0          
1619 0         0 0 && debug("error in subrequest: $errstr");
1620 0         0 return errsubst("error in subrequest: $errstr")
1621             }
1622             elsif (is_error($status)) {
1623 0         0 0 && debug("sending error response status $status with r $r");
1624 0         0 $r->send_error_response(&Apache::OK)
1625             }
1626             elsif (($status != &Apache::OK) && !is_success($status) && !is_redirect($status)) {
1627 0         0 return err ("unknown status code '$status' returned from subrequest");
1628             }
1629             else {
1630 0         0 0 && debug("status $status OK");
1631             }
1632 0         0 goto HANDLER_COMPLETE;
1633              
1634              
1635             }
1636             else {
1637              
1638              
1639             # html/text must be a param
1640             #
1641 0   0     0 my $html_sr=$param_hr->{'html'} || $param_hr->{'text'} ||
1642             return err ('no data supplied to redirect method');
1643              
1644              
1645             # Set content type
1646             #
1647 0   0     0 my $r=$self->r() || return err ();
1648 0 0       0 if ($param_hr->{'html'}) {
    0          
1649 0         0 $r->content_type($WEBDYNE_CONTENT_TYPE_HTML)
1650             }
1651             elsif ($param_hr->{'text'}) {
1652 0         0 $r->content_type($WEBDYNE_CONTENT_TYPE_PLAIN)
1653             }
1654              
1655              
1656             # And length
1657             #
1658 0   0     0 my $headers_out_hr=$r->headers_out || return err ();
1659 0 0       0 $headers_out_hr->{'Content-Length'}=length(ref($html_sr) ? ${$html_sr} : $html_sr);
  0         0  
1660              
1661              
1662             # Set status, send header
1663             #
1664 0         0 $r->status(RC_OK);
1665 0 0       0 $r->send_http_header() if !$MP2;
1666              
1667              
1668             # Print directly and shorcut return from render routine with non-harmful GOTO ! Should
1669             # always be SR, but be generous.
1670             #
1671 0 0       0 $r->print(ref($html_sr) ? ${$html_sr} : $html_sr);
  0         0  
1672 0         0 goto RENDER_COMPLETE;
1673              
1674              
1675             }
1676              
1677              
1678             }
1679              
1680              
1681             sub subrequest {
1682              
1683              
1684             # Redirect render to different location
1685             #
1686 0     0 0 0 my ($self, $param_hr)=@_;
1687              
1688              
1689             # Debug
1690             #
1691 0         0 0 && debug('in subrequest %s', Dumper($param_hr));
1692              
1693              
1694             # Get request object, var for subrequest object
1695             #
1696 0 0       0 my ($r, $cgi_or)=map {$self->$_() || return err ("unable to run '$_' method")} qw(request CGI);
  0         0  
1697 0         0 my $r_child;
1698              
1699              
1700             # Run taks appropriate for subrequest - location redirects with 302, uri does sinternal redirect,
1701             # and file sends content of file.
1702             #
1703 0 0       0 if (my $location=$param_hr->{'location'}) {
1704              
1705              
1706             # Does the request handler take care of it ?
1707             #
1708 0 0       0 if (UNIVERSAL::can($r, 'redirect')) {
1709              
1710              
1711             # Let the request handler take care of it
1712             #
1713 0         0 0 && debug('handler does redirect, handing off');
1714 0         0 $r->redirect($location); # no return value
1715 0         0 return RC_FOUND;
1716              
1717             }
1718             else {
1719              
1720              
1721             # Must do it ourselves
1722             #
1723 0         0 0 && debug('doing redirect ourselves');
1724 0   0     0 my $headers_out_hr=$r->headers_out || return err ();
1725 0         0 $headers_out_hr->{'Location'}=$location;
1726 0         0 $r->status(RC_FOUND);
1727 0 0       0 $r->send_http_header if !$MP2;
1728 0         0 return RC_FOUND;
1729              
1730             }
1731             }
1732 0 0       0 if (my $uri=$param_hr->{'uri'}) {
    0          
1733              
1734             # Handle internally if possible
1735             #
1736 0 0       0 if (UNIVERSAL::can($r, 'internal_redirect')) {
1737              
1738              
1739             # Let the request handler take care of it
1740             #
1741 0         0 0 && debug('handler does internal_redirect, handing off');
1742 0         0 $r->internal_redirect($uri); # no return value
1743 0         0 return $r->status;
1744              
1745             }
1746             else {
1747              
1748             # Must do it ourselves
1749             #
1750 0   0     0 $r_child=$r->lookup_uri($uri) ||
1751             return err ('undefined lookup_uri error');
1752 0         0 0 && debug('r_child handler %s', $r->handler());
1753 0         0 $r->headers_out($r_child->headers_out());
1754 0         0 $r->uri($uri);
1755              
1756             }
1757              
1758              
1759             }
1760             elsif (my $file=$param_hr->{'file'}) {
1761              
1762             # Get cwd, make request absolute rel to cwd if no dir given.
1763             #
1764 0         0 my $dn=(File::Spec->splitpath($r->filename()))[1];
1765 0         0 my $file_pn=File::Spec->rel2abs($file, $dn);
1766              
1767              
1768             # Get a new request object
1769             #
1770 0   0     0 $r_child=$r->lookup_file($file_pn) ||
1771             return err ('undefined lookup_file error');
1772 0         0 $r->headers_out($r_child->headers_out());
1773              
1774             }
1775             else {
1776              
1777              
1778             # Must be one or other
1779             #
1780 0         0 return err ('must specify file, uri or locations for subrequest');
1781              
1782             }
1783              
1784              
1785             # Save child object, else cleanup handlers will be run when
1786             # we exit and r_child is destroyed, but before r (main) is
1787             # complete.
1788             #
1789             # UPDATE no longer needed, leave here as reminder though ..
1790             #
1791             #push @{$self->{'_r_child'}},$r_child;
1792              
1793              
1794             # Safty check after calling getting r_child - should always be
1795             # OK, but do sanity check.
1796             #
1797 0         0 my $status=$r_child->status();
1798 0         0 0 && debug("r_child status return: $status");
1799 0 0 0     0 if (($status && !is_success($status)) || (my $errstr=errstr())) {
      0        
1800 0 0       0 if ($errstr) {
1801             return errsubst(
1802             "error in status phase of subrequest to '%s': $errstr",
1803 0   0     0 $r_child->uri() || $param_hr->{'file'}
1804             )
1805             }
1806             else {
1807             return err (
1808             "error in status phase of subrequest to '%s', return status was $status",
1809 0   0     0 $r_child->uri() || $param_hr->{'file'}
1810             )
1811             }
1812             }
1813              
1814              
1815             # Debug
1816             #
1817 0         0 0 && debug('cgi param %s', Dumper($param_hr->{'param'}));
1818              
1819              
1820             # Set up CGI with any new params
1821             #
1822 0         0 while (my ($param, $value)=each %{$param_hr->{'param'}}) {
  0         0  
1823              
1824              
1825             # Add to CGI
1826             #
1827 0         0 $cgi_or->param($param, $value);
1828 0         0 0 && debug("set cgi param $param, value $value");
1829              
1830              
1831             }
1832              
1833              
1834             # Debug
1835             #
1836 0         0 0 && debug("about to call child handler with params self $self %s", Dumper($param_hr->{'param'}));
1837              
1838              
1839             # Change of plan - used to check result, but now pass back whatever the child returns - we
1840             # will let Apache handle any errors internally
1841             #
1842 0 0       0 defined($status=(ref($r_child)=~/^WebDyne::/) ? $r_child->run($self) : $r_child->run()) ||
    0          
1843             return err ();
1844 0         0 0 && debug("r_child run return status $status, rc_child status %s", $r_child->status());
1845 0   0     0 return $status || $r_child->status();
1846              
1847              
1848             }
1849              
1850              
1851             sub eof {
1852              
1853 0     0 0 0 goto HANDLER_COMPLETE;
1854              
1855             }
1856              
1857              
1858             sub erase_block {
1859              
1860             # Erase a block section so not rendered if encountered again
1861             #
1862 0     0 0 0 my ($self, $param_hr)=@_;
1863              
1864              
1865             # Has user only given name as param
1866             #
1867 0 0       0 ref($param_hr) || ($param_hr={name => $param_hr, param => {@_[2..$#_]}});
1868              
1869              
1870             # Get block name
1871             #
1872 0   0     0 my $name=$param_hr->{'name'} || $param_hr->{'block'} ||
1873             return err ('no block name specified');
1874 0         0 0 && debug("in erase_block, name $name");
1875 0         0 delete $self->{'_block_param'}{$name};
1876 0         0 delete $self->{'_block_render'}{$name}
1877              
1878             }
1879              
1880              
1881             sub unrender_block {
1882              
1883             # Synonym for erase_block
1884             #
1885 0     0 0 0 return shift()->erase_block(@_);
1886              
1887             }
1888              
1889              
1890             sub render_block {
1891              
1892              
1893             # Render a section of HTML
1894             #
1895 4     4 0 10 my ($self, $param_hr)=@_;
1896              
1897              
1898             # Has user only given name as param
1899             #
1900 4 50       24 ref($param_hr) || ($param_hr={name => $param_hr, param => {@_[2..$#_]}});
1901              
1902              
1903             # Get block name
1904             #
1905 4   0     13 my $name=$param_hr->{'name'} || $param_hr->{'block'} ||
1906             return err ('no block name specified');
1907 4         5 0 && debug("in render_block, name $name");
1908              
1909              
1910             # Get current data block
1911             #
1912             #my $data_ar=$self->{'_perl'}[0] ||
1913             #return err("unable to get current data node");
1914 4   33     10 my $data_ar=$self->{'_perl'}[0] || do {
1915              
1916             #if ($WEBDYNE_DELAYED_BLOCK_RENDER) {
1917             push @{$self->{'_block_param'}{$name} ||= []}, $param_hr->{'param'}; # if $WEBDYNE_DELAYED_BLOCK_RENDER;
1918             return \undef;
1919              
1920             #}
1921             #else {
1922             # return err("unable to get current data node")
1923             #}
1924             };
1925              
1926              
1927             # Find block name
1928             #
1929 4         5 my @data_block_ar;
1930              
1931              
1932             # Debug
1933             #
1934 4         5 0 && debug("render_block self $self, name $name, data_ar $data_ar, %s", Dumper($data_ar));
1935              
1936              
1937             # Have we seen this search befor ?
1938             #
1939 4 50       9 unless (exists($self->{'_block_cache'}{$name})) {
1940              
1941              
1942             # No, search for block
1943             #
1944 4         5 0 && debug("searching for node $name in data_ar");
1945              
1946              
1947             # Do it
1948             #
1949 4   50     20 my $data_block_all_ar=$self->find_node(
1950             {
1951              
1952             data_ar => $data_ar,
1953             tag => 'block',
1954             all_fg => 1,
1955              
1956             }) || return err ();
1957              
1958              
1959             # Debug
1960             #
1961 4         8 0 && debug('find_node returned %s', join('*', @{$data_block_all_ar}));
1962              
1963              
1964             # Go through each block found and svae in block_cache
1965             #
1966 4         5 foreach my $data_block_ar (@{$data_block_all_ar}) {
  4         6  
1967              
1968              
1969             # Get block name
1970             #
1971 5         10 my $name=$data_block_ar->[$WEBDYNE_NODE_ATTR_IX]->{'name'};
1972 5         4 0 && debug("looking at block $data_block_ar, name $name");
1973              
1974              
1975             # Save
1976             #
1977             #$self->{'_block_cache'}{$name}=$data_block_ar;
1978 5   100     5 push @{$self->{'_block_cache'}{$name} ||= []}, $data_block_ar;
  5         21  
1979              
1980              
1981             }
1982              
1983              
1984             # Done, store
1985             #
1986 4         6 @data_block_ar=@{$self->{'_block_cache'}{$name}};
  4         10  
1987              
1988              
1989             }
1990             else {
1991              
1992              
1993             # Yes, set data_block_ar to whatever we saw before, even if it is
1994             # undef
1995             #
1996 0         0 @data_block_ar=@{$self->{'_block_cache'}{$name}};
  0         0  
1997              
1998              
1999             # Debug
2000             #
2001 0         0 0 && debug("retrieved data_block_ar @data_block_ar for node $name from cache");
2002              
2003              
2004             }
2005              
2006              
2007             # Debug
2008             #
2009             #0 && debug("set block node to $data_block_ar %s", Dumper($data_block_ar));
2010              
2011              
2012             # Store params for later block render (outside perl block) if needed
2013             #
2014 4   50     5 push @{$self->{'_block_param'}{$name} ||= []}, $param_hr->{'param'}; # if $WEBDYNE_DELAYED_BLOCK_RENDER;
  4         16  
2015              
2016              
2017             # No data_block_ar ? Could not find block - remove this line if global block
2018             # rendering is desired (ie blocks may lay outside perl code calling render_bloc())
2019             #
2020 4 100       9 unless (@data_block_ar) {
2021              
2022             #if ($WEBDYNE_DELAYED_BLOCK_RENDER) {
2023 1         4 return \undef;
2024              
2025             #}
2026             #else {
2027             # return err("could not find block '$name' to render") unless $WEBDYNE_DELAYED_BLOCK_RENDER;
2028             #}
2029             }
2030              
2031              
2032             # Now, was it set to something ?
2033             #
2034 3         3 my @html_sr;
2035 3         4 foreach my $data_block_ar (@data_block_ar) {
2036              
2037              
2038             # Debug
2039             #
2040 4         10 0 && debug("rendering block name $name, data $data_ar with param %s", Dumper($param_hr->{'param'}));
2041              
2042              
2043             # Yes, Get HTML for block immedialtly
2044             #
2045             my $html_sr=$self->render(
2046             {
2047              
2048             data => $data_block_ar->[$WEBDYNE_NODE_CHLD_IX],
2049 4   50     13 param => $param_hr->{'param'},
2050              
2051             }) || return err ();
2052              
2053              
2054             # Debug
2055             #
2056 4         6 0 && debug("block $name rendered HTML $html_sr %s, pushing onto name $name, data_ar $data_block_ar", ${$html_sr});
2057              
2058              
2059             # Store away for this block
2060             #
2061 4   50     5 push @{$self->{'_block_render'}{$name}{$data_block_ar} ||= []}, $html_sr;
  4         20  
2062              
2063              
2064             # Store
2065             #
2066 4         16 push @html_sr, $html_sr;
2067              
2068              
2069             }
2070 3 50       7 if (@html_sr) {
2071              
2072              
2073             # Return scalar or array ref, depending on number of elements
2074             #
2075             #0 && debug('returning %s', Dumper(\@html_sr));
2076 3 100       60 return $#html_sr ? $html_sr[0] : \@html_sr;
2077              
2078             }
2079             else {
2080              
2081              
2082             # No, could not find block below us, store param away for later
2083             # render. NOTE now done for all blocks so work both in and out of
2084             # section. Moved this code above
2085             #
2086             #push @{$self->{'_block_param'}{$name} ||=[]},$param_hr->{'param'};
2087              
2088              
2089             # Debug
2090             #
2091 0         0 0 && debug("block $name not found in tree, storing params for later render");
2092              
2093              
2094             # Done, return undef at this stage
2095             #
2096 0         0 return \undef;
2097              
2098             }
2099              
2100              
2101             }
2102              
2103              
2104             sub block {
2105              
2106              
2107             # Called when we encounter a tag
2108             #
2109 11     11 0 19 my ($self, $data_ar, $attr_hr, $param_data_hr, $text)=@_;
2110 11         12 0 && debug("in block code, data_ar $data_ar");
2111              
2112              
2113             # Get block name
2114             #
2115 11   50     20 my $name=$attr_hr->{'name'} ||
2116             return err ('no block name specified');
2117 11         10 0 && debug("in block, looking for name $name, attr given %s", Dumper($attr_hr));
2118              
2119              
2120             # Only render if registered, do once for every time spec'd
2121             #
2122 11 100       37 if (exists($self->{'_block_render'}{$name}{$data_ar})) {
    100          
    100          
2123              
2124              
2125             # The block name has been pre-rendered - return it
2126             #
2127 4         4 0 && debug("found pre-rendered block $name");
2128              
2129              
2130             # Var to hold render result
2131             #
2132 4         8 my $html_ar=delete $self->{'_block_render'}{$name}{$data_ar};
2133              
2134              
2135             # Return result as a single scalar ref
2136             #
2137 4         7 return \join(undef, map {${$_}} @{$html_ar});
  4         5  
  4         22  
  4         9  
2138              
2139              
2140             }
2141             elsif (exists($self->{'_block_param'}{$name})) {
2142              
2143              
2144             # The block params have been registered, but the block itself was
2145             # not yet rendered. Do it now
2146             #
2147 3         3 0 && debug("found block param for $name in register");
2148              
2149              
2150             # Var to hold render result
2151             #
2152 3         4 my @html_sr;
2153              
2154              
2155             # Render the block for as many times as it has parameters associated
2156             # with it, eg user may have called ->render_block several times in
2157             # their code
2158             #
2159 3         4 foreach my $param_data_block_hr (@{$self->{'_block_param'}{$name}}) {
  3         11  
2160              
2161              
2162             # If no explicit data hash, use parent hash - not sure how useful
2163             # this really is
2164             #
2165 3   33     7 $param_data_block_hr ||= $param_data_hr;
2166              
2167              
2168             # Debug
2169             #
2170 3         4 0 && debug("about to render block $name, param %s", Dumper($param_data_block_hr));
2171              
2172              
2173             # Render it
2174             #
2175 3   50     7 push @html_sr, $self->render(
2176             {
2177              
2178             data => $data_ar->[$WEBDYNE_NODE_CHLD_IX],
2179             param => $param_data_block_hr
2180              
2181             }) || return err ();
2182              
2183             }
2184              
2185              
2186             # Return result as a single scalar ref
2187             #
2188 3         6 return \join(undef, map {${$_}} @html_sr);
  3         4  
  3         12  
2189              
2190             }
2191             elsif ($attr_hr->{'display'}) {
2192              
2193              
2194             # User wants block displayed normally
2195             #
2196 1   33     13 return $self->render(
2197             {
2198              
2199             data => $data_ar->[$WEBDYNE_NODE_CHLD_IX],
2200             param => $param_data_hr
2201              
2202             }) || err ();
2203              
2204             }
2205             else {
2206              
2207              
2208             # Block name not registered, therefore do not render - return
2209             # blank
2210             #
2211 3         7 return \undef;
2212              
2213             }
2214              
2215              
2216             }
2217              
2218              
2219             sub perl {
2220              
2221              
2222             # Called when we encounter a tag
2223             #
2224 26     26 0 154 my ($self, $data_ar, $attr_hr, $param_data_hr, $text)=@_;
2225              
2226             #0 && debug("rendering perl tag in block $data_ar, attr %s");
2227              
2228              
2229             # If inline, run now
2230             #
2231 26 100       64 if (my $perl_code=$attr_hr->{'perl'}) {
2232              
2233              
2234             # May be inline code params to supply to this block
2235             #
2236 6         7 my $perl_param_hr=$attr_hr->{'param'};
2237              
2238              
2239             # Run the same code as the inline eval (!{! ... !}) would run,
2240             # for consistancy
2241             #
2242 6   33     18 return $Package{'_eval_cr'}{'!'}->($self, $data_ar, $perl_param_hr, $perl_code) ||
2243             err ();
2244              
2245              
2246             }
2247             else {
2248              
2249              
2250             # Not inline, must want to call a handler, get method and caller
2251             #
2252             #my $function=join('::', grep {$_} @{$attr_hr}{qw(package class method)}) ||
2253 20   50     38 my $function=join('::', grep {$_} map {exists($attr_hr->{$_}) && $attr_hr->{$_}} qw(package class method)) ||
2254             return err ('could not determine perl routine to run');
2255              
2256              
2257             # Try to get the package name as an array, pop the method off
2258             #
2259 20         86 my @package=split(/\:+/, $function);
2260 20         35 my $method=pop @package;
2261              
2262              
2263             # And return package
2264             #
2265 20         44 my $package=join('::', grep {$_} @package);
  0         0  
2266              
2267              
2268             # Debug
2269             #
2270 20         18 0 && debug("perl package $package, method $method");
2271              
2272              
2273             # If no method by now, dud caller
2274             #
2275 20 50       37 $method ||
2276             return err ("no package/method in perl block");
2277              
2278              
2279             # If the require fails, we want to catch it in an eval
2280             # and return a meaningful error message. BTW this is an
2281             # order of magnitued faster than doing eval("require $package");
2282             #
2283 20 50       36 0 && debug("about to require $package") if $package;
2284 20         37 my $package_fn=join('/', @package) . '.pm';
2285 20 50 33     41 if ($package && !$INC{$package_fn}) {
2286              
2287             # Add psp file cwd to INC incase package stored in same dir
2288             #
2289 0         0 local @INC=@INC;
2290 0         0 push @INC, $self->cwd();
2291 0 0 0     0 eval {require $package_fn} ||
  0         0  
2292             return errsubst(
2293             "error loading package '$package', %s", errstr() || $@ || 'undefined error'
2294             )
2295             }
2296 20         23 0 && debug("package $package loaded OK");
2297              
2298              
2299             # Push data_ar so we can use it if the perl routine calls self->render(). render()
2300             # then has to "know" where it is in the data_ar structure, and can get that info
2301             # here.
2302             #
2303             #unshift @{$self->{'_perl'}}, $data_ar->[$WEBDYNE_NODE_CHLD_IX];
2304 20         27 unshift @{$self->{'_perl'}}, $data_ar;
  20         56  
2305              
2306              
2307             # Run the eval code to get HTML
2308             #
2309 20   33     106 my $html_sr=$Package{'_eval_cr'}{'!'}->($self, $data_ar, $attr_hr->{'param'}, "&${function}") || do {
2310              
2311              
2312             # Error occurred. Pop data ref off stack and return
2313             #
2314             shift @{$self->{'_perl'}};
2315             return err ();
2316              
2317              
2318             };
2319              
2320              
2321             # Debug
2322             #
2323             #0 && debug('perl eval return %s', Dumper($html_sr));
2324              
2325              
2326             # Modify return value if we were returned an array. COMMENTED OUT - is done in eval
2327             #
2328             #(ref($html_sr) eq 'ARRAY') && do {
2329             # $html_sr=\ join(undef, map { ref($_) ? ${$_} : $_ } @{$html_sr})
2330             #};
2331              
2332              
2333             # Unless we have a scalar ref by now, the eval returned the
2334             # wrong type of value.
2335             #
2336 20 50       57 (ref($html_sr) eq 'SCALAR') || do {
2337              
2338              
2339             # Error occurred. Pop data ref off stack and return
2340             #
2341 0         0 shift @{$self->{'_perl'}};
  0         0  
2342 0         0 return err ("error in perl method '$method'- code did not return a SCALAR ref value.");
2343              
2344             };
2345              
2346              
2347             # Any printed data ? COMMENTED OUT - is done in eval
2348             #
2349             #$self->{'_print_ar'} && do {
2350             # $html_sr=\ join(undef, grep {$_} map { ref($_) ? ${$_} : $_ } @{delete $self->{'_print_ar'}}) };
2351              
2352              
2353             # Shift perl data_ar ref from stack
2354             #
2355 20         25 shift @{$self->{'_perl'}};
  20         33  
2356              
2357              
2358             # And return scalar val
2359             #
2360 20         58 return $html_sr
2361              
2362             }
2363              
2364             }
2365              
2366              
2367             sub perl_init {
2368              
2369              
2370             # Init the perl package space for this inode
2371             #
2372 18     18 0 39 my ($self, $perl_ar, $perl_debug_ar)=@_;
2373 18   50     50 my $inode=$self->{'_inode'} || 'ANON'; #ANON used when run from command line
2374              
2375              
2376             # Prep package space
2377             #
2378 18         53 0 && debug("perl_init inode $inode");
2379              
2380             #$Package{'_cache'}{$inode}{'perl_init'}++ && return \undef;
2381 18         23 0 && debug("init perl code $perl_ar, %s", Dumper($perl_ar));
2382 18         44 *{"WebDyne::${inode}::err"}=\&err;
  18         183  
2383 18     0   89 *{"WebDyne::${inode}::self"}=sub {$self};
  18         68  
  0         0  
2384 18     0   78 *{"WebDyne::${inode}::AUTOLOAD"}=sub {die("unknown function $AUTOLOAD")};
  18         94  
  0         0  
2385              
2386              
2387             # Run each piece of perl code
2388             #
2389 18         33 foreach my $ix (0..$#{$perl_ar}) {
  18         67  
2390              
2391              
2392             # Get perl code and debug information
2393             #
2394 9         20 my $perl_sr=$perl_ar->[$ix];
2395 9         13 my ($perl_line_no, $perl_srce_fn)=@{$perl_debug_ar->[$ix]};
  9         22  
2396              
2397              
2398             # Do not execute twice
2399             #
2400 9 50       71 $Package{'_cache'}{$inode}{'perl_init'}{$perl_sr}++ && next;
2401              
2402              
2403             # Set inc to include psp dir so can include packages easily
2404             #
2405 9         126 local @INC=@INC;
2406 9         39 push @INC, $self->cwd();
2407              
2408              
2409             # Wrap in anon CR, eval for syntax
2410             #
2411 9 50       82 if ($WEBDYNE_EVAL_SAFE) {
2412              
2413              
2414             # Safe mode, vars don't matter so much
2415             #
2416 0   0     0 my $safe_or=$self->{'_eval_safe'} || do {
2417             0 && debug('safe init (perl_init)');
2418             require Safe;
2419             require Opcode;
2420              
2421             #Safe->new($self->{'_inode'});
2422             Safe->new();
2423             };
2424 0   0     0 $self->{'_eval_safe'} ||= do {
2425 0         0 $safe_or->permit_only(@{$WEBDYNE_EVAL_SAFE_OPCODE_AR});
  0         0  
2426 0         0 $safe_or;
2427             };
2428 0 0       0 $safe_or->reval(${$perl_sr}, $WebDyne::WEBDYNE_EVAL_USE_STRICT) || do {
  0         0  
2429              
2430              
2431             # Nothing was returned - did an error occur ?
2432             #
2433 0 0 0     0 if ($@ || errstr()) {
2434              
2435             # An error has occurred. Deregister self subroutine call in package
2436             #
2437 0         0 undef *{"WebDyne::${inode}::self"};
  0         0  
2438              
2439              
2440             # Make up a fake data block with details of error
2441             #
2442 0         0 my @data;
2443 0         0 @data[
2444             $WEBDYNE_NODE_LINE_IX,
2445             $WEBDYNE_NODE_LINE_TAG_END_IX,
2446             $WEBDYNE_NODE_SRCE_IX,
2447             ]=($perl_line_no, $perl_line_no, $perl_srce_fn);
2448              
2449              
2450             # Save away as current data block for reference by error handler
2451             #
2452 0         0 $self->{'_data_ar'}=\@data;
2453              
2454              
2455             # Throw error
2456             #
2457 0 0       0 return $self->err_eval($@ ? "error in __PERL__ block: $@" : undef, $perl_sr);
2458              
2459             }
2460             };
2461              
2462              
2463             }
2464             else {
2465              
2466              
2467             # Now init the perl code
2468             #
2469 9   66     87 my $eval_cr=&perl_init_cr($inode, $perl_sr, $perl_line_no) || do {
2470              
2471              
2472             # Nothing was returned from perl_init - did an error occur ?
2473             #
2474             if ($@ || errstr()) {
2475              
2476              
2477             # An error has occurred. Deregister self subroutine call in package
2478             #
2479             undef *{"WebDyne::${inode}::self"};
2480              
2481              
2482             # Make up a fake data block with details of error
2483             #
2484             my @data;
2485             @data[
2486             $WEBDYNE_NODE_LINE_IX,
2487             $WEBDYNE_NODE_LINE_TAG_END_IX,
2488             $WEBDYNE_NODE_SRCE_IX,
2489             ]=($perl_line_no, $perl_line_no, $perl_srce_fn);
2490              
2491              
2492             # Save away as current data block for reference by error handler
2493             #
2494             $self->{'_data_ar'}=\@data;
2495              
2496              
2497             # Throw error
2498             #
2499             return $self->err_eval($@ ? "error in __PERL__ block: $@" : undef, $perl_sr);
2500              
2501             }
2502             };
2503             }
2504              
2505              
2506             }
2507              
2508              
2509             # Done
2510             #
2511 18         37 undef *{"WebDyne::${inode}::self"};
  18         138  
2512 18         30 0 && debug('perl_init complete');
2513 18         111 \undef;
2514              
2515             }
2516              
2517              
2518             sub subst {
2519              
2520              
2521             # Called to eval text block, replace params
2522             #
2523 16     16 0 43 my ($self, $data_ar, $attr_hr, $param_data_hr, $text)=@_;
2524              
2525              
2526             # Debug
2527             #
2528 16         20 0 && debug("eval $text %s", Dumper($param_data_hr));
2529              
2530              
2531             # Get eval code refs for subst
2532             #
2533 16   50     43 my $eval_cr=$Package{'_eval_cr'} ||
2534             return err ('unable to get eval code ref table');
2535              
2536              
2537             # Do we have to replace something in the text, look for pattern. We
2538             # should always find something, as subst tag is only inserted at
2539             # compile time in front of text with one of theses patterns
2540             #
2541 16         19 my $index;
2542             my $cr=sub {
2543 19   50 19   73 my $sr=$eval_cr->{$_[0]}($self, $data_ar, $param_data_hr, $_[1], $_[2]) ||
2544             return err ();
2545 19 50       52 (ref($sr) eq 'SCALAR') ||
2546             return err ("eval of '$_[1]' returned %s ref, should return SCALAR ref", ref($sr));
2547 19         83 $sr;
2548 16         85 };
2549 16 50       136 $text=~s/([\$!+*^]){1}{(\1?)(.*?)\2}/${$cr->($1,$3,$index++) || return err()}/ge;
  19         28  
  19         82  
2550              
2551              
2552             # Done
2553             #
2554 16         92 return \$text;
2555              
2556              
2557             }
2558              
2559              
2560             sub subst_attr {
2561              
2562              
2563             # Called to eval tag attributes
2564             #
2565 18     18 0 40 my ($self, $data_ar, $attr_hr, $param_hr)=@_;
2566              
2567              
2568             # Debug
2569             #
2570 18         25 0 && debug('subst_attr %s', Dumper({%{$attr_hr}, perl => undef}));
2571              
2572              
2573             # Get eval code refs for subst
2574             #
2575 18   50     50 my $eval_cr=$Package{'_eval_cr'} ||
2576             return err ('unable to get eval code ref table');
2577              
2578              
2579             # Hash to hold results
2580             #
2581 18         25 my %attr=%{$attr_hr};
  18         70  
2582              
2583              
2584             # Go through each attribute and value
2585             #
2586 18         28 my $index;
2587 18         66 while (my ($attr_name, $attr_value)=each %attr) {
2588              
2589              
2590             # Skip perl attr, as that is perl code, do not do any regexp on perl code, as we will
2591             # probably botch it.
2592             #
2593 27 50       61 next if ($attr_name eq 'perl');
2594              
2595              
2596             # Look for attribute value strings that need substitution. First and second attemps did'nt work as single regexp
2597             #
2598             #if ($attr_value=~/^\s*([\$@%!+*^]){1}{(\1?)([^{]+)\2}\s*$/so ) {
2599             #if ($attr_value=~/^\s*([\$@%!+*^]){1}{(\1?)(.*)\2}\s*$/so ) {
2600 27 100 100     179 if ($attr_value=~/^\s*([\@%!+*^]){1}{(\1?)(.*)\2}\s*$/so || $attr_value=~/^\s*(\$){1}{(\1?)([^{]+)\2}\s*$/so) {
2601              
2602             # Straightforward $@%!+^ operator, must be only content of value (can't be mixed
2603             # with string, e.g.
2604             #
2605 16         55 my ($oper, $eval_text)=($1, $3);
2606 16   50     57 my $eval=$eval_cr->{$oper}->($self, $data_ar, $param_hr, $eval_text, $index++, 1) ||
2607             return err ();
2608 16 100       77 $attr{$attr_name}=(ref($eval) eq 'SCALAR') ? ${$eval} : $eval;
  5         26  
2609              
2610             }
2611             else {
2612              
2613             # Trickier - might be interspersed in strings, e.g
2614             # Substitution needed
2615             #
2616             my $cr=sub {
2617 10   50 10   27 my $sr=$eval_cr->{$_[0]}($self, $data_ar, $param_hr, $_[1], $_[2]) ||
2618             return err ();
2619 10 50       19 (ref($sr) eq 'SCALAR') ||
2620             return err ("eval of '$_[1]' returned %s ref, should return SCALAR ref", ref($sr));
2621 10         38 $sr;
2622 11         57 };
2623 11 50       37 $attr_value=~s/([\$!+*^]){1}{(\1?)(.*?)\2}/${$cr->($1,$3,$index++) || return err()}/ge;
  10         16  
  10         19  
2624 11         69 $attr{$attr_name}=$attr_value;
2625              
2626             }
2627              
2628             }
2629              
2630              
2631             # Debug
2632             #
2633 18         28 0 && debug('returning attr hash %s', Dumper({%attr, perl => undef}));
2634              
2635              
2636             # Return new attribute hash
2637             #
2638 18         52 \%attr;
2639              
2640             }
2641              
2642              
2643             sub include {
2644              
2645              
2646             # Called to include text/psp block. Can be called from tag or
2647             # perl code, so need to massage params appropriatly.
2648             #
2649 5     5 0 11 my $self=shift();
2650 5         13 my ($data_ar, $param_hr, $param_data_hr, $text);
2651              
2652              
2653             # Normally get:
2654             #
2655             # my ($self, $data_ar, $attr_hr, $param_data_hr, $text)=@_;
2656             #
2657             # from tag, but in this case param_hr subs for attr_hr because
2658             # we use that for code called from perl. Check what called us
2659             # now - if first param (after self) is array ref, called from
2660             # tag
2661             #
2662 5 100       16 if (ref($_[0]) eq 'ARRAY') {
2663              
2664             # Called from tag
2665             #
2666 3         9 ($data_ar, $param_hr, $param_data_hr, $text)=@_;
2667             }
2668             else {
2669              
2670             # Called from perl code, massage params into hr if not already there
2671             #
2672 2         4 $param_hr=shift();
2673 2 50       7 ref($param_hr) || ($param_hr={file => $param_hr, param => {@_}});
2674              
2675             }
2676              
2677              
2678             # Debug
2679             #
2680 5         8 0 && debug('in include, param %s, %s', Dumper($param_hr, $param_data_hr));
2681              
2682              
2683             # Get CWD
2684             #
2685 5   50     18 my $r=$self->r() || return err ();
2686 5   50     18 my $dn=(File::Spec->splitpath($r->filename()))[1] ||
2687             return err ('unable to determine cwd for requested file %s', $r->filename());
2688              
2689              
2690             # Any param must supply a file name as an attribute
2691             #
2692 5   50     46 my $fn=$param_hr->{'file'} ||
2693             return err ('no file name supplied with include tag');
2694 5         103 my $pn=File::Spec->rel2abs($fn, $dn);
2695              
2696              
2697             # Check what user wants to do
2698             #
2699 5 100       21 if (my $node=(grep {exists $param_hr->{$_}} qw(head body))[0]) {
  10 100       45  
2700              
2701              
2702             # They want to include the head or body section of an existing pure HTML
2703             # file.
2704             #
2705 2         5 0 && debug('head or body render');
2706 2         18 my %option=(
2707              
2708             nofilter => 1,
2709             noperl => 1,
2710             stage0 => 1,
2711             srce => $pn,
2712              
2713             );
2714              
2715             # compile spec'd file
2716             #
2717 2   50     18 my $container_ar=$self->compile(\%option) ||
2718             return err ();
2719 2         5 my $block_data_ar=$container_ar->[1];
2720 2         5 0 && debug('compiled to data_ar %s', Dumper($block_data_ar));
2721              
2722              
2723             # Find the head or body tag
2724             #
2725 2   50     10 my $block_ar=$self->find_node(
2726             {
2727              
2728             data_ar => $block_data_ar,
2729             tag => $node,
2730              
2731             }) || return err ();
2732 2 50       4 @{$block_ar} ||
  2         4  
2733             return err ("unable to find block '$node' in include file '$fn'");
2734 2         4 0 && debug('found block_ar %s', Dumper($block_ar));
2735              
2736              
2737             # Find_node returns array of blocks that match - we only want first
2738             #
2739 2         4 $block_ar=$block_ar->[0];
2740              
2741              
2742             # Need to finish compiling now found
2743             #
2744 2 50       10 $self->optimise_one($block_ar) || return err ();
2745 2 50       7 $self->optimise_two($block_ar) || return err ();
2746 2         4 0 && debug('optimised data now %s', Dumper($block_ar));
2747              
2748              
2749             # Need to encapsulate into tag, so alter tag name, attr
2750             #
2751 2         6 $block_ar->[$WEBDYNE_NODE_NAME_IX]='block';
2752 2         11 $block_ar->[$WEBDYNE_NODE_ATTR_IX]={name => $node, display => 1};
2753              
2754              
2755             # Incorporate into top level data so we don't have to do this again if
2756             # called from tag
2757             #
2758 2 50       6 @{$data_ar}=@{$block_ar} if $data_ar;
  2         7  
  2         3  
2759              
2760              
2761             # Render included block and return
2762             #
2763 2   33     18 return $self->render({data => $block_ar->[$WEBDYNE_NODE_CHLD_IX], param => $param_hr->{'param'}}) || err ();
2764              
2765             }
2766             elsif (my $block=$param_hr->{'block'}) {
2767              
2768             # Wants to include a paticular block from a psp library file
2769             #
2770 2         8 0 && debug('block render');
2771 2         16 my %option=(
2772              
2773             nofilter => 1,
2774              
2775             #noperl => 1,
2776             stage1 => 1,
2777             srce => $pn
2778              
2779             );
2780              
2781             # compile spec'd file
2782             #
2783 2   50     19 my $container_ar=$self->compile(\%option) ||
2784             return err ();
2785 2         6 my $block_data_ar=$container_ar->[1];
2786 2         3 0 && debug('block data %s', Dumper($block_data_ar));
2787              
2788              
2789             # Find the block node with name we want
2790             #
2791 2         16 0 && debug("looking for block name $block");
2792 2   50     29 my $block_ar=$self->find_node(
2793             {
2794              
2795             data_ar => $block_data_ar,
2796             tag => 'block',
2797             attr_hr => {name => $block},
2798              
2799             }) || return err ();
2800 2 50       9 @{$block_ar} ||
  2         9  
2801             return err ("unable to find block '$block' in include file '$fn'");
2802 2         6 0 && debug('found block_ar %s', Dumper($block_ar));
2803              
2804              
2805             # Find_node returns array of blocks that match - we only want first
2806             #
2807 2         5 $block_ar=$block_ar->[0];
2808              
2809              
2810             # Set to attr always display
2811             #
2812 2         9 $block_ar->[$WEBDYNE_NODE_ATTR_IX]{'display'}=1;
2813              
2814              
2815             # Incorporate into top level data so we don't have to do this again if
2816             # called from tag
2817             #
2818 2 50       9 @{$data_ar}=@{$block_ar} if $data_ar;
  0         0  
  0         0  
2819              
2820              
2821             # We don't want to render tags, so start at
2822             # child of results [WEBDYNE_NODE_CHLD_IX].
2823             #
2824 2         4 0 && debug('calling render');
2825 2   33     36 return $self->render({data => $block_ar->[$WEBDYNE_NODE_CHLD_IX], param => ($param_hr->{'param'} || $param_data_hr)}) || err ();
2826              
2827             }
2828             else {
2829              
2830              
2831             # Plain vanilla file include, no mods
2832             #
2833 1         6 0 && debug('vanilla file include');
2834 1   50     14 my $fh=IO::File->new($pn, O_RDONLY) || return err ("unable to open file '$fn' for read, $!");
2835 1         96 my @html;
2836 1         15 while (<$fh>) {
2837 1         6 push @html, $_;
2838             }
2839 1         8 $fh->close();
2840 1         17 \join(undef, @html);
2841              
2842             }
2843              
2844             }
2845              
2846              
2847             sub find_node {
2848              
2849              
2850             # Find a particular node in the tree
2851             #
2852 36     36 0 79 my ($self, $param_hr)=@_;
2853              
2854              
2855             # Get max depth we can descend to, zero out in params
2856             #
2857 36         99 my ($data_ar, $tag, $attr_hr, $depth_max, $prnt_fg, $all_fg)=@{$param_hr}{
2858 36         56 qw(data_ar tag attr_hr depth prnt_fg all_fg)
2859             };
2860 36         46 0 && debug("find_node looking for tag $tag in data_ar $data_ar, %s", Dumper($data_ar));
2861              
2862              
2863             # Array to hold results, depth
2864             #
2865 36         54 my ($depth, @node);
2866              
2867              
2868             # Create recursive anon sub
2869             #
2870             my $find_cr=sub {
2871              
2872              
2873             # Get params
2874             #
2875 87     87   138 my ($find_cr, $data_ar, $data_prnt_ar)=@_;
2876 87         118 0 && debug("find_cr, data_ar $data_ar, data_prnt_ar $data_prnt_ar");
2877              
2878              
2879             # Do we match at this level ?
2880             #
2881 87 100       182 if ((my $data_ar_tag=$data_ar->[$WEBDYNE_NODE_NAME_IX]) eq $tag) {
2882              
2883              
2884             # Match for tag name, now check any attrs
2885             #
2886 26         36 my $tag_attr_hr=$data_ar->[$WEBDYNE_NODE_ATTR_IX];
2887              
2888              
2889             # Debug
2890             #
2891 26         28 0 && debug("tag '$tag' match, $data_ar_tag, checking attr %s", Dumper($tag_attr_hr));
2892              
2893              
2894             # Check for match
2895             #
2896 26 100       31 if (
2897 12         39 (grep {$tag_attr_hr->{$_} eq $attr_hr->{$_}} keys %{$tag_attr_hr}) ==
  26         63  
2898 26         64 (keys %{$attr_hr})
2899             ) {
2900              
2901              
2902             # Match, debug
2903             #
2904 25         32 0 && debug("$data_ar_tag attr match, saving");
2905              
2906              
2907             # Tag name and attribs match, push onto node
2908             #
2909 25 50       63 push @node, $prnt_fg ? $data_prnt_ar : $data_ar;
2910 25 100       84 return $node[0] unless $all_fg;
2911              
2912              
2913             }
2914              
2915             }
2916             else {
2917              
2918 61         70 0 && debug("mismatch on tag $data_ar_tag for tag '$tag'");
2919              
2920             }
2921              
2922              
2923             # Return if out of depth
2924             #
2925 69 50 33     126 return if ($depth_max && (++$depth > $depth_max));
2926              
2927              
2928             # Start looking through current node
2929             #
2930 69 100       121 my @data_child_ar=$data_ar->[$WEBDYNE_NODE_CHLD_IX] ? @{$data_ar->[$WEBDYNE_NODE_CHLD_IX]} : undef;
  64         122  
2931 69         110 foreach my $data_child_ar (@data_child_ar) {
2932              
2933              
2934             # Only check and/or recurse through children that are child nodes, (ie
2935             # are refs), ignor non-ref (text) nodes
2936             #
2937 80 100       208 ref($data_child_ar) && do {
2938              
2939              
2940             # We have a ref, recurse look for match
2941             #
2942 51 100       154 if (my $data_match_ar=$find_cr->($find_cr, $data_child_ar, $data_ar)) {
2943              
2944              
2945             # Found match during recursion, return
2946             #
2947 21 50       59 return $data_match_ar unless $all_fg;
2948              
2949             }
2950              
2951             }
2952              
2953             }
2954              
2955 36         219 };
2956              
2957              
2958             # Start it running with our top node
2959             #
2960 36         95 $find_cr->($find_cr, $data_ar);
2961              
2962              
2963             # Debug
2964             #
2965 36         44 0 && debug('find complete, return node %s', \@node);
2966              
2967              
2968             # Return results
2969             #
2970 36         352 return \@node;
2971              
2972             }
2973              
2974              
2975             sub delete_node {
2976              
2977              
2978             # Delete a particular node from the tree
2979             #
2980 0     0 0 0 my ($self, $param_hr)=@_;
2981              
2982              
2983             # Get max depth we can descend to, zero out in params
2984             #
2985 0         0 my ($data_ar, $node_ar)=@{$param_hr}{qw(data_ar node_ar)};
  0         0  
2986 0         0 0 && debug("delete node $node_ar starting from data_ar $data_ar");
2987              
2988              
2989             # Create recursive anon sub
2990             #
2991             my $find_cr=sub {
2992              
2993              
2994             # Get params
2995             #
2996 0     0   0 my ($find_cr, $data_ar)=@_;
2997              
2998              
2999             # Iterate through child nodes
3000             #
3001 0         0 foreach my $data_chld_ix (0..$#{$data_ar->[$WEBDYNE_NODE_CHLD_IX]}) {
  0         0  
3002              
3003 0   0     0 my $data_chld_ar=$data_ar->[$WEBDYNE_NODE_CHLD_IX][$data_chld_ix] ||
3004             return err ("unable to get chld node from $data_ar");
3005 0         0 0 && debug("looking at chld node $data_chld_ar");
3006              
3007 0 0       0 if ($data_chld_ar eq $node_ar) {
3008              
3009             # Found node we want to delete. Get rid of it, all done
3010             #
3011 0         0 0 && debug("match - splicing at chld $data_chld_ix from array %s", Dumper($data_ar));
3012 0         0 splice(@{$data_ar->[$WEBDYNE_NODE_CHLD_IX]}, $data_chld_ix, 1);
  0         0  
3013 0         0 return \1;
3014              
3015             }
3016             else {
3017              
3018              
3019             # Not target node - recurse
3020             #
3021 0         0 0 && debug("no match - recursing to chld $data_chld_ar");
3022 0 0       0 ${$find_cr->($find_cr, $data_chld_ar) || return err ()} &&
  0 0       0  
3023             return \1;
3024              
3025             }
3026             }
3027              
3028              
3029             # All done, but no cigar
3030             #
3031 0         0 return \undef;
3032              
3033 0         0 };
3034              
3035              
3036             # Start
3037             #
3038 0   0     0 return $find_cr->($find_cr, $data_ar) || err ()
3039              
3040             }
3041              
3042              
3043             sub CGI {
3044              
3045              
3046             # Accessor method for CGI object
3047             #
3048 11   66 11 0 76 return shift()->{'_CGI'} ||= do {
3049              
3050             # Debug
3051             #
3052 10         14 0 && debug('CGI init');
3053              
3054              
3055             # Need to turn off XHTML generation - CGI wants to turn it on every time for
3056             # some reason
3057             #
3058 10         32 $CGI::XHTML=0;
3059 10         32 $CGI::NOSTICKY=1;
3060              
3061              
3062             # CGI good practice
3063             #
3064 10         18 $CGI::DISABLE_UPLOADS=$WEBDYNE_CGI_DISABLE_UPLOADS;
3065 10         19 $CGI::POST_MAX=$WEBDYNE_CGI_POST_MAX;
3066              
3067              
3068             # And create it
3069             #
3070 10         101 my $cgi_or=CGI::->new();
3071              
3072              
3073             # Set defaults
3074             #
3075 10         3437 $cgi_or->autoEscape($WEBDYNE_CGI_AUTOESCAPE);
3076              
3077              
3078             # Expand params if we need to
3079             #
3080 10 50       161 &CGI_param_expand($cgi_or) if $WEBDYNE_CGI_PARAM_EXPAND;
3081              
3082              
3083             # Return new CGI object
3084             #
3085 10         184 $cgi_or;
3086              
3087             };
3088              
3089             }
3090              
3091              
3092             sub CGI_param_expand {
3093              
3094             # Expand CGI params if the form "foo;a=b" into "foo=param", "a=b";
3095             #
3096 10   50 10 0 29 my $cgi_or=shift() ||
3097             return err ("unable to get CGI object");
3098 10         35 local ($CGI::LIST_CONTEXT_WARN)=0;
3099 10         33 foreach my $param (grep /=/, $cgi_or->param()) {
3100 0         0 my (@pairs)=split(/[&;]/, $param);
3101 0         0 foreach my $pair (@pairs) {
3102 0         0 my ($key, $value)=split('=', $pair, 2);
3103 0   0     0 $value ||= $cgi_or->param($param);
3104 0         0 $key=&CGI::unescape($key);
3105 0         0 $value=&CGI::unescape($value);
3106 0         0 $cgi_or->param($key, $value);
3107             }
3108 0         0 $cgi_or->delete($param);
3109             }
3110             }
3111              
3112              
3113             sub request {
3114              
3115              
3116             # Accessor method for Apache request object
3117             #
3118 5     5 0 8 my $self=shift();
3119 5 50       23 return @_ ? $self->{'_r'}=shift() : $self->{'_r'};
3120              
3121             }
3122              
3123              
3124             sub dump {
3125              
3126              
3127             # Run the dump CGI dump routine. Is here because it produces different output each
3128             # time it is run, and if not a WebDyne tag it would be optimised to static text by
3129             # the compiler
3130             #
3131 0     0 0 0 my ($self, $data_ar, $attr_hr)=@_;
3132 0 0 0     0 return ($WEBDYNE_DUMP_FLAG || $attr_hr->{'force'} || $attr_hr->{'display'}) ? \$self->{'_CGI'}->Dump() : \undef;
3133              
3134             }
3135              
3136              
3137             sub cwd {
3138              
3139             # Return cwd of current psp file
3140             #
3141 9     9 0 57 (File::Spec->splitpath(shift()->{'_r'}->filename()))[1];
3142              
3143             }
3144              
3145              
3146             sub source_mtime {
3147              
3148             # Get mtime of source file. Is a no-op here so can be subclassed by other handlers. We
3149             # return undef, means engine will use original source mtime
3150             #
3151 10     10 0 84 \undef;
3152              
3153             }
3154              
3155              
3156             sub cache_mtime {
3157              
3158             # Mtime accessor - will return mtime of srce inode (default), or mtime of supplied
3159             # inode if given
3160             #
3161 0     0 0 0 my $self=shift();
3162             my $inode_pn=${
3163 0 0       0 $self->cache_filename(@_) || return err ()};
  0         0  
3164 0 0       0 \(stat($inode_pn))[9] if $inode_pn;
3165              
3166             }
3167              
3168              
3169             sub cache_filename {
3170              
3171             # Get cache fq filename given inode or using srce inode if not supplied
3172             #
3173 0     0 0 0 my $self=shift();
3174 0 0       0 my $inode=@_ ? shift() : $self->{'_inode'};
3175 0 0       0 my $inode_pn=File::Spec->catfile($WEBDYNE_CACHE_DN, $inode) if $WEBDYNE_CACHE_DN;
3176 0         0 \$inode_pn;
3177              
3178             }
3179              
3180              
3181             sub cache_inode {
3182              
3183             # Get cache inode string, or generate new unique inode
3184             #
3185 0     0 0 0 my $self=shift();
3186 0 0       0 @_ && ($self->{'_inode'}=md5_hex($self->{'_inode'}, $_[0]));
3187              
3188             # See comment in handler section about future inode gen
3189             #
3190             #@_ && ($self->{'_inode'}.=('_'. md5_hex($_[0])));
3191 0         0 \$self->{'_inode'};
3192              
3193             }
3194              
3195              
3196             sub cache_html {
3197              
3198             # Write an inode that is fully HTML out to disk to we dispatch it as a subrequest
3199             # next time. This is a ®ister_cleanup callback
3200             #
3201 0     0 0 0 my ($cache_pn, $html_sr)=@_;
3202 0         0 0 && debug("cache_html @_");
3203              
3204             # If there was an error no html_sr will be supplied
3205             #
3206 0 0       0 if ($html_sr) {
3207              
3208             # No point || return err(), just warn so (maybe) is written to logs, otherwise go for it
3209             #
3210 0   0     0 my $cache_fh=IO::File->new($cache_pn, O_WRONLY | O_CREAT | O_TRUNC) ||
3211             return warn("unable to open cache file $cache_pn for write, $!");
3212 0         0 CORE::print $cache_fh ${$html_sr};
  0         0  
3213 0         0 $cache_fh->close();
3214             }
3215 0         0 \undef;
3216              
3217             }
3218              
3219              
3220             sub cache_compile {
3221              
3222             # Compile flag accessor - if set will force inode recompile, regardless of mtime
3223             #
3224 0     0 0 0 my $self=shift();
3225 0 0       0 @_ && ($self->{'_compile'}=shift());
3226 0         0 0 && debug("cache_compile set to %s", $self->{'_compile'});
3227 0         0 \$self->{'_compile'};
3228              
3229             }
3230              
3231              
3232             sub filter {
3233              
3234              
3235             # No op
3236             #
3237 0     0 0 0 my ($self, $data_ar)=@_;
3238 0         0 0 && debug('in filter');
3239 0         0 $data_ar;
3240              
3241             }
3242              
3243              
3244             sub meta {
3245              
3246             # Return/read/update meta info hash
3247             #
3248 0     0 0 0 my ($self, @param)=@_;
3249 0         0 my $inode=$self->{'_inode'};
3250 0         0 0 && debug("get meta data for inode $inode");
3251 0   0     0 my $meta_hr=$Package{'_cache'}{$inode}{'meta'} ||= (delete $self->{'_meta_hr'} || {});
      0        
3252 0         0 0 && debug("existing meta $meta_hr %s", Dumper($meta_hr));
3253 0 0       0 if (@param == 2) {
    0          
3254 0         0 return $meta_hr->{$param[0]}=$param[1];
3255             }
3256             elsif (@param) {
3257 0         0 return $meta_hr->{$param[0]};
3258             }
3259             else {
3260 0         0 return $meta_hr;
3261             }
3262              
3263             }
3264              
3265              
3266             sub static {
3267              
3268              
3269             # Set static flag for this instance only. If all instances wanted
3270             # set in meta data. This method used by WebDyne::Static module
3271             #
3272 0     0 0 0 my $self=shift();
3273 0         0 $self->{'_static'}=1;
3274              
3275              
3276             }
3277              
3278              
3279             sub cache {
3280              
3281             # Set cache handler for this instance only. If all instances wanted
3282             # set in meta data. This method used by WebDyne::Cache module
3283             #
3284 0     0 0 0 my $self=shift();
3285 0   0     0 $self->{'_cache'}=shift() ||
3286             return err ('cache code ref or method name must be supplied');
3287              
3288             }
3289              
3290              
3291             sub set_filter {
3292              
3293             # Set cache handler for this instance only. If all instances wanted
3294             # set in meta data. This method used by WebDyne::Cache module
3295             #
3296 0     0 0 0 my $self=shift();
3297 0   0     0 $self->{'_filter'}=shift() ||
3298             return err ('filter name must be supplied');
3299              
3300             }
3301              
3302              
3303             sub set_handler {
3304              
3305              
3306             # Set/return internal handler. Only good in __PERL__ block, after
3307             # that is too late !
3308             #
3309 0     0 0 0 my $self=shift();
3310 0   0     0 my $meta_hr=$self->meta() || return err ();
3311 0 0       0 @_ && ($meta_hr->{'handler'}=shift());
3312 0         0 \$meta_hr->{'handler'};
3313              
3314              
3315             }
3316              
3317              
3318             sub select {
3319              
3320              
3321             # If we are in select mode where print output is redirected to handler output
3322             #
3323 0     0 0 0 shift->{'_select'};
3324              
3325             }
3326              
3327              
3328             sub inode {
3329              
3330              
3331             # Return inode name
3332             #
3333 0     0 0 0 my $self=shift();
3334 0 0       0 @_ ? $self->{'_inode'}=shift() : $self->{'_inode'};
3335              
3336             }
3337              
3338              
3339             sub data_ar {
3340              
3341              
3342             # Return current data node, assumes we are in a perl block or subst
3343             #
3344 0     0 0 0 shift()->{'_data_ar'};
3345              
3346             }
3347              
3348              
3349             sub data_ar_html_srce_fn {
3350              
3351              
3352             # The file name that this data node was sourced from
3353             #
3354 0     0 0 0 my ($self, $data_ar)=@_;
3355 0 0 0     0 if ($data_ar ||= $self->data_ar()) {
3356 0         0 return ${$data_ar->[$WEBDYNE_NODE_SRCE_IX]}
  0         0  
3357             }
3358              
3359             }
3360              
3361              
3362             sub data_ar_html_line_no {
3363              
3364              
3365             # The line number (in the original HTML file) this data node was sourced from. Return tag start line in scalar ref, tag start + tag end in array ref
3366             #
3367 0     0 0 0 my ($self, $data_ar)=@_;
3368 0 0 0     0 if ($data_ar ||= $self->data_ar()) {
3369 0 0       0 return wantarray ? @{$data_ar}[$WEBDYNE_NODE_LINE_IX, $WEBDYNE_NODE_LINE_TAG_END_IX] : $data_ar->[$WEBDYNE_NODE_LINE_IX];
  0         0  
3370             }
3371              
3372              
3373             }
3374              
3375              
3376             sub print {
3377              
3378 3     3 0 6 my $self=shift();
3379 3         5 my $data_ar=$self->{'_data_ar'};
3380 3   50     5 push @{$self->{'_print_ar'}{$data_ar} ||= []}, @_;
  3         19  
3381 3         10 return \undef;
3382              
3383             }
3384              
3385              
3386             sub printf {
3387              
3388 0     0 0 0 my $self=shift();
3389 0         0 my $data_ar=$self->{'_data_ar'};
3390 0   0     0 push @{$self->{'_print_ar'}{$data_ar} ||= []}, sprintf(shift(), @_);
  0         0  
3391 0         0 return \undef;
3392              
3393             }
3394              
3395              
3396             sub DESTROY {
3397              
3398              
3399             # Stops AUTOLOAD chucking wobbly at end of request because no DESTROY method
3400             # found, logs total page cycle time
3401             #
3402 10     10   27 my $self=shift();
3403              
3404              
3405             # Call CGI reset_globals if we created a CGI object
3406             #
3407 10 50       56 $self->{'_CGI'} && (&CGI::_reset_globals);
3408              
3409              
3410             # Work out complete request cylcle time
3411             #
3412 10         437 0 && debug("in destroy self $self, param %s", Dumper(\@_));
3413 10         73 my $time_request=sprintf('%0.4f', time()-$self->{'_time'});
3414 10         16 0 && debug("page request cycle time , $time_request sec");
3415              
3416              
3417             # Destroy object
3418             #
3419 10         15 %{$self}=();
  10         83  
3420 10         145 undef $self;
3421              
3422             }
3423              
3424              
3425             sub AUTOLOAD {
3426              
3427              
3428             # Get self ref
3429             #
3430 0     0   0 my $self=$_[0];
3431 0         0 0 && debug("AUTOLOAD $self, $AUTOLOAD");
3432              
3433              
3434             # Get method user was looking for
3435             #
3436 0         0 my $method=(reverse split(/\:+/, $AUTOLOAD))[0];
3437              
3438              
3439             # Vars for iterator, call stack
3440             #
3441 0         0 my $i; my @caller;
3442              
3443              
3444             # Start going backwards through call stack, looking for package that can
3445             # run method, pass control to it if found
3446             #
3447 0         0 my %caller;
3448 0         0 while (my $caller=(caller($i++))[0]) {
3449 0 0       0 next if ($caller{$caller}++);
3450 0         0 push @caller, $caller;
3451 0 0       0 if (my $cr=UNIVERSAL::can($caller, $method)) {
3452              
3453             # POLLUTE is virtually useless - no speedup in real life ..
3454 0 0       0 if ($WEBDYNE_AUTOLOAD_POLLUTE) {
3455 0         0 my $class=ref($self);
3456 0         0 *{"${class}::${method}"}=$cr;
  0         0  
3457             }
3458              
3459             #return $cr->($self, @_);
3460 0         0 goto &{$cr}
  0         0  
3461             }
3462             }
3463              
3464              
3465             # If we get here, we could not find the method in any caller. Error
3466             #
3467 0         0 err ("unable to find method '$method' in call stack: %s", join(', ', @caller));
3468 0         0 goto RENDER_ERROR;
3469              
3470             }
3471              
3472              
3473             # Package to tie select()ed output handle to so we can override print() command
3474             #
3475             package WebDyne::TieHandle;
3476              
3477              
3478             sub TIEHANDLE {
3479              
3480 10     10   32 my ($class, $self)=@_;
3481 10         66 bless \$self, $class;
3482             }
3483              
3484              
3485             sub PRINT {
3486              
3487 3     3   6 my $self=shift();
3488 3         4 return ${$self}->print(@_);
  3         10  
3489              
3490             }
3491              
3492              
3493             sub PRINTF {
3494              
3495 0     0   0 my $self=shift();
3496 0         0 return ${$self}->printf(@_);
  0         0  
3497              
3498             }
3499              
3500              
3501       0     sub DESTROY {
3502             }
3503              
3504              
3505       10     sub UNTIE {
3506             }
3507              
3508              
3509       0     sub AUTOLOAD {
3510             }
3511              
3512              
3513             __END__