File Coverage

blib/lib/XAO/DO/Web/Page.pm
Criterion Covered Total %
statement 452 508 88.9
branch 224 280 80.0
condition 61 94 64.8
subroutine 48 52 92.3
pod 22 32 68.7
total 807 966 83.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XAO::DO::Web::Page - core object of XAO::Web rendering system
4              
5             =head1 SYNOPSIS
6              
7             Outside web environment:
8              
9             my $page=XAO::Objects->new(objname => 'Page');
10             my $date=$page->expand(template => '<%Date%>');
11              
12             Inside XAO::Web template:
13              
14             <%Page path="/bits/some-path" ARG={<%SomeObject/f%>}%>
15              
16             =head1 DESCRIPTION
17              
18             As XAO::DO::Web::Page object (from now on just Page displayable
19             object) is the core object for XAO::Web web rendering engine we
20             will start with basics of how it works.
21              
22             The goal of XAO::Web rendering engine is to produce HTML data file
23             that can be understood by browser and displayed to a user. It will
24             usually use database tables, templates and various displayable objects
25             to achieve that.
26              
27             Every time a page is requested in someone's web browser a XAO::Web handler
28             gets executed, prepares site configuration, opens database connection,
29             determines what would be start object and/or start path and does a lot
30             of other useful things. If you have not read about it yet it is suggested to
31             do so -- see L and L.
32              
33             Although XAO::Web handler can call arbitrary object with arbitrary arguments
34             to produce an HTML page we will assume the simplest scenario of calling
35             Page object with just one argument -- path to an HTML file template for
36             simplicity (another way to pass some template to a Page object is
37             to pass argument named "template" with the template text as the
38             value). This is the default behavior of XAO::Web handler if you
39             do not override it in configuration.
40              
41             Let's say user asked for http://oursite.com/ and XAO::Web translated
42             that into the call to Page's display method with "path" argument set to
43             "/index.html". All template paths are treated relative to "templates"
44             directory in site directory or to system-wide "templates" directory if
45             site-specific template does not exist. Suppose templates/index.html file
46             in our site's home directory contains the following:
47              
48             Hello, World!
49              
50             As there are no special symbols in that template Page's display method
51             will return exactly that text without any changes (it will also cache
52             pre-parsed template for re-use under mod_perl, but this is irrelevant
53             for now).
54              
55             Now let's move to a more complex example -- suppose we want some kind of
56             header and footer around our text:
57              
58             <%Page path="/bits/header-template"%>
59              
60             Hello, World!
61              
62             <%Page path="/bits/footer-template"%>
63              
64             Now, Page's parser sees reference to other items in that template -
65             these things, surrounded by <% %> signs. What it does is the following.
66              
67             First it checks if there is an argument given to original Page's
68             display() method named 'Page' (case sensitive). In our case there is no
69             such argument present.
70              
71             Then, as no such static argument is found, it attempts to load an
72             object named 'Page' and pass whatever arguments given to that object's
73             display method.
74              
75             I it is recommended to name static
76             arguments in all-lowercase (for standard parameters accepted by an
77             object) or all-uppercase (for parameters that are to be included into
78             template literally) letters to distinguish them from object names where
79             only the first letter of every word is capitalized.
80              
81             In our case Page's parser will create yet another instance of Page
82             displayable object and pass argument "path" with value
83             "/bits/header-template". That will include the content of
84             templates/bits/header-template file into the output. So, if the content
85             of /bits/header-template file is:
86              
87            
88              
89             And the content of /bits/footer-template is:
90              
91            
92              
93             Then the output produced by the original Page's display would be:
94              
95            
96              
97             Hello, World!
98              
99            
100              
101             For the actual site you might opt to use specific objects for header and
102             footer (see L and L):
103              
104             <%Header title="My first XAO::Web page"%>
105              
106             Hello, World!
107              
108             <%Footer%>
109              
110             Page's parser is not limited to only these simple cases, you can embed
111             references to variables and objects almost everywhere. In the following
112             example Utility object (see L) is used to
113             build complete link to a specific page:
114              
115             /somepage.html">blah blah blah
116              
117             If current (configured or guessed) site URL is "http://demosite.com/"
118             this template would be translated into:
119              
120             blah blah blah
121              
122             Even more interesting is that you can use embedding to create arguments
123             for embedded objects:
124              
125             <%Date gmtime={<%CgiParam param="shippingtime" default="0"%>}%>
126              
127             If your page was called with "shippingtime=984695182" argument in the
128             query then this code would expand to (in PST timezone):
129              
130             Thu Mar 15 14:26:22 2001
131              
132             As you probably noticed, in the above example argument value was in
133             curly brackets instead of quotes. Here are the options for passing
134             values for objects' arguments:
135              
136             =over
137              
138             =item 1
139              
140             You can surround value with double quotes: name="value". This is
141             recommended for short strings that do not include any " characters.
142              
143             =item 2
144              
145             You can surround value with matching curly brackets. Curly brackets
146             inside are allowed and counted so that these expansions would work:
147              
148             name={Some text with " symbols}
149              
150             name={Multiple
151             Lines}
152              
153             name={something <%Foo bar={test}%> alsdj}
154              
155             The interim brackets in the last example would be left untouched by the
156             parser. Although this example won't work because of unmatched brackets:
157              
158             name={single { inside}
159              
160             See below for various ways to include special symbols inside of
161             arguments.
162              
163             =item 3
164              
165             Just like for HTML files if the value does not include any spaces or
166             special symbols quotes can be left out:
167              
168             number=123
169              
170             But it is not recommended to use that method and it is not guaranteed
171             that this will remain legal in future versions. Kept mostly for
172             compatibility with already deployed code.
173              
174             =item 4
175              
176             To pass a string literally without performing any substitutions you can
177             use single quotes. For instance:
178              
179             <%FS
180             uri="/Members/<%MEMBER_ID/f%>"
181             mode="show-hash"
182             fields="*"
183             template='<%MEMBER_AGE/f%> -- <%MEMBER_STATUS/f%>'
184             %>
185              
186             If double quotes were used in this example then the parser would try to
187             expand <%MEMBER_AGE%> and <%MEMBER_STATUS%> variables using the current
188             object arguments which is not what is intended. Using single quotes it
189             is possible to let FS object do the expansion and therefore insert
190             database values in this case.
191              
192             =item 5
193              
194             To pass multiple nested arguments literally or to include a single quote
195             into the string matching pairs of {' and '} can be used:
196              
197             <%FS
198             uri="/Members/<%MEMBER_ID/f%>"
199             mode="show-hash"
200             fields="*"
201             template={'Member's age is <%MEMBER_AGE/f%>'}
202             %>
203              
204             =back
205              
206              
207             =head2 BINARY vs UNICODE
208              
209             By default expanded templates and object arguments are bytes, not
210             Unicode characters. This does not mean that they cannot be used in
211             Unicode (or other encoding) web sites, but it does mean that objects
212             need to convert arguments and expansion results into characters where
213             and as needed.
214              
215             Starting with version 1.48 it is possible to switch the engine
216             into returning perl characters from template expansion and for
217             passing characters in object arguments. This is achieved by setting
218             /xao/page/character_mode configuration parameter to '1'.
219              
220             One exception allowing to retrieve byte data is when a template is
221             expanded with 'unparsed' qualifier. This is typically used to pass
222             binary data such as images and spreadsheets to objects such as
223             L.
224              
225             There is an important caveat for using 'unparsed' with binary data in
226             character mode. A template like the one below wou pass perl characters
227             in 'data' argument when executing Process.
228              
229             <%Process
230             data={<%Page
231             path='/binary/data.bin'
232             unparsed
233             %>}
234             %>
235              
236             There is no way to mix byte and character processing mode. Pick one
237             depending on your needs. Character mode allows a more natural processing
238             in Perl while byte mode allows more granular control over the data flow.
239              
240              
241             =head2 EMBEDDING SPECIAL CHARACTERS
242              
243             Sometimes it is necessary to include various special symbols into
244             argument values. This can be done in the same way you would embed
245             special symbols into HTML tags arguments:
246              
247             =over
248              
249             =item *
250              
251             By using &tag; construction, where tag could be "quot", "lt", "gt" and
252             "amp" for double quote, left angle bracket, right angle bracket and
253             ampersand respectfully.
254              
255             =item *
256              
257             By using &#NNN; construction where NNN is the decimal code for the
258             corresponding symbol. For example left curly bracket could be encoded as
259             { and right curly bracket as }. The above example should be
260             re-written as follows to make it legal:
261              
262             name={single { inside}
263              
264             =back
265              
266              
267             =head2 OUTPUT CONVERSION
268              
269             As the very final step in the processing of an embedded object or
270             variable the parser will check if it has any flags and convert it
271             accordingly. This can (and should) be used to safely pass special
272             characters into fields, HTML documents and so on.
273              
274             For instance, the following code might break if you do not use flags and
275             variable will contain a duoble quote character in it:
276              
277            
278              
279             Correct way to write it would be (note /f after VALUE):
280              
281            
282              
283             Generic format for specifying flags is:
284              
285             <%Object/x ...%> or <$VARIABLE/x$>
286              
287             Where 'x' could be one of:
288              
289             =over
290              
291             =item f
292              
293             Converts text for safe use in HTML elements attributes. Mnemonic for
294             remembering - (f)ield.
295              
296             Will convert '123"234' into '123"234'.
297              
298             =item h
299              
300             Converts text for safe use in HTML text. Mnemonic - (H)TML.
301              
302             Will convert '123
234' into '123<BR>234'.
303              
304             =item q
305              
306             Converts text for safe use in HTML query parameters. Mnemonic - (q)uery.
307              
308             Will convert '123 234' into '123%20234'.
309              
310             Example: Test '<$VAR/h$>'
311              
312             =item s
313              
314             The same as 'h' excepts that it translates empty string into
315             ' '. Suitable for inserting pieces of text into table cells.
316              
317             =item u
318              
319             The same as 'q'. Mnemonic - (U)RL, as it can be used to convert text for
320             inclusion into URLs.
321              
322             =back
323              
324             It is a very good habit to use flags as much as possible and always
325             specify a correct conversion. Leaving output untranslated may lead to
326             anything from broken HTML to security violations.
327              
328             =head2 LEVELS OF PARSING
329              
330             Arguments can include as many level of embedding as you like, but you
331             must remember:
332              
333             =over
334              
335             =item 1
336              
337             That all embedded arguments are expanded from the deepest
338             level up to the top before executing main object.
339              
340             =item 2
341              
342             That undefined references to either non-existing object or non-existing
343             variable produce a run-time error and the page is not shown.
344              
345             =item 3
346              
347             All embedded arguments are processed in the same arguments space that
348             the template one level up from them.
349              
350             =back
351              
352             As a test of how you understood everything above please attempt to
353             predict what would be printed by the following example (after reading
354             L or guessing its meaning). The answer is
355             about one page down, at the end of this chapter.
356              
357             <%SetArg name="V1" value="{}"%>
358             <%SetArg name="V2" value={""}%>
359             <%Page template={<%V1%><%V2%>
360             <%Page template={<%SetArg name="V2" value="[]" override%><%V2%>}%>
361             <%V2%><%V1%>}
362             %>
363              
364             In most cases it is not recommended to make complex inline templates
365             though. It is usually better to move a sub-template into a separate file
366             and include it by passing a 'path' parameter into Page. Usually it is
367             also more efficient because templates with known paths are cached in
368             parsed state the first time they are used while inlined templates are
369             parsed every time.
370              
371             It is usually a good idea to make templates as simple as possible and move
372             most of the logic inside of objects. To comment what you're doing in
373             various parts of template you can use normal HTML-style comments. They
374             are removed from the output completely, so you can include any amount
375             of text inside of comments -- it won't impact the size of final HTML
376             file. Here is an example:
377              
378            
379             <%Header title="demosite.com"%>
380             <%Page path="/bits/menu"%>
381              
382            
383             <%Page path="/bits/body"%>
384              
385            
386             <%Footer%>
387              
388             One exception is JavaScript code which is usually put into comments. The
389             parser will NOT remove comments if the opening comment is
397            
398              
399              
400             =head2 CACHING
401              
402             Parsed templates are always cached either locally or using a configured
403             cache. The cache is keyed on 'path' or 'template' parameters value (two
404             identical 'template's will only parse once). Parse cache can be disabled
405             by giving a "xao.uncached" parameter. See parse() method description
406             for details.
407              
408             The fully rendered content can also be cached if a couple of conditions
409             are met:
410              
411             =over
412              
413             =item *
414              
415             /xao/page/render_cache_name in the config -- this should contain a name of
416             the cache to be used for rendered page components.
417              
418             =item *
419              
420             The page is configured to be cacheable with either an entry in
421             the configuration under '/xao/page/render_cache_allow' or with a
422             'xao.cacheable' parameter given (e.g. something like <%Page ...
423             xao.cacheable%>).
424              
425             =item *
426              
427             There is no "/xao/page/render_cache_update" in the clipboard. This can be used
428             to force cache reload by checking some environmental variable early in
429             the flow and setting the clipboard to disable all render caches for that
430             one render. Cached content is not used, but is updated -- so subsequent
431             cached calls with the same parameters will return new content.
432              
433             =item *
434              
435             There is no "/xao/page/render_cache_skip" in the clipboard. This can be used to
436             skip cache altogether if it is known that pages rendered in this session
437             are different from cached and the cache does not want to be contaminated
438             with them.
439              
440             =back
441              
442             Properly used render cache can speed up pages significantly, but if
443             used incorrectly it can also introduce very hard to find issues in the
444             rendered content.
445              
446             Carefully consider what pages to tag with "cacheable" tag. Benchmarking
447             reports can be of great help for that.
448              
449             Entries in the config /xao/page/render_cache_allow may include additional
450             specifications for what parameters are checked when rendered content is
451             cached. By default, if the value is '1' or 'on' all of Page template
452             parameters are checked, but none of CGI or cookies. Values for
453             parameters 'path' and 'template' are always checked, regardless of the
454             configuration.
455              
456             The configuration can look like this:
457              
458             xao => {
459             page => {
460             render_cache_name => 'xao_page_render',
461             render_cache_allow => {
462             'p:/bits/complex-template' => 1,
463             'p:/bits/complex-cgi' => {
464             param => [ '*' ],
465             cgi => [ 'cf*' ],
466             },
467             'p:/bits/complex-cookie' => {
468             param => [ '*', '!session*' ],
469             cookie => [ 'session' ],
470             },
471             },
472             },
473             }
474              
475              
476             =head2 BENCHMARKING
477              
478             Benchmarking can be started and stopped by using benchmark_start()
479             and benchmark_stop() calls. The hash with current benchmarking data can
480             be retrieved with benchmark_stats() call.
481              
482             When benchmarking is started all rendered paths (and optionally all
483             templates) are timed and are also analyzed for potential cacheability --
484             if rendered content is repeatedly the same for some set of parameters.
485              
486             Custom execution paths spanning multiple templates can be tracked by
487             using benchmark_enter($tag) and benchmark_leave($tag) calls.
488              
489             The data is "static", not specific to a particular Page object.
490              
491             Benchmarking slows down processing. Do not use it in production.
492              
493             For an easy way to control benchmarking from templates use <%Benchmark%>
494             object.
495              
496              
497             =head2 NOTE FOR HARD-BOILED HACKERS
498              
499             If you do not like something in the parser behavior you can define
500             site-specific Page object and refine or replace any methods of system
501             Page object. Your new object would then be used by all system and
502             site-specific objects B and won't impact any other sites
503             installed on the same host. But this is mentioned here merely as a
504             theoretical possibility, not as a good thing to do.
505              
506             =head2 TEST OUTPUT
507              
508             The output of the test above would be:
509              
510             {}""
511             []
512             ""{}
513              
514             In fact first two SetArg's would add two empty lines in front because
515             they have carriage returns after them, but this is only significant if
516             your HTML code is space-sensitive.
517              
518             =head1 METHODS
519              
520             Publicly accessible methods of Page (and therefor of all objects derived
521             from Page unless overwritten) are:
522              
523             =over
524              
525             =cut
526              
527             ###############################################################################
528             package XAO::DO::Web::Page;
529 17     17   27593 use warnings;
  17         46  
  17         1378  
530 17     17   116 use strict;
  17         39  
  17         691  
531 17     17   8676 use utf8;
  17         5097  
  17         154  
532 17     17   13899 use Digest::SHA qw(sha1_hex);
  17         77124  
  17         2160  
533 17     17   161 use Encode;
  17         37  
  17         2244  
534 17     17   135 use Time::HiRes qw(gettimeofday tv_interval);
  17         40  
  17         450  
535 17     17   13866 use JSON qw(to_json);
  17         219802  
  17         148  
536 17     17   5102 use XAO::Cache;
  17         54  
  17         657  
537 17     17   161 use XAO::Objects;
  17         40  
  17         552  
538 17     17   95 use XAO::PageSupport;
  17         38  
  17         727  
539 17     17   297 use XAO::Projects qw(:all);
  17         38  
  17         3544  
540 17     17   154 use XAO::Templates;
  17         32  
  17         504  
541 17     17   115 use XAO::Utils;
  17         36  
  17         1459  
542 17     17   117 use Error qw(:try);
  17         40  
  17         185  
543              
544 17     17   3687 use base XAO::Objects->load(objname => 'Atom');
  17         175  
  17         185  
545              
546             # Prototypes
547             #
548             sub cache ($%);
549             sub cgi ($);
550             sub check_db ($);
551             sub dbh ($);
552             sub display ($%);
553             sub expand ($%);
554             sub finaltextout ($%);
555             sub object ($%);
556             sub odb ($);
557             sub parse ($%);
558             sub siteconfig ($);
559             sub textout ($%);
560             sub benchmark_enabled ($);
561             sub benchmark_enter ($$;$$$);
562             sub benchmark_leave ($$;$$);
563             sub benchmark_reset ($);
564             sub benchmark_start ($;$);
565             sub benchmark_stats ($;$);
566             sub benchmark_stop ($);
567             sub page_clipboard ($);
568              
569             sub _do_pass_args ($$$);
570              
571             ###############################################################################
572              
573             sub params_digest ($$;$) {
574 115     115 0 1260 my ($self,$args,$spec)=@_;
575              
576             # Dropping non-scalar values from params. They get in by calling
577             # ::Action::data_... methods for example, and in other scenarios
578             # too.
579             #
580 115 100       420 my $params={ map { ref $args->{$_} ? () : ($_ => $args->{$_}) } keys %$args };
  396         1376  
581              
582             # Template and path are always passed along
583             #
584 115         388 my $path=delete $params->{'path'};
585 115         212 my $template=delete $params->{'template'};
586              
587             # Checking what is considered important for the digest, getting a
588             # specification. It may come from outside in testing.
589             #
590 115 100       276 if(!$spec) {
591 101         181 $spec=$args->{'xao.cacheable'};
592             }
593              
594 115 100 66     764 if(!$spec && !defined $args->{'template'} && (my $path=$args->{'path'})) {
      66        
595 89         184 my $cache_allow=$self->{'cache_allow'};
596 89 50       181 if($cache_allow) {
597 89         256 $spec=$cache_allow->{'p:'.$path};
598             }
599             }
600              
601             # It may be a hash of instructions about what to keep and what to
602             # drop for the key:
603             #
604             # param => [ 'FOO*', '!FOO.BAR*' ],
605             # cgi => [ 'fn', 'fv' ],
606             # cookie => [ 'customer_id' ],
607             #
608             # Default is to ignore cookies and CGI and hash all scalar
609             # parameters.
610             #
611 115         333 my $cgis;
612             my $cookies;
613 115         0 my $protocol;
614 115 100 100     403 if($spec && ref($spec)) {
615 13         61 while(my ($spec_key,$spec_list)=each %$spec) {
616 31         62 my $hash;
617             my $target;
618              
619 31 100 66     172 if($spec_key eq 'param') {
    100 33        
    100          
    50          
620 13         20 $hash=$params;
621 13         27 $target=\$params;
622             }
623             elsif($spec_key eq 'cgi') {
624 8         34 my $cgi=$self->cgi;
625 8         218 $hash={ map { $_ => [ $cgi->param($_) ] } $cgi->param };
  16         638  
626 8         297 $target=\$cgis;
627             }
628             elsif($spec_key eq 'cookie' || $spec_key eq 'cookies') {
629 8         29 my $config=$self->siteconfig;
630 8         25 $hash={ map { $_ => $config->get_cookie($_,1) } $self->cgi->cookie() };
  14         5226  
631 8         27 $target=\$cookies;
632             }
633             elsif($spec_key eq 'proto' && $spec_list) {
634 2 50       10 $protocol=$self->is_secure ? 'https' : 'http';
635 2         71 next;
636             }
637             else {
638 0         0 throw $self "- unsupported source '$spec_key' for '$args->{'path'}'";
639             }
640              
641 29         139 $$target=$self->_do_pass_args($hash,$spec_list);
642             }
643             }
644              
645             # Converting to a canonical scalar and calculating a unique digest.
646             #
647 115         891 my $params_json=to_json([$path,$template,$params,$cgis,$cookies,$protocol],{ utf8 => 1, canonical => 1 });
648              
649 115         5184 my $params_digest=sha1_hex($params_json);
650              
651 115 100       786 return wantarray ? ($params_digest,$params_json) : $params_digest;
652             }
653              
654             ###############################################################################
655              
656             sub _do_display ($@) {
657 1141     1141   2114 my $self=shift;
658 1141         3592 my $cache_args=get_args(\@_);
659              
660             # We need to operate on this specific hash because it can get
661             # modified during template processing.
662             #
663 1141   33     9515 my $args=$self->{'args'} || throw $self "- no 'args' in self";
664              
665             # Preparing to benchmark if requested
666             #
667 1141         3107 my $benchmark=$self->benchmark_enabled();
668              
669             # We need to bookmark buffer position to analyze content data
670             # for cacheability later.
671             #
672 1141 100       43562 my $bookmark=$benchmark ? XAO::PageSupport::bookmark() : 0;
673              
674             # When called from a cache retrieve we have a cache_key parameter.
675             #
676 1141         2156 my $from_cache_retrieve=$cache_args->{'cache_key'};
677 1141 100       2484 if($from_cache_retrieve) {
678 4         10 XAO::PageSupport::push();
679              
680 4 50       9 if($self->debug_check('render-cache-add')) {
681 0         0 my ($args_digest,$args_json)=$self->params_digest($args);
682 0         0 dprint "RENDER_CACHE_ADD: $args_digest / $args_json";
683             }
684             }
685              
686             # Parsing template or getting already pre-parsed template when it is
687             # available.
688             #
689             # Also defining the tag for benchmarking. Normally it is only
690             # defined for paths, but can also be defined for templates.
691             #
692 1141         3730 my $benchmark_tag;
693             my $args_digest;
694 1141         0 my $args_json;
695 1141         0 my $parsed;
696 1141 100       2253 if($benchmark) {
697 447         1885 $parsed=$self->parse($args,{ cache_key_ref => \$benchmark_tag });
698              
699 447 100 66     2824 if($benchmark<2 && $benchmark_tag && substr($benchmark_tag,0,2) ne 'p:') {
      100        
700 208         331 $benchmark_tag=undef;
701             }
702             }
703             else {
704 694         2063 $parsed=$self->parse($args);
705             }
706              
707             # Starting the stopwatch if needed. We may not get a tag if this is
708             # an inner pre-parsed template.
709             #
710             # Calculating a 'run' key that uniquely identifies a specific set of
711             # parameters. Used for two purposes: identifying cacheable pages
712             # and benchmarking self-referencing recurrent templates.
713             #
714 1141 100       2944 if($benchmark_tag) {
715 89         267 ($args_digest,$args_json)=$self->params_digest($args);
716 89 100       335 $self->benchmark_enter($benchmark_tag,$args_digest,$args_json,$self->can_cache_render($args) ? 1 : 0);
717             }
718              
719             # Template processing itself. Pretty simple, huh? :)
720             #
721 1141         2551 foreach my $item (@$parsed) {
722 7054         15329 my $stop_after;
723             my $itemflag;
724 7054         0 my $text;
725              
726 7054 100       17713 if(exists $item->{'text'}) {
    100          
    50          
727 3355         6034 $text=$item->{'text'};
728             }
729              
730             elsif(exists $item->{'varname'}) {
731 2255         3591 my $varname=$item->{'varname'};
732 2255         3590 $text=$args->{$varname};
733 2255 50       3890 defined $text ||
734             throw $self "- undefined argument '$varname'";
735 2255         3722 $itemflag=$item->{'flag'};
736             }
737              
738             elsif(exists $item->{'objname'}) {
739 1444         2448 my $objname=$item->{'objname'};
740              
741 1444         2137 $itemflag=$item->{'flag'};
742              
743             # First we're trying to substitute from arguments for old
744             # style <%FUBAR%>
745             #
746 1444         2324 $text=$args->{$objname};
747              
748             # Executing object if not.
749             #
750 1444 100       2954 if(!defined $text) {
751 1388         3441 my $obj=$self->object(objname => $objname);
752              
753             # Preparing arguments. If argument includes object references -
754             # they are expanded first.
755             #
756 1388         134089 my %objargs;
757 1388         2697 my $ia=$item->{'args'};
758 1388         2184 my $args_copy;
759             my $page_obj;
760 1388         3695 foreach my $a (keys %$ia) {
761 2975         5014 my $v=$ia->{$a};
762 2975 100       5864 if(ref($v)) {
763 787 100 100     3428 if(@$v==1 && exists($v->[0]->{'text'})) {
764 487         1122 $v=$v->[0]->{'text'};
765             }
766             else {
767 300 100       735 if(!$args_copy) {
768 287         714 $args_copy=merge_refs($args);
769 287         3582 delete $args_copy->{'path'};
770             }
771 300 100       713 if(!$page_obj) {
772 287         762 $page_obj=$self->object(objname => 'Page');
773             }
774 300         21319 $args_copy->{'template'}=$v;
775 300         885 $v=$page_obj->expand($args_copy);
776             }
777             }
778              
779             # Decoding entities from arguments. Lt, gt, amp,
780             # quot and &#DEC; are supported.
781             #
782 2975         5480 $v=~s/</
783 2975         4655 $v=~s/>/>/sg;
784 2975         4225 $v=~s/"/"/sg;
785 2975         4321 $v=~s/&#(\d+);/chr($1)/sge;
  26         103  
786 2975         4214 $v=~s/&/&/sg;
787              
788 2975         6999 $objargs{$a}=$v;
789             }
790              
791             # Executing object. For speed optimisation we call object's
792             # display method directly if we're not going to do anything
793             # with the text anyway. This way we avoid push/pop and at
794             # least two extra memcpy's.
795             #
796 1388 100 66     6021 if($itemflag && $itemflag ne 't') {
797 160         553 $text=$obj->expand(\%objargs);
798             }
799             else {
800 1228         4269 $obj->display(\%objargs);
801             }
802              
803             # Indicator that we do not need to parse or display anything
804             # after that point.
805             #
806 1386         4960 $stop_after=$self->clipboard->get('_no_more_output');
807              
808             # Was it something like SetArg object? Merging changes in then.
809             #
810 1386 100       92426 if($self->{'merge_args'}) {
811 181         277 @{$args}{keys %{$self->{'merge_args'}}}=values %{$self->{'merge_args'}};
  181         458  
  181         13631  
  181         510  
812 181         1313 delete $self->{'merge_args'};
813             }
814             }
815             }
816              
817 7052 100       12635 if(defined $text) {
818              
819             # When the text is from an external argument like \xe9
820             # it might be stored in a platform encoding and not in
821             # Unicode. Upgrading it.
822             #
823 5826 100 100     10487 utf8::upgrade($text) if $self->_character_mode && !$item->{'binary'};
824              
825             # Safety conversion - q for query, h - for html, s - for
826             # nbsp'ced html, f - for tag fields, u - for URLs, t - for text
827             # as is (default).
828             #
829 5826 100 66     13552 if($itemflag && $itemflag ne 't') {
830 1637 100       4258 if($itemflag eq 'h') {
    50          
    100          
    100          
    100          
    50          
831 453         889 $text=XAO::Utils::t2ht($text);
832             }
833             elsif($itemflag eq 's') {
834 0 0 0     0 $text=(defined $text && length($text)) ? XAO::Utils::t2ht($text) : " ";
835             }
836             elsif($itemflag eq 'q') {
837 451         895 $text=XAO::Utils::t2hq($text);
838             }
839             elsif($itemflag eq 'f') {
840 731         1846 $text=XAO::Utils::t2hf($text);
841             }
842             elsif($itemflag eq 'u') {
843 1         11 $text=XAO::Utils::t2hq($text);
844             }
845             elsif($itemflag eq 'j') {
846 1         6 $text=XAO::Utils::t2hj($text);
847             }
848             else {
849 0         0 eprint "Unsupported translation flag '$itemflag', objname=",$item->{'objname'};
850             }
851             }
852              
853             # Sending out the text
854             #
855 5826         30321 $self->textout($text);
856             }
857              
858             # Checking if this object required to stop processing
859             #
860 7052 100       15229 last if $stop_after;
861             }
862              
863             # We need to return the actual rendered content if this is called
864             # from cache render.
865             #
866 1139         4156 my $content=undef;
867 1139 100       3072 if($from_cache_retrieve) {
    100          
868 4   33     12 $content=XAO::PageSupport::pop($self->_character_mode && !$args->{'unparsed'});
869             }
870             elsif($benchmark_tag) {
871 89   33     207 $content=XAO::PageSupport::peek($bookmark,$self->_character_mode && !$args->{'unparsed'});
872             }
873              
874             # When benchmarking we stop the timer and we also remember the
875             # content for cacheability analysis.
876             #
877 1139 100       2752 if($benchmark_tag) {
878 89         1003 my $content_digest=sha1_hex($content);
879 89         333 $self->benchmark_leave($benchmark_tag,$args_digest,$content_digest);
880             }
881              
882             # This will be an undef if the call is not from cache. That is fine.
883             #
884 1139         6461 return $content;
885             }
886              
887             ###############################################################################
888              
889             sub _character_mode ($) {
890 7997     7997   11657 my $self=shift;
891              
892 7997 100       24093 return $self->{'character_mode'} if exists $self->{'character_mode'};
893              
894 1042 100       3069 my $character_mode=$self->siteconfig->get('/xao/page/character_mode') ? 1 : 0;
895              
896 1042         77593 $self->{'character_mode'}=$character_mode;
897              
898 1042         3479 return $character_mode;
899             }
900              
901             ###############################################################################
902              
903             sub _render_cache ($) {
904 21     21   37 my $self=$_[0];
905              
906 21 100       95 return $self->{'render_cache_obj'} if exists $self->{'render_cache_obj'};
907              
908 3   100     11 my $cache_name=$self->siteconfig->get('/xao/page/render_cache_name') || '';
909              
910 3         170 my $cache_obj;
911              
912 3 100       12 if($cache_name) {
913 2         32 dprint "Using a cache '$cache_name' for rendered templates";
914              
915 2         22 $cache_obj=$self->cache(
916             name => $cache_name,
917             coords => [ 'cache_key' ],
918             retrieve => \&_do_display,
919             );
920             }
921              
922 3         461 $self->{'render_cache_obj'}=$cache_obj;
923              
924 3         12 return $cache_obj;
925             }
926              
927             ###############################################################################
928              
929             # In case of memcached this clears ALL caches, not just render!
930              
931             sub render_cache_clear ($) {
932 1     1 0 60 my $self=$_[0];
933              
934 1         12 my $cache=$self->_render_cache;
935              
936 1 50       8 $cache->drop_all if $cache;
937             }
938              
939             ###############################################################################
940              
941             sub can_cache_render ($$) {
942 1236     1236 0 2458 my ($self,$args)=@_;
943              
944 1236 50       3207 return 0 if $self->page_clipboard->{'render_cache_skip'};
945              
946 1236 100       3256 return 1 if $args->{'xao.cacheable'};
947              
948 1216   66     4301 my $path=!defined $args->{'template'} && $args->{'path'};
949              
950 1216 100       4058 return 0 unless $path;
951              
952 285         575 my $cache_key='p:' . $path;
953              
954 285         597 my $cache_allow=$self->{'cache_allow'};
955 285 100       735 if(!$cache_allow) {
956 132         390 $cache_allow=$self->siteconfig->get('/xao/page/render_cache_allow');
957 132 100       11796 if($cache_allow) {
958 121         537 $self->{'cache_allow'}=$cache_allow;
959             }
960             else {
961 11         63 $cache_allow=$self->{'cache_allow'}={ };
962 11         63 $self->siteconfig->put('/xao/page/render_cache_allow' => $cache_allow);
963             }
964             }
965              
966 285         1611 return $cache_allow->{$cache_key};
967             }
968              
969             ###############################################################################
970              
971             =item display (%)
972              
973             Displays given template to the current output buffer. The system uses
974             buffers to collect all text displayed by various objects in a rather
975             optimal way using XAO::PageSupport (see L)
976             module. In XAO::Web handler the global buffer is initialized and after all
977             displayable objects have worked their way it retrieves whatever was
978             accumulated in that buffer and displays it.
979              
980             This way you do not have to think about where your output goes as long
981             as you do not "print" anything by yourself - you should always call
982             either display() or textout() to print any piece of text.
983              
984             Display() accepts the following arguments:
985              
986             =over
987              
988             =item pass
989              
990             Passes arguments from calling context into the template.
991              
992             The syntax allows to map parent arguments into new names,
993             and/or to limit what is passed. Multiple semi-colon separated rules are
994             allowed. Rules are processed from left to right.
995              
996             NEWNAME=OLDNAME - pass the value of OLDNAME as NEWNAME
997             NEW*=OLD* - pass all old values starting with OLD as NEW*
998             VAR;VAR.* - pass VAR and VAR.* under their own names
999             *;!VAR* - pass everything except VAR*
1000              
1001             The default, when the value of 'pass' is 'on' or '1', is the same as
1002             passing '*' -- meaning that all parent arguments are passed literally
1003             under their own names.
1004              
1005             There are exceptions, that are never passed from parent arguments:
1006             'pass', 'objname', 'path', and 'template'.
1007              
1008             Arguments given to display() override those inherited from the caller
1009             using 'pass'.
1010              
1011             =item path => 'path/to/the/template'
1012              
1013             Gives Page a path to the template that should be processed and
1014             displayed.
1015              
1016             =item template => 'template text'
1017              
1018             Provides Page with the actual template text.
1019              
1020             =item unparsed => 1
1021              
1022             If set it does not parse template, just displays it literally.
1023              
1024             =back
1025              
1026             Any other argument given is passed into template unmodified as a
1027             variable. Remember that it is recommended to pass variables using
1028             all-capital names for better visual recognition.
1029              
1030             Example:
1031              
1032             $obj->display(path => "/bits/left-menu", ITEM => "main");
1033              
1034             For security reasons it is also recommended to put all sub-templates
1035             into /bits/ directory under templates tree or into "bits" subdirectory
1036             of some tree inside of templates (like /admin/bits/admin-menu). Such
1037             templates cannot be displayed from XAO::Web handler by passing their
1038             path in URL.
1039              
1040             =cut
1041              
1042             sub display ($%) {
1043 1147     1147 1 6287 my $self=shift;
1044 1147         3005 my $args=$self->{'args'}=get_args(\@_);
1045              
1046             # Merging parent's args in if requested.
1047             #
1048 1147 100       14476 if($args->{'pass'}) {
1049 28         154 $args=$self->{'args'}=$self->pass_args($args->{'pass'},$args);
1050             }
1051              
1052             # Is this page cacheable? There is a distinction between page not
1053             # being cached with '/xao/page/render_cache_skip' and page being flushed in
1054             # cache with '/xao/page/render_cache_update'.
1055             #
1056 1147 100       3699 if($self->can_cache_render($args)) {
1057 20 100       154 if(my $cache=$self->_render_cache()) {
1058              
1059             # The key depends on all arguments.
1060             #
1061 10         24 my ($cache_key,$params_json)=$self->params_digest($args);
1062              
1063 10 50       24 if($self->debug_check('render-cache-get')) {
1064 0         0 dprint "RENDER_CACHE_GET: $cache_key / $params_json";
1065             }
1066              
1067             # Building the content. Real arguments for displaying are in
1068             # $self->{'args'}.
1069             #
1070             my $content=$cache->get($self,{
1071             cache_key => $cache_key,
1072 10   33     23 force_update => ($self->page_clipboard->{'render_cache_update'} || $args->{'xao.uncached'}),
1073             });
1074              
1075 10         420 $self->textout($content);
1076              
1077 10         27 return;
1078             }
1079             }
1080              
1081             # We get here if the page cannot be cached
1082             #
1083 1137         3755 $self->_do_display();
1084             }
1085              
1086             ###############################################################################
1087              
1088             =item expand (%)
1089              
1090             Returns a string corresponding to the expanded template. Accepts exactly
1091             the same arguments as display(). Here is an example:
1092              
1093             my $str=$obj->expand(template => '<%Date%>');
1094              
1095             =cut
1096              
1097             sub expand ($%) {
1098 967     967 1 53513 my $self=shift;
1099 967         2917 my $args=get_args(\@_);
1100              
1101             # First it prepares a place in stack for new text (push) and after
1102             # display it calls pop to get back whatever was written. The sole
1103             # reason for all this is speed optimization - XAO::PageSupport is
1104             # implemented in C in quite optimal way.
1105             #
1106 967         13884 XAO::PageSupport::push();
1107              
1108             # Not using Error's try{} -- it is too slow. Benchmarking showed
1109             # about 7% slowdown.
1110             #
1111             ### my $args=get_args(\@_);
1112             ### try {
1113             ### $self->display($args);
1114             ### }
1115             ### otherwise {
1116             ### my $e=shift;
1117             ###
1118             ### # Popping out the potential output of the failed
1119             ### # template. Otherwise we are going to break the stack order.
1120             ### #
1121             ### XAO::PageSupport::pop();
1122             ###
1123             ### $e->throw();
1124             ### };
1125              
1126             # Eval is faster, almost indistinguishable from the bare call on
1127             # benchmark results.
1128             #
1129 967         2125 eval {
1130 967         3060 $self->display($args);
1131             };
1132              
1133 967 100       374492 if($@) {
1134 2         40 XAO::PageSupport::pop(0);
1135              
1136 2 50       26 if($@->can('throw')) {
1137 2         59 throw $@;
1138             }
1139             else {
1140 0         0 throw $self "- $@";
1141             }
1142             }
1143              
1144             # Text pages are converted into perl characters, otherwise returning
1145             # bytes.
1146             #
1147             my $chmode=$self->_character_mode &&
1148 965   100     2565 !$args->{'unparsed'} &&
1149             !$self->siteconfig->force_byte_output;
1150              
1151 965 100       6007 return XAO::PageSupport::pop($chmode ? 1 : 0);
1152             }
1153              
1154             ###############################################################################
1155              
1156             =item parse ($%)
1157              
1158             Takes template from either 'path' or 'template' and parses it. If given
1159             the following template:
1160              
1161             Text <%Object a=A b="B" c={X<%C/f ca={CA}%>} d='D' e={'<$E$>'}%>
1162              
1163             It will return a reference to an array of the following structure:
1164              
1165             [ { text => 'Text ',
1166             },
1167             { objname => 'Object',
1168             args => {
1169             a => [
1170             { text => 'A',
1171             },
1172             ],
1173             b => [
1174             { text => 'B',
1175             },
1176             ],
1177             c => [
1178             { text => 'X',
1179             },
1180             { objname => 'C',
1181             flag => 'f',
1182             args => {
1183             ca => [
1184             { text => 'CA',
1185             },
1186             ],
1187             },
1188             },
1189             ],
1190             d => 'D',
1191             e => '<$E$>',
1192             },
1193             },
1194             ]
1195              
1196             With "unparsed" parameter the content of the template is not analyzed
1197             and is returned as a single 'text' node.
1198              
1199             Templates are only parsed once, unless an "xao.uncached" parameter is
1200             set to true.
1201              
1202             Normally the parsed templates cache uses a local perl hash. If
1203             desired a XAO::Cache based implementation can be used by setting
1204             /xao/page/parse_cache_name parameter in the site configuration to the desired
1205             cache name (e.g. "xao_parse_cache").
1206              
1207             Statistics of various ways of calling:
1208              
1209             memcached-cache-path 1866/s
1210             memcached-cache-template 2407/s
1211             no-cache-path 5229/s
1212             no-cache-template 5572/s
1213             memory-cache-template 26699/s
1214             memory-cache-path 45253/s
1215             local-cache-template 49681/s
1216             local-cache-path 149806/s
1217              
1218             Unless the site has a huge number of templates there is really no
1219             compelling reason to use anything but the default local cache. The
1220             performance of memcached is worse than no caching at all for example.
1221              
1222             The method always returns with a correct array or throws an error.
1223              
1224             =cut
1225              
1226             sub parse_retrieve ($@);
1227              
1228             my %parsed_cache;
1229              
1230             sub parse ($%) {
1231 1141     1141 1 1884 my $self=shift;
1232 1141         2992 my $args=get_args(\@_);
1233              
1234 1141         17916 my $unparsed=$args->{'unparsed'};
1235              
1236 1141         2125 my $uncached=$args->{'xao.uncached'};
1237              
1238             # Preparing a short key that uniquely identifies the template given
1239             # (by either a path or an inline text). Uniqueness is only needed
1240             # within the site context. Global scope uniqueness is dealt with by
1241             # cache implementations below.
1242             #
1243 1141         2280 my $path;
1244             my $cache_key;
1245 1141 100       2583 if(defined($args->{'template'})) {
1246 931         1835 my $template=$args->{'template'};
1247              
1248 931 100       2482 if(ref($template)) {
1249 300         1148 return $template; # Pre-parsed as an argument of some upper class
1250             }
1251              
1252 631 100       2560 my $tbytes=Encode::is_utf8($template) ? Encode::encode_utf8($template) : $template;
1253              
1254 631 100       1522 if(length $tbytes < 80) {
1255 576 100       1720 $cache_key=($unparsed ? 'T' : 't').':'.$tbytes;
1256             }
1257             else {
1258 55 50       598 $cache_key=($unparsed ? 'H' : 'h').':'.sha1_hex($tbytes);
1259             }
1260             }
1261             else {
1262 210   33     730 $path=$args->{'path'} ||
1263             throw $self "- no 'path' and no 'template' given to a Page object";
1264              
1265 210 100       697 $cache_key=($unparsed ? 'P' : 'p').':'.$path;
1266             }
1267              
1268             # Remembering the key if needed. It is used for benchmark cache.
1269             #
1270 841         1655 my $cache_key_ref=$args->{'cache_key_ref'};
1271 841 100       2006 $$cache_key_ref=$cache_key if $cache_key_ref;
1272              
1273             # Encoding also matters
1274             #
1275 841         2553 $cache_key.=':'.$self->_character_mode;
1276              
1277             # With uncached we don't even try to use any caches.
1278             #
1279 841         1597 my $parsed;
1280 841 50       1681 if($uncached) {
1281 0         0 $parsed=$self->parse_retrieve($args);
1282             }
1283              
1284             # Caching either locally, or in a standard cache
1285             #
1286             else {
1287              
1288             # Setup, only executed once.
1289             #
1290 841         1556 my $cache_name=$self->{'parse_cache_name'};
1291 841 100       1976 if(!defined $cache_name) {
1292 594   50     1523 $cache_name=$self->{'parse_cache_name'}=$self->siteconfig->get('/xao/page/parse_cache_name') || '';
1293             }
1294              
1295             # A fast totally local implementation.
1296             #
1297             # About two times faster than a memcached, but grows a template
1298             # cache per-process.
1299             #
1300 841 50       30698 if(!$cache_name) {
1301              
1302             # Making it unique per site
1303             #
1304 841   50     3882 my $sitename=$self->{'sitename'} || get_current_project_name() || '';
1305 841         5127 $cache_key=$sitename . ':' . $cache_key;
1306              
1307             # Checking if we have parsed and cached this before
1308             #
1309 841         1902 $parsed=$parsed_cache{$cache_key};
1310              
1311 841 100       3489 return $parsed if defined $parsed;
1312              
1313             # Reading and parsing.
1314             #
1315 281         826 $parsed=$self->parse_retrieve($args);
1316              
1317             # Caching the parsed template.
1318             #
1319 281         1099 $parsed_cache{$cache_key}=$parsed;
1320              
1321             # Logging the size
1322             #
1323 281 50       686 if($self->debug_check('page-cache-size')) {
1324 0         0 $self->cache_show_size($cache_key);
1325             }
1326             }
1327              
1328             # More generic implementation that can be switched from local to
1329             # memcached to anything else
1330             #
1331             else {
1332 0         0 my $cache=$self->{'parse_cache_obj'};
1333 0 0       0 if(!$cache) {
1334 0         0 dprint "Using a named cache '$cache_name' for parsed templates";
1335              
1336 0         0 $cache=$self->{'parse_cache_obj'}=$self->siteconfig->cache(
1337             name => $cache_name,
1338             coords => [ 'cache_key' ],
1339             retrieve => \&parse_retrieve,
1340             );
1341             }
1342              
1343 0         0 $parsed=$cache->get($self,$args,{
1344             cache_key => $cache_key,
1345             force_update => $uncached,
1346             });
1347             }
1348             }
1349              
1350 281         879 return $parsed;
1351             }
1352              
1353             ###############################################################################
1354              
1355             sub parse_retrieve ($@) {
1356 281     281 0 486 my $self=shift;
1357 281         922 my $args=get_args(\@_);
1358              
1359 281         2993 my $path=$args->{'path'};
1360 281         586 my $template=$args->{'template'};
1361              
1362             # Reading and parsing.
1363             #
1364 281 100 66     924 if($path && !defined $template) {
1365 38 50       187 if($self->debug_check('show-read')) {
1366 0         0 dprint $self->objname."- read path='$path'";
1367             }
1368              
1369 38         266 $template=XAO::Templates::get(path => $path);
1370              
1371 38 50       135 defined($template) ||
1372             throw $self "- no template found (path=$path)";
1373             }
1374              
1375             # An unparsed template is very simple. But it might include binary
1376             # data. We don't encode/decode it regardless of encoding settings.
1377             #
1378 281 100       703 if($args->{'unparsed'}) {
1379 9         94 return [ { text => $template, binary => 1 } ];
1380             }
1381              
1382             # Logging the template or path if requested.
1383             #
1384 272 50       881 if($self->debug_check('show-parse')) {
1385 0 0       0 if($path) {
1386 0         0 dprint $self->objname."- parsing path='$path'"
1387             }
1388             else {
1389 0         0 my $te=substr($template,0,20);
1390 0         0 $te=~s/\r/\\r/sg;
1391 0         0 $te=~s/\n/\\n/sg;
1392 0         0 $te=~s/\t/\\t/sg;
1393 0 0       0 $te.='...' if length($template)>20;
1394 0         0 dprint $self->objname."- parsing template='$te'";
1395             }
1396             }
1397              
1398             # Parsing.
1399             #
1400 272         526 my $parsed;
1401 272 100       732 if($self->_character_mode) {
1402              
1403             # We might get a latin1 string like \xe9 that is meant to
1404             # be a Unicode, but is not. Unless all code is switched to
1405             # use 'unicode_strings' feature this can easily happen.
1406             #
1407             # BUT! We can also get an already UTF-8 encoded byte string,
1408             # in which case upgrade would break it.
1409             #
1410             # Using shameful black magic :(
1411             #
1412 49 100       147 if(!Encode::is_utf8($template)) {
1413 30         83 Encode::_utf8_on($template);
1414              
1415             # UTF-8 encoded bytes or plain ASCII
1416             #
1417 30 50       96 if(Encode::is_utf8($template,1)) {
1418             # No-op
1419             }
1420             else {
1421 0         0 Encode::_utf8_off($template);
1422 0         0 utf8::upgrade($template);
1423             }
1424             }
1425              
1426 49         504 $parsed=XAO::PageSupport::parse($template,1);
1427             }
1428             else {
1429 223         3585 $parsed=XAO::PageSupport::parse($template,0);
1430             }
1431              
1432             # If a scalar is returned it is an indicator of an error.
1433             #
1434 272 50       807 ref $parsed ||
1435             throw $self "- $parsed";
1436              
1437 272         892 return $parsed;
1438             }
1439              
1440             ###############################################################################
1441              
1442             =item object (%)
1443              
1444             Creates a new displayable object correctly tied to the current one. You
1445             should always get a reference to a displayable object by calling this
1446             method, not by using XAO::Objects' new() method. Currently most
1447             of the objects would work fine even if you do not, but this is not
1448             guaranteed.
1449              
1450             Possible arguments are (the same as for XAO::Objects' new method):
1451              
1452             =over
1453              
1454             =item objname => 'ObjectName'
1455              
1456             The name of an object you want to have an instance of. Default is
1457             'Page'. All objects are assumed to be in XAO::DO::Web namespace,
1458             prepending them with 'Web::' is optional.
1459              
1460             =item baseobj => 1
1461              
1462             If present then site specific object is ignored and system object is
1463             loaded.
1464              
1465             =back
1466              
1467             Example of getting Page object:
1468              
1469             sub display ($%) {
1470             my $self=shift;
1471             my $obj=$self->object;
1472             $obj->display(template => '<%Date%>');
1473             }
1474              
1475             Or even:
1476              
1477             $self->object->display(template => '<%Date%>');
1478              
1479             Getting FilloutForm object:
1480              
1481             sub display ($%) {
1482             my $self=shift;
1483             my $ff=$self->object(objname => 'FilloutForm');
1484             $ff->setup(...);
1485             ...
1486             }
1487              
1488             Object() method always returns object reference or throws an exception
1489             - meaning that under normal circumstances you do not need to worry
1490             about returned object correctness. If you get past the call to object()
1491             method then you have valid object reference on hands.
1492              
1493             =cut
1494              
1495             sub object ($%) {
1496 1964     1964 1 3189 my $self=shift;
1497 1964         5151 my $args=get_args(@_);
1498              
1499 1964   100     26550 my $objname=$args->{objname} || 'Page';
1500 1964 100       6437 $objname='Web::' . $objname unless substr($objname,0,5) eq 'Web::';
1501              
1502 1964         7781 XAO::Objects->new(
1503             objname => $objname,
1504             parent => $self,
1505             );
1506             }
1507              
1508             ###############################################################################
1509              
1510             =item textout ($)
1511              
1512             Displays a piece of text literally, without any changes.
1513              
1514             It used to be called as textout(text => "text") which is still
1515             supported for compatibility, but is not recommended any more. Call it
1516             with single argument -- text to be displayed.
1517              
1518             Example:
1519              
1520             $obj->textout("Text to be displayed");
1521              
1522             This method is the only place where text is actually gets displayed. You
1523             can override it if you really need some other output strategy for you
1524             object. Although it is not recommended to do so.
1525              
1526             =cut
1527              
1528             sub textout ($%) {
1529 6616     6616 1 27025 my $self=shift;
1530              
1531 6616 50       11939 return unless @_;
1532              
1533 6616         8023 my $text;
1534 6616 50       10676 if(@_ == 1) {
1535 6616         9302 $text=$_[0];
1536             }
1537             else {
1538 0         0 my %args=@_;
1539 0   0     0 $text=$args{'text'} // '';
1540             }
1541              
1542 6616 100       20193 if(Encode::is_utf8($text)) {
1543 74         358 XAO::PageSupport::addtext(Encode::encode_utf8($text));
1544             }
1545             else {
1546 6542         27328 XAO::PageSupport::addtext($text);
1547             }
1548             }
1549              
1550             ###############################################################################
1551              
1552             =item finaltextout ($)
1553              
1554             Displays some text and stops processing templates on all levels. No more
1555             objects should be called in this session and no more text should be
1556             printed.
1557              
1558             Used in Redirect object to break execution immediately for example.
1559              
1560             Accepts the same arguments as textout() method.
1561              
1562             =cut
1563              
1564             sub finaltextout ($%) {
1565 8     8 1 12 my $self=shift;
1566 8         21 $self->textout(@_);
1567 8         17 $self->clipboard->put(_no_more_output => 1);
1568             }
1569              
1570             ###############################################################################
1571              
1572             =item dbh ()
1573              
1574             Returns current database handler or throws an error if it is not
1575             available.
1576              
1577             Example:
1578              
1579             sub display ($%)
1580             my $self=shift;
1581             my $dbh=$self->dbh;
1582              
1583             # if you got this far - you have valid DB handler on hands
1584             }
1585              
1586             =cut
1587              
1588             sub dbh ($) {
1589 0     0 1 0 my $self=shift;
1590 0 0       0 return $self->{dbh} if $self->{'dbh'};
1591 0         0 $self->{dbh}=$self->siteconfig->dbh;
1592 0 0       0 return $self->{dbh} if $self->{dbh};
1593 0         0 throw $self "- no database connection";
1594             }
1595              
1596             ###############################################################################
1597              
1598             =item odb ()
1599              
1600             Returns current object database handler or throws an error if it is not
1601             available.
1602              
1603             Example:
1604              
1605             sub display ($%) {
1606             my $self=shift;
1607             my $odb=$self->odb;
1608              
1609             # ... if you got this far - you have valid DB handler on hands
1610             }
1611              
1612             =cut
1613              
1614             sub odb ($) {
1615 0     0 1 0 my $self=shift;
1616 0 0       0 return $self->{odb} if $self->{odb};
1617              
1618 0         0 $self->{odb}=$self->siteconfig->odb;
1619 0 0       0 return $self->{odb} if $self->{odb};
1620              
1621 0         0 throw $self "- requires object database connection";
1622             }
1623              
1624             ###############################################################################
1625              
1626             =item cache (%)
1627              
1628             A shortcut that actually calls $self->siteconfig->cache. See the
1629             description of cache() in L for more details.
1630              
1631             =cut
1632              
1633             sub cache ($%) {
1634 4     4 1 7135 my $self=shift;
1635 4         15 my $args=get_args(\@_);
1636 4         79 return $self->siteconfig->cache($args);
1637             }
1638              
1639             ###############################################################################
1640              
1641             =item cgi ()
1642              
1643             Returns CGI object reference (see L) or throws an error if it is
1644             not available.
1645              
1646             =cut
1647              
1648             sub cgi ($) {
1649 142     142 1 739 my $self=shift;
1650 142         369 $self->siteconfig->cgi;
1651             }
1652              
1653             ###############################################################################
1654              
1655             =item clipboard ()
1656              
1657             Returns clipboard object, which inherets XAO::SimpleHash methods. Use
1658             this object to pass data between various objects that work together to
1659             produce a page. Clipboard is cleaned before starting every new session.
1660              
1661             =cut
1662              
1663             sub clipboard ($) {
1664 3985     3985 1 6807 my $self=shift;
1665 3985         6504 my $clipboard=$self->{'clipboard'};
1666 3985 100       8688 if(!$clipboard) {
1667 1004         2449 $clipboard=$self->{'clipboard'}=$self->siteconfig->clipboard;
1668             }
1669 3985         13406 return $clipboard;
1670             }
1671              
1672             ###############################################################################
1673              
1674             =item siteconfig ()
1675              
1676             Returns site configuration reference. Be careful with your changes to
1677             configuration, try not to change configuration -- use clipboard to pass
1678             data between objects. See L for more details.
1679              
1680             =cut
1681              
1682             sub siteconfig ($) {
1683 3412     3412 1 6482 my $self=shift;
1684 3412         5750 my $siteconfig=$self->{'siteconfig'};
1685 3412 100       7519 if(!$siteconfig) {
1686             $siteconfig=$self->{'siteconfig'}=
1687 1296 50       5025 $self->{'sitename'} ? get_project($self->{'sitename'})
1688             : get_current_project();
1689             }
1690 3412         115094 return $siteconfig;
1691             }
1692              
1693             ###############################################################################
1694              
1695             =item base_url (%)
1696              
1697             Returns base_url for secure or normal connection. Depends on parameter
1698             "secure" if it is set, or current state if it is not.
1699              
1700             If 'active' parameter is set then will return active URL, not the base
1701             one. In most practical cases active URL is the same as base URL except
1702             when your server is set up to answer for many domains. Base will stay
1703             at what is set in the site configuration and active will be the one
1704             taken from the Host: header.
1705              
1706             Examples:
1707              
1708             # Returns secure url in secure mode and normal
1709             # url in normal mode.
1710             #
1711             my $url=$self->base_url;
1712              
1713             # Return secure url no matter what
1714             #
1715             my $url=$self->base_url(secure => 1);
1716              
1717             # Return normal url no matter what
1718             #
1719             my $url=$self->base_url(secure => 0);
1720              
1721             # Return secure equivalent of the current active URL
1722             #
1723             my $url=$self->base_url(secure => 1, active => 1);
1724              
1725             =cut
1726              
1727             sub base_url ($;%) {
1728 48     48 1 114 my $self=shift;
1729 48         240 my $args=get_args(\@_);
1730              
1731 48         752 my $secure=$args->{secure};
1732 48 50       127 $secure=$self->is_secure unless defined $secure;
1733              
1734 48         100 my $active=$args->{active};
1735              
1736 48         76 my $url;
1737 48 100       112 if($secure) {
1738 26 100       95 $url=$active ? $self->clipboard->get('active_url_secure')
1739             : $self->siteconfig->get('base_url_secure');
1740             } else {
1741 22 100       117 $url=$active ? $self->clipboard->get('active_url')
1742             : $self->siteconfig->get('base_url');
1743             }
1744              
1745 48         2711 return $url;
1746             }
1747              
1748             ###############################################################################
1749              
1750             =item is_secure ()
1751              
1752             Returns 1 if the current the current connection is a secure one or
1753             0 otherwise. If there is a defined cgi() value then the result is
1754             defined by its https() method; otherwise the default is taken from
1755             /xao/page/default_https configuration variable. The later is useful for
1756             scripts that don't have a CGI environment.
1757              
1758             =cut
1759              
1760             sub is_secure ($) {
1761 31     31 1 61 my $self=shift;
1762 31         156 my $cgi=$self->cgi;
1763 31 50       91 if($cgi) {
1764 31 100       249 return $cgi->https() ? 1 : 0;
1765             }
1766             else {
1767 0 0       0 return $self->siteconfig->get('/xao/page/default_https') ? 1 : 0;
1768             }
1769             }
1770              
1771             ###############################################################################
1772              
1773             =item pageurl (%)
1774              
1775             Returns full URL of current page without parameters. Accepts the same
1776             arguments as base_url() method.
1777              
1778             =cut
1779              
1780             sub pageurl ($;%) {
1781 33     33 1 63 my $self=shift;
1782              
1783 33   33     87 my $pagedesc=$self->clipboard->get('pagedesc') ||
1784             throw $self "- no Web context, needs clipboard->'pagedesc'";
1785              
1786 33         1765 my $url=$self->base_url(@_);
1787              
1788             # This works in both CGI and PSGI environments, but simply
1789             # requesting $cgi->url(-absolute => 1) does not work for PSGI
1790             # because it sets PATH_INFO and REQUEST_URI to the same value,
1791             # making them cancel each other.
1792             #
1793 33         112 my $uri=$self->cgi->request_uri();
1794 33         411 $uri =~ s/\?.*$//s;
1795 33         127 $uri = $self->cgi->unescape($uri);
1796              
1797 33         819 return $url.$uri;
1798             }
1799              
1800             ###############################################################################
1801              
1802             sub _do_pass_args ($$$) {
1803 179     179   391 my ($self,$pargs,$spec)=@_;
1804              
1805 179         355 my $hash={ };
1806              
1807 179         360 foreach my $rule (@$spec) {
1808 212         2057 $rule=~s/^\s*(.*?)\s*$/$1/;
1809              
1810             ### dprint "...rule='$rule'";
1811              
1812 212 100       991 if($rule eq '*') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
1813 163         444 $hash=merge_refs($pargs,$hash);
1814             }
1815             elsif($rule =~ /^([\w\.]+)\s*=\s*([\w\.]+)$/) { # VAR=FOO
1816 8         54 $hash->{$1}=$pargs->{$2};
1817             }
1818             elsif($rule =~ /^([\w\.]*)\*([\w\.]*)\s*=\s*([\w\.]*)\*([\w\.]*)$/) {# VAR*=FOO* or *VAR=*FOO or V*R=T*Z or *=X*Z
1819 9         85 my ($prnew,$sufnew,$prold,$sufold)=($1,$2,$3,$4);
1820 9         411 my $re=qr/^\Q$prold\E(.*)\Q$sufold\E$/;
1821 9         59 foreach my $k (keys %$pargs) {
1822 63 100       343 next unless $k =~ $re;
1823 20         111 $hash->{$prnew.$1.$sufnew}=$pargs->{$k};
1824             }
1825             }
1826             elsif($rule =~ /^([\w\.]+)$/) { # VAR
1827 6         26 $hash->{$1}=$pargs->{$1};
1828             }
1829             elsif($rule =~ /^([\w\.]*)\*([\w\.]*)$/) { # VAR* or *VAR or VAR*FOO
1830 2         16 my ($pr,$suf)=($1,$2);
1831 2         49 my $re=qr/^\Q$pr\E(.*)\Q$suf\E$/;
1832 2         41 foreach my $k (keys %$pargs) {
1833 4 100       29 next unless $k =~ $re;
1834 2         11 $hash->{$k}=$pargs->{$k};
1835             }
1836             }
1837             elsif($rule =~ /^!([\w\.]+)$/) { # !VAR
1838 10         57 delete $hash->{$1};
1839             }
1840             elsif($rule =~ /^!([\w\.]*)\*([\w\.]*)$/) { # !VAR* or !*VAR or !VAR*FOO
1841 13         50 my ($pr,$suf)=($1,$2);
1842 13         333 my $re=qr/^\Q$pr\E(.*)\Q$suf\E$/;
1843 13         34 my @todel;
1844 13         45 foreach my $k (keys %$hash) {
1845 38 100       236 next unless $k =~ $re;
1846 18         42 push(@todel,$k);
1847             }
1848 13         30 delete @{$hash}{@todel};
  13         58  
1849             }
1850             elsif($rule eq '!*') {
1851 0         0 $hash={};
1852             }
1853             elsif($rule eq '') {
1854             # no-op
1855             }
1856             else {
1857 0         0 throw $self "- don't know how to pass for '$rule'";
1858             }
1859             }
1860              
1861 179         2430 return $hash;
1862             }
1863              
1864             ###############################################################################
1865              
1866             =item pass_args ($) {
1867              
1868             Helper method for supporting "pass" argument in web objects. Synopsis:
1869              
1870             $page->display($page->pass_args($args->{'pass'},$args),{
1871             path => $args->{'blah.path'},
1872             template => $args->{'blah.template'},
1873             FOO => 'bar',
1874             });
1875              
1876             If "pass" argument is not defined it will just return the original args,
1877             otherwise the following rules are supported:
1878              
1879             "on" or "1" - pass all arguments from parent object
1880             "VAR=FOO" - pass FOO from parent as VAR
1881             "VAR*=FOO*" - pass FOO* from parent renaming as VAR*
1882             "*=FOO*" - pass FOO* from parent stripping FOO
1883             "VAR" - pass only VAR from parent
1884             "VAR*" - pass only VAR* from parent
1885              
1886             Multiple pass specifications can be given with semi-colon delimiter.
1887              
1888             Several special tags are deleted from parent arguments: pass, path,
1889             template, and objname.
1890              
1891             =cut
1892              
1893             sub pass_args ($$;$) {
1894 177     177 1 562 my ($self,$pass,$args)=@_;
1895              
1896 177   100     825 $args||={ };
1897              
1898             # The first argument is the content of 'pass', if it's not defined
1899             # we return unadulteraded arguments.
1900             #
1901 177 100       484 return $args unless $pass;
1902              
1903             # If we don't have parent arguments then there is nothing to do.
1904             #
1905 150         239 my $pargs;
1906 150 50 33     749 if(!$self->{'parent'} || !($pargs=$self->{'parent'}->{'args'})) {
1907 0         0 return $args;
1908             }
1909              
1910             # Simplified (old) way of calling with just <%Page pass
1911             # template='xxx'%> would result in pass being 'on'.
1912             #
1913 150 100 66     444 if($pass eq 'on' || $pass eq '1') {
1914 135         236 $pass='*';
1915             }
1916              
1917             # Building inherited hash.
1918             #
1919 150         830 my $hash=$self->_do_pass_args($pargs,[split(/;/,$pass)]);
1920              
1921             # Always deleting pass, path and template
1922             #
1923 150         368 delete @{$hash}{'pass','objname','path','template'};
  150         504  
1924              
1925             # This is it, merging with the arguments given to us and returning
1926             #
1927 150         431 return merge_refs($hash,$args);
1928             }
1929              
1930             ###############################################################################
1931              
1932             sub benchmark_enabled ($) {
1933 1144     1144 0 1802 my $self=shift;
1934 1144 100       3489 $self->clipboard->get('_page_benchmark_enabled') || 0;
1935             }
1936              
1937             ###############################################################################
1938              
1939             sub _benchmark_hash ($) {
1940 195     195   371 my $self=shift;
1941              
1942 195         346 my $stats=$self->{'benchmark_stats'};
1943              
1944 195 100       384 if(!$stats) {
1945 18         65 $stats=$self->siteconfig->get('_page_benchmark_stats');
1946 18 100       940 if($stats) {
1947 17         58 $self->{'benchmark_stats'}=$stats;
1948             }
1949             else {
1950 1         16 $stats=$self->{'benchmark_stats'}={ };
1951 1         5 $self->siteconfig->put('_page_benchmark_stats' => $stats);
1952             }
1953             }
1954              
1955 195         428 return $stats;
1956             }
1957              
1958             ###############################################################################
1959              
1960             sub benchmark_tag_data ($$) {
1961 180     180 0 404 my ($self,$tag,$key)=@_;
1962              
1963 180 50       491 $tag || throw $self "- no 'tag'";
1964              
1965 180   100     394 $key||='-';
1966              
1967 180 50       389 ref $tag && throw $self "- tag '$tag' is not a scalar";
1968              
1969 180         537 my $stats=$self->_benchmark_hash();
1970              
1971 180         449 my $tagdata=$stats->{$tag};
1972              
1973 180 100       380 if(!$tagdata) {
1974 6         44 $tagdata=$stats->{$tag}={
1975             count => 0,
1976             total => 0,
1977             last => [ ],
1978             runs => { },
1979             };
1980             }
1981              
1982 180         372 my $rundata=$tagdata->{'runs'};
1983 180   100     592 $rundata->{$key}||={ };
1984 180         400 $rundata=$rundata->{$key};
1985              
1986 180 50       689 return wantarray ? ($tagdata,$rundata,$key) : $tagdata;
1987             }
1988              
1989             ###############################################################################
1990              
1991             =item benchmark_enter($;$$$)
1992              
1993             Start tracking the given tag execution time until benchmark_leave() is
1994             called on the same tag.
1995              
1996             An optional second argument can contain a unique key that identifies a
1997             specific run for the tag (in case of recurrent tag execution). The third
1998             optional argument is a description of this run.
1999              
2000             =cut
2001              
2002             sub benchmark_enter ($$;$$$) {
2003 90     90 1 345 my ($self,$tag,$key,$description,$cache_flag)=@_;
2004              
2005 90         161 my ($tagdata,$rundata);
2006 90         222 ($tagdata,$rundata,$key)=$self->benchmark_tag_data($tag,$key);
2007              
2008 90 50       294 if($rundata->{'started'}) {
2009 0         0 eprint "Benchmark for '$tag' (key '$key') not finished, discarding";
2010             }
2011              
2012 90   100     184 $description||='';
2013 90 100       319 $rundata->{'description'}=length $description > 100 ? substr($description,0,100) : $description;
2014              
2015 90 100       279 $rundata->{'cache_flag'}=$cache_flag ? 1 : 0;
2016              
2017 90         428 $rundata->{'started'}=[ gettimeofday ];
2018             }
2019              
2020             ###############################################################################
2021              
2022             =item benchmark_leave ($)
2023              
2024             Stop time tracking for the given tag and record tracking results in the
2025             history.
2026              
2027             =cut
2028              
2029             sub benchmark_leave ($$;$$) {
2030 90     90 1 325 my ($self,$tag,$key,$content_digest)=@_;
2031              
2032 90         149 my ($tagdata,$rundata);
2033 90         289 ($tagdata,$rundata,$key)=$self->benchmark_tag_data($tag,$key);
2034              
2035             ### dprint to_json($tagdata);
2036              
2037 90         252 my $started=$rundata->{'started'};
2038 90 50       191 if(!$started) {
2039 0         0 eprint "Benchmark for '$tag' (key '$key') was not started";
2040 0         0 return;
2041             }
2042              
2043 90         507 my $taken=tv_interval($started);
2044              
2045             # For median calculation
2046             #
2047 90         1751 my $last=$tagdata->{'last'};
2048 90         254 push(@$last,$taken);
2049 90 50       227 shift(@$last) if scalar(@$last) > 50;
2050              
2051 90         187 ++$tagdata->{'count'};
2052 90         176 ++$rundata->{'count'};
2053              
2054 90         183 $tagdata->{'total'}+=$taken;
2055 90         217 $rundata->{'total'}+=$taken;
2056              
2057             # Remembering the content for cacheability analysis.
2058             #
2059 90   100     229 $content_digest||='-';
2060 90         313 ++$rundata->{'content'}->{$content_digest};
2061              
2062             # Resetting for the next run
2063             #
2064 90         304 $rundata->{'started'}=undef;
2065             }
2066              
2067             ###############################################################################
2068              
2069             =item benchmark_reset()
2070              
2071             Clear all benchmarking statistics accumulated so far.
2072              
2073             =cut
2074              
2075             sub benchmark_reset ($) {
2076 0     0 1 0 my $self=shift;
2077 0         0 %{$self->_benchmark_hash()}=();
  0         0  
2078             }
2079              
2080             ###############################################################################
2081              
2082             =item benchmark_start(;$)
2083              
2084             Start automatic system-wide page rendering benchmarking.
2085              
2086             By default only 'path' based rendering is benchmarked. If an optional
2087             single argument is set to '2' then templates are also benchmarked (this
2088             may demand a lot of extra memory!).
2089              
2090             =cut
2091              
2092             sub benchmark_start ($;$) {
2093 2     2 1 5 my ($self,$level)=@_;
2094 2   50     10 $self->clipboard->put('_page_benchmark_enabled' => ($level || 1));
2095             }
2096              
2097             ###############################################################################
2098              
2099             =item benchmark_stop()
2100              
2101             Stop automatic system-wide rendering benchmarking.
2102              
2103             =cut
2104              
2105             sub benchmark_stop ($) {
2106 1     1 1 2 my $self=shift;
2107 1         5 $self->clipboard->put('_page_benchmark_enabled' => 0);
2108             }
2109              
2110             ###############################################################################
2111              
2112             =item benchmark_stats
2113              
2114             Return a hash with accumulated benchmark statistics.
2115              
2116             =cut
2117              
2118             sub benchmark_stats ($;$) {
2119 15     15 1 2200 my ($self,$desired_tag)=@_;
2120              
2121 15         61 my $stats=$self->_benchmark_hash();
2122              
2123 15         27 my %analyzed;
2124              
2125 15         72 foreach my $tag (keys %$stats) {
2126 79         188 my $d=$stats->{$tag};
2127 79 50       175 next unless $d->{'count'};
2128 79 100 100     227 next if $desired_tag && $tag ne $desired_tag;
2129              
2130 55         133 $d->{'average'}=$d->{'total'} / $d->{'count'};
2131 55         92 $d->{'median'}=$d->{'last'}->[scalar(@{$d->{'last'}})/2];
  55         161  
2132              
2133             # The page is cacheable if the content only depends on
2134             # parameters and not on clipboard, cookies, CGI, time, or other
2135             # environment.
2136             #
2137             $d->{'cacheable'}=scalar(grep {
2138 248         366 scalar(keys %{$d->{'runs'}->{$_}->{'content'}}) != 1
  248         810  
2139 55 100       82 } keys %{$d->{'runs'}}) ? 0 : 1;
  55         205  
2140              
2141             # Current cacheable flag, if it's shared across all runs
2142             #
2143             $d->{'cache_flag'}=scalar(grep {
2144 248         558 ! $d->{'runs'}->{$_}->{'cache_flag'}
2145 55 100       97 } keys %{$d->{'runs'}}) ? 0 : 1;
  55         130  
2146              
2147 55         172 $analyzed{$tag}=$d;
2148             }
2149              
2150             ### dprint to_json(\%analyzed,{ utf8 => 1, canonical => 1, pretty => 1 });
2151              
2152 15         101 return \%analyzed;
2153             }
2154              
2155             ###############################################################################
2156              
2157             sub cache_show_size ($$) {
2158 0     0 0 0 my ($self,$path)=@_;
2159              
2160 0         0 eval {
2161 0         0 require Devel::Size;
2162             };
2163              
2164 0 0       0 if($@) {
2165 0         0 eprint "Devel::Size not available, disabling debug 'page-cache-size'";
2166 0         0 $self->debug_set('cache-size' => 0);
2167 0         0 return;
2168             }
2169              
2170 0         0 my $size=Devel::Size::total_size(\%parsed_cache);
2171              
2172 0         0 eprint "Web::Page cache size ".sprintf('%.3f',$size/1024.0)." KB - ",$path;
2173             }
2174              
2175             ###############################################################################
2176              
2177             sub debug_check ($$) {
2178 610     610 0 1429 my ($self,$type)=@_;
2179              
2180             # This is a speed up (makes the parsing more than twice faster when a
2181             # local parsing cache is also used).
2182             #
2183             # 8 wallclock secs ( 8.78 usr + 0.01 sys = 8.79 CPU) @ 113765.64/s (n=1000000)
2184             # 19 wallclock secs (18.97 usr + 0.00 sys = 18.97 CPU) @ 52714.81/s (n=1000000)
2185             #
2186             ### return $self->clipboard->get("debug/Web/Page/$type");
2187              
2188 610         1106 my $debug_hash=$self->{'debug_hash'};
2189              
2190 610 100       1547 if(!$debug_hash) {
2191 175         508 $debug_hash=$self->clipboard->get('/debug/Web/Page');
2192 175 100       10474 if($debug_hash) {
2193 82         279 $self->{'debug_hash'}=$debug_hash;
2194             }
2195             else {
2196 93         327 $self->{'debug_hash'}=$debug_hash={ };
2197 93         246 $self->clipboard->put('/debug/Web/Page' => $debug_hash);
2198             }
2199             }
2200              
2201 610         6569 return $debug_hash->{$type};
2202             }
2203              
2204             ###############################################################################
2205              
2206             sub debug_set ($%) {
2207 3     3 0 5 my $self=shift;
2208 3         10 my $args=get_args(\@_);
2209 3         35 foreach my $type (keys %$args) {
2210 4 100       74 $self->clipboard->put("/debug/Web/Page/$type",$args->{$type} ? 1 : 0);
2211             }
2212             }
2213              
2214             ###############################################################################
2215              
2216             sub page_clipboard ($) {
2217 1246     1246 0 2126 my $self=shift;
2218              
2219 1246         2090 my $cb_hash=$self->{'page_clipboard'};
2220              
2221 1246 100       2944 if(!$cb_hash) {
2222 881         2506 $cb_hash=$self->clipboard->get('/xao/page');
2223 881 100       60958 if($cb_hash) {
2224 739         2284 $self->{'page_clipboard'}=$cb_hash;
2225             }
2226             else {
2227 142         527 $self->{'page_clipboard'}=$cb_hash={ };
2228 142         354 $self->clipboard->put('/xao/page' => $cb_hash);
2229             }
2230             }
2231              
2232 1246         9245 return $cb_hash;
2233             }
2234              
2235             ###############################################################################
2236             1;
2237             __END__