File Coverage

blib/lib/XAO/Web.pm
Criterion Covered Total %
statement 221 307 71.9
branch 66 136 48.5
condition 21 75 28.0
subroutine 22 27 81.4
pod 9 10 90.0
total 339 555 61.0


line stmt bran cond sub pod time code
1             package XAO::Web;
2 22     22   159726 use warnings;
  22         42  
  22         711  
3 22     22   105 use strict;
  22         43  
  22         355  
4 22     22   89 use Encode;
  22         33  
  22         1448  
5 22     22   110 use Error qw(:try);
  22         57  
  22         109  
6 22     22   2873 use XAO::Utils;
  22         53  
  22         1036  
7 22     22   2017 use XAO::Projects;
  22         5674  
  22         824  
8 22     22   2047 use XAO::Objects;
  22         14771  
  22         683  
9 22     22   8916 use XAO::SimpleHash;
  22         36023  
  22         686  
10 22     22   7833 use XAO::PageSupport;
  22         83  
  22         628  
11 22     22   7105 use XAO::Templates;
  22         55  
  22         646  
12 22     22   122 use XAO::Errors qw(XAO::Web);
  22         39  
  22         130  
13              
14             ###############################################################################
15             # XAO::Web version number. Hand changed with every release!
16             #
17 22     22   6507 use vars qw($VERSION);
  22         38  
  22         71021  
18             $VERSION='1.89';
19              
20             ###############################################################################
21              
22             =head1 NAME
23              
24             XAO::Web - XAO Web Developer, dynamic content building suite
25              
26             =head1 SYNOPSIS
27              
28             use XAO::Web;
29              
30             my $web=XAO::Web->new(sitename => 'test');
31              
32             $web->execute(cgi => $cgi,
33             path => '/index.html');
34              
35             my $config=$web->config;
36              
37             $config->clipboard->put(foo => 'bar');
38              
39             =head1 DESCRIPTION
40              
41             Please read L for general overview and setup
42             instructions, and please read L for an overview
43             of the templating system. Check also misc/samplesite for code examples
44             and a generic site setup.
45              
46             XAO::Web module provides a frameworks for loading site configuration and
47             executing objects and templates in the site context. It is used in
48             scripts and in Apache web server handler to generate actual web pages
49             content.
50              
51             Normally a developer does not need to use XAO::Web directly.
52              
53             =head1 SITE INITIALIZATION
54              
55             When XAO::Web creates a new site (for mod_perl that happens only once
56             during each instance on Apache lifetime) it first loads new 'Config'
57             object using XAO::Objects' new() method and site name it knows. If site
58             overrides Config - it loads site specific Config, if not - the systme
59             one.
60              
61             After the object is created XAO::Web embeds two standard additional
62             configuration objects into it:
63              
64             =over
65              
66             =item hash
67              
68             Hash object is primarily used to keep site configuration parameters. It
69             is just a XAO::SimpleHash object and most of its methods get embedded -
70             get, put, getref, delete, defined, exists, keys, values, contains.
71              
72             =item web
73              
74             Web configuration embeds methods that allow cookie, clipboard and
75             cgi manipulations -- add_cookie, cgi, clipboard, cookies, header,
76             header_args.
77              
78             =back
79              
80             After that XAO::Web calls init() method on the Config object which
81             is supposed to finish configuration set up and usually stuffs some
82             parameters into 'hash', then connects to a database and embeds database
83             configuration object into the Config object as well. Refer to
84             L for an example of site specific Config object and
85             init() method.
86              
87             When object initialization is completed the Config object is placed into
88             XAO::Projects registry and is retrieved from there on next access to the
89             same site in case of mod_perl.
90              
91             B that means that if you are embedding a site specific version
92             of an object during initialisation you need to pass 'sitename' into
93             XAO::Objects' new() method.
94              
95             =head1 METHODS
96              
97             Methods of XAO::Web objects include:
98              
99             =over
100              
101             =cut
102              
103             ###############################################################################
104              
105             sub analyze ($$;$$);
106             sub clipboard ($);
107             sub config ($);
108             sub execute ($%);
109             sub new ($%);
110             sub set_current ($);
111             sub sitename ($);
112              
113             ###############################################################################
114              
115             =item analyze ($;$$)
116              
117             Checks how to display the given path (scalar or split up array
118             reference). Always returns valid results or throws an error if that
119             can't be accomplished.
120              
121             Returns hash reference:
122              
123             prefix => longest matching prefix (directory in case of template found)
124             path => path to the page after the prefix
125             fullpath => full path from original query
126             objname => object name that will serve this path
127             objargs => object args hash (may be empty)
128              
129             Optional second argument can be used to enforce a specific site name.
130              
131             Optional third argument must be used to allow returning records of types
132             other than 'xaoweb'. This is used by Apache::XAO to get 'maptodir' and
133             'external' mappings. Default is to look for xaoweb only records.
134              
135             =cut
136              
137             sub analyze ($$;$$) {
138 84     84 1 157 my ($self,$patharr,$sitename,$allow_other_types)=@_;
139              
140 84 50       136 $patharr=[ split(/\/+/,$patharr) ] unless ref $patharr;
141              
142 84   66     483 shift @$patharr while @$patharr && !length($patharr->[0]);
143 84         154 unshift(@$patharr,'');
144 84         172 my $path=join('/',@$patharr);
145              
146             # Looking for the object matching the path.
147             #
148 84         131 my $siteconfig=$self->config;
149 84         1300 my $table=$siteconfig->get('path_mapping_table');
150 84 50       2973 if($table) {
151 84         172 for(my $i=@$patharr; $i>=0; $i--) {
152 248 100       412 my $dir=$i ? join('/',@{$patharr}[0..$i-1]) : '';
  167         311  
153              
154             my $od=$table->{$dir} ||
155             $table->{'/'.$dir} ||
156             $table->{$dir.'/'} ||
157 248   33     1158 $table->{'/'.$dir.'/'};
158 248 100       535 next unless defined $od;
159              
160             ##
161             # If $od is an empty string or an empty array reference --
162             # this means that we need to fall back to default handler
163             # for that path.
164             #
165             # The same happens for 'default' type in a hash reference.
166             #
167 3         4 my $rhash;
168 3 50       8 if(ref($od) eq 'HASH') {
    0          
169 3   50     16 my $type=$od->{'type'} || 'xaoweb';
170 3 50 0     10 if($type eq 'default') {
    50          
    0          
    0          
171 0         0 last;
172             }
173             elsif($type eq 'xaoweb') {
174 3 50       6 if(!$od->{'objname'}) {
175 0         0 throw XAO::E::Web "analyze - no objname/objargs for '$dir'";
176             }
177 3         8 $rhash=merge_refs($od);
178             }
179             elsif($allow_other_types) {
180 0         0 $rhash=merge_refs($od);
181             }
182             elsif($od->{'xaoweb'} && ref($od->{'xaoweb'}) eq 'HASH') {
183 0         0 $rhash=merge_refs($od->{'xaoweb'});
184             }
185             else {
186 0         0 next;
187             }
188             }
189             elsif(ref($od) eq 'ARRAY') {
190 0 0       0 last unless @$od;
191 0         0 my %args;
192 0 0       0 if(scalar(@{$od})%2 == 1) {
  0         0  
193 0         0 %args=@{$od}[1..$#{$od}];
  0         0  
  0         0  
194             }
195             else {
196 0         0 throw XAO::E::Web "analyze - odd number of arguments in the mapping table, dir=$dir, objname=$od->[0]";
197             }
198 0         0 $rhash={
199             type => 'xaoweb',
200             objname => $od->[0],
201             objargs => \%args,
202             };
203             }
204             else {
205 0 0       0 last unless length($od);
206 0         0 $rhash={
207             type => 'xaoweb',
208             objname => $od,
209             objargs => { },
210             };
211             }
212              
213 3         89 $rhash->{'path'}=join('/',@{$patharr}[$i..$#$patharr]);
  3         10  
214 3         8 $rhash->{'patharr'}=$patharr;
215 3         9 $rhash->{'prefix'}=$dir;
216 3         7 $rhash->{'fullpath'}=$path;
217              
218 3         8 return $rhash;
219             }
220             }
221              
222             ##
223             # Now looking for exactly matching template and returning Page
224             # object if found.
225             #
226 81         217 my $filename=XAO::Templates::filename($path,$sitename);
227 81 100       210 if($filename) {
228             return {
229             type => 'xaoweb',
230             subtype => 'file',
231             objname => 'Page',
232             objargs => { },
233             path => $path,
234             patharr => $patharr,
235             fullpath => $path,
236 80         180 prefix => join('/',@{$patharr}[0..($#$patharr-1)]),
  80         672  
237             filename => $filename,
238             };
239             }
240              
241             ##
242             # Nothing was found, returning Default object
243             #
244             return {
245 1         15 type => 'xaoweb',
246             subtype => 'notfound',
247             objname => 'Default',
248             path => $path,
249             patharr => $patharr,
250             fullpath => $path,
251             prefix => ''
252             };
253             }
254              
255             ###############################################################################
256              
257             =item clipboard ()
258              
259             Returns site clipboard object.
260              
261             =cut
262              
263             sub clipboard ($) {
264 0     0 1 0 my $self=shift;
265 0         0 return $self->config->clipboard;
266             }
267              
268             ###############################################################################
269              
270             =item config ()
271              
272             Returns site configuration object reference.
273              
274             =cut
275              
276             sub config ($) {
277 630     630 1 855 my $self=shift;
278 630   33     4722 return $self->{'siteconfig'} ||
279             throw XAO::E::Web "config - no configuration object";
280             }
281              
282             ###############################################################################
283              
284             =item execute (%)
285              
286             Executes given `path' using given `cgi' environment. Prints results to
287             standard output and uses CGI object methods to send header.
288              
289             B Execute() changes global projects context and is not re-entry safe
290             currently! Meaning that if you create a XAO::Web object in any method
291             called inside of execute() loop and then call execute() on that newly
292             created XAO::Web object the system will fail and no useful results will
293             be produced.
294              
295             =cut
296              
297             sub execute ($%) {
298 11     11 1 24 my $self=shift;
299 11         33 my $args=get_args(\@_);
300              
301             # Setting dprint/eprint to Apache or PSGI methods if needed
302             #
303 11         134 my $old_logprint_handler;
304 11 50       49 if($args->{'apache'}) {
    50          
305             $old_logprint_handler=XAO::Utils::set_logprint_handler(sub {
306 0     0   0 $args->{'apache'}->server->warn($_[0]);
307 0         0 });
308             }
309             elsif($args->{'psgi'}) {
310             $old_logprint_handler=XAO::Utils::set_logprint_handler(sub {
311 0     0   0 $args->{'psgi'}->{'psgi.errors'}->print($_[0]."\n");
312 0         0 });
313             }
314              
315             # Setting the current project context to our site.
316             #
317 11         25 $self->set_current();
318              
319             # We check if the site has a mapping for '/internal-error' in
320             # path_mapping_table. If it has we wrap process() into the try block
321             # and execute /internal-error if we get an error.
322             #
323 11         52 my $pagetext;
324             try {
325 11     11   338 $pagetext=$self->process($args);
326             }
327             otherwise {
328 0     0   0 my $e=shift;
329              
330             # Under mod_perl we get apache's internal exceptions for genuine apache
331             # problems (timeouts, etc). These are not re-throwable apparently,
332             # so we wrap them into Error::Simple.
333             #
334 0 0       0 if($e->isa('APR::Error')) {
335 0         0 $e=Error::Simple->new("$e");
336             }
337              
338             $self->config->header_args(
339 0         0 -Status => '500 Internal Error',
340             -expires => 'now',
341             -cache_control => 'no-cache',
342             );
343              
344 0   0     0 my $edata=$self->clipboard->get('/internal_error') || { };
345              
346 0   0     0 my $path=$edata->{'display_path'} || '/internal-error/index.html';
347 0         0 my $pd=$self->analyze($path);
348              
349 0 0 0     0 if($pd && $pd->{'type'} eq 'xaoweb' && $pd->{'objname'} ne 'Default') {
      0        
350 0         0 eprint "$e";
351              
352 0   0     0 $edata->{'message'}||="$e";
353 0   0     0 $edata->{'code'}||='UNKNOWN';
354 0   0     0 $edata->{'path'}||=$args->{'path'};
355 0   0     0 $edata->{'pagedesc'}||=$self->clipboard->get('pagedesc');
356              
357 0         0 $self->clipboard->put(internal_error => $edata);
358              
359 0         0 $pagetext=$self->process($args,{
360             path => $path,
361             template => undef,
362             pagedesc => $pd,
363             });
364             }
365             else {
366 0 0       0 XAO::Utils::set_logprint_handler($old_logprint_handler) if $old_logprint_handler;
367 0         0 throw $e;
368             }
369 11         111 };
370              
371             # We need to call "header" for CGI to do its magic on it. We
372             # typically will get an empty string in mod_perl environment, and the
373             # header will be sent to Apache by CGI.
374             #
375 11         203 my $header=$self->config->header;
376              
377             # If we get the header then it was not printed before and we are
378             # expected to print out the page. This is almost always true except
379             # when page includes something like Redirect object.
380             #
381 11         9696 my $result;
382 11 50       21 if(defined $header) {
383 11 50       32 if(my $env=$args->{'psgi'}) {
    50          
384              
385             # Can't use $header, need an array that includes header_args
386             # and cookies.
387             #
388             $result=[
389 0         0 $args->{'cgi'}->psgi_header({ $self->config->header_array() }),
390             [ $pagetext ],
391             ];
392             }
393             elsif(my $r=$args->{'apache'}) {
394 0         0 my $h=$self->config->header_args;
395              
396 0 0 0     0 if($mod_perl::VERSION && $mod_perl::VERSION >= 1.99) {
397             # This is accomplished by CGI when config->header is
398             # called above, and it does not work properly anyway
399             #
400             ### while(my ($n,$v)=each %$h) {
401             ### dprint "n='$n' v='$v'";
402             ### $r->headers_out->set($n => $v);
403             ### $r->err_headers_out->set($n => $v);
404             ### }
405 0 0       0 $r->content_type('text/html') unless $r->content_type;
406             }
407             else {
408 0         0 while(my ($n,$v)=each %$h) {
409 0         0 $r->header_out($n => $v);
410 0         0 $r->err_header_out($n => $v);
411             }
412 0         0 $r->send_http_header;
413             }
414              
415 0 0       0 $r->print($pagetext) unless $r->header_only;
416             }
417             else {
418 11         344 print $header,
419             $pagetext;
420             }
421             }
422              
423             # Cleaning up site configuration
424             #
425 11         39 $self->config->cleanup(mode => 'after');
426              
427             # Restoring the default dprint/eprint handling
428             #
429 11 50       74 XAO::Utils::set_logprint_handler($old_logprint_handler) if $old_logprint_handler;
430              
431             # Only really needed for PSGI
432             #
433 11         31 return $result;
434             }
435              
436             ###############################################################################
437              
438             =item expand (%)
439              
440             Expands given `path' using given `cgi' or 'apache' environment. Returns
441             just the text of the page in scalar context and page content plus header
442             content in array context.
443              
444             This is normally used in scripts to execute only a particular template
445             and to get results of execution. BUT this code is also used as part of
446             the normal execute().
447              
448             `Objargs' argument may refer to a hash of additional parameters to be
449             passed to the template being executed.
450              
451             Example:
452              
453             my $report=$web->expand(
454             cgi => XAO::Objects->new(objname => 'CGI'),
455             path => '/bits/stat-report',
456             objargs => {
457             CUSTOMER_ID => '123X234Z',
458             MIN_TIME => time - 86400 * 7,
459             },
460             );
461              
462             See also lower level process() method.
463              
464             =cut
465              
466             sub expand ($%) {
467 71     71 1 523 my $self=shift;
468 71         159 my $args=get_args(\@_);
469              
470 71         687 $self->set_current;
471              
472             # Processing the page and getting its text. Setting dprint and
473             # eprint to use Apache logging if there is a reference to Apache
474             # request given to us.
475             #
476 71         211 my $pagetext=$self->process($args);
477              
478             # In scalar context (normal cases) we return only the resulting page
479             # text. In array context (compatibility) we return header as well.
480             #
481 71 50       121 if(wantarray) {
482 0         0 eprint "Calling ".ref($self)."::expand in ARRAY context is obsolete";
483 0         0 my $header=$self->config->header;
484 0         0 $self->config->cleanup(mode => 'after');
485 0         0 return ($pagetext,$header);
486             }
487             else {
488 71         140 $self->config->cleanup(mode => 'after');
489 71         283 return $pagetext;
490             }
491             }
492              
493             ###############################################################################
494              
495             sub _expand_list ($$) {
496 161     161   4108 my ($self,$autolist)=@_;
497              
498 161         214 my $content='';
499              
500 161 100       258 if(!$autolist) {
    50          
    0          
501 138         391 return '';
502             }
503             elsif(ref($autolist) eq 'ARRAY') {
504 23         41 my $clipboard=$self->config->clipboard;
505              
506 23         53 for(my $i=0; $i<@$autolist; $i+=2) {
507 28         151 my ($objname,$objargs)=@{$autolist}[$i,$i+1];
  28         61  
508 28         82 my $obj=XAO::Objects->new(objname => $objname);
509 28         1672 $content.=$obj->expand($objargs);
510              
511             # Not processing any more if there was a final output.
512             #
513 28 100       76 last if $clipboard->get('_no_more_output');
514             }
515             }
516             elsif(ref($autolist) eq 'HASH') {
517 0         0 eprint "Using HASH auto-list is deprecated, use an ordered array";
518 0         0 foreach my $objname (keys %{$autolist}) {
  0         0  
519 0         0 my $obj=XAO::Objects->new(objname => $objname);
520 0         0 $content.=$obj->expand($autolist->{$objname});
521             }
522             }
523             else {
524 0         0 throw XAO::E::Web "process - don't know how to handle ($autolist)," .
525             " must be a hash or an array reference";
526             }
527              
528 23         568 return $content;
529             }
530              
531             ###############################################################################
532              
533             =item process (%)
534              
535             Takes the same arguments as the expand() method returning expanded page
536             text. Does not clean the site context and should not be called directly
537             -- for normal situations either expand() or execute() methods should be
538             called.
539              
540             =cut
541              
542             sub process ($%) {
543 82     82 1 117 my $self=shift;
544 82         148 my $args=get_args(\@_);
545              
546 82         603 my $siteconfig=$self->config;
547 82         1516 my $clipboard=$siteconfig->clipboard;
548 82         128 my $sitename=$self->sitename;
549              
550             # Making sure path starts from a slash
551             #
552 82   33     170 my $path=$args->{'path'} || throw XAO::E::Web "process - no 'path' given";
553 82         145 $path='/' . $path;
554 82         291 $path=~s/\/{2,}/\//g;
555              
556             # Resetting page text stack in case it was terminated abnormally
557             # before and we're in the same process/memory.
558             #
559 82         210 XAO::PageSupport::reset();
560              
561             # Analyzing the path. We have to do that up here because the object
562             # might specify that we should not touch CGI.
563             #
564 82         111 my $pd=$args->{'pagedesc'};
565 82 50       144 if(!$pd) {
566 82         183 my @path=split(/\//,$path);
567 82 50       154 push(@path,"") unless @path;
568 82 50       160 push(@path,"index.html") if $path =~ /\/$/;
569 82         165 $pd=$self->analyze(\@path);
570             }
571              
572             # Figuring out current active URL. It might be the same as base_url
573             # and in most cases it is, but it just as well might be different.
574             #
575             # The URL should be full path to the start point -
576             # http://host.com in case of rewrite and something like
577             # http://host.com/cgi-bin/xao-apache.pl/sitename in case of plain
578             # CGI usage.
579             #
580 82         131 my $active_url;
581 82         115 my $apache=$args->{'apache'};
582 82         99 my $cgi=$args->{'cgi'};
583 82 100       149 if(!$cgi) {
584 7 50       12 !$args->{'psgi'} ||
585             throw XAO::E::Web "- need to have a CGI with PSGI";
586 7         29 $cgi=XAO::Objects->new(objname => 'CGI', no_cgi => $pd->{'no_cgi'});
587             }
588 82 50       147 if($apache) {
589 0         0 $active_url="http://" . $apache->hostname;
590             }
591             else {
592 82 50 33     236 if(defined($CGI::VERSION) && $CGI::VERSION>=2.80) {
593 82         452 $active_url=$cgi->url(-base => 1, -full => 0);
594 82   100     25177 my $pinfo=$cgi->path_info || '';
595 82   100     964 my $uri=$cgi->request_uri || '';
596 82         546 $uri=~s/^(.*?)\?.*$/$1/;
597 82 100 33     611 if($pinfo =~ /^\/\Q$sitename\E(\/.+)?\Q$uri\E/) {
    50          
598             # mod_rewrite
599             }
600             elsif($pinfo && $uri =~ /^(.*)\Q$pinfo\E$/) {
601             # cgi
602 0         0 $active_url.=$1;
603             }
604             # dprint ">2.8 $active_url";
605             }
606             else {
607 0         0 $active_url=$cgi->url(-full => 1, -path_info => 0);
608 0 0       0 $active_url=$1 if $active_url=~/^(.*)(\Q$path\E)$/;
609             # dprint "<2.8 $active_url";
610             }
611              
612             # Trying to understand if rewrite module was used or not. If not
613             # - adding sitename to the end of guessed URL.
614             #
615 82 50 33     340 if($active_url =~ /cgi-bin/ || $active_url =~ /xao-[\w-]+\.pl/) {
616 0         0 $active_url.="/$sitename";
617             }
618             }
619              
620             # Eating extra slashes
621             #
622 82         160 chop($active_url) while $active_url =~ /\/$/;
623 82         185 $active_url=~s/(?
624              
625             # Figuring out secure URL
626             #
627 82         101 my $active_is_secure;
628             my $active_url_secure;
629 82 100       225 if($active_url =~ /^http:(\/\/.*)$/) {
    50          
630 48         114 $active_url_secure='https:' . $1;
631 48         58 $active_is_secure=0;
632             }
633             elsif($active_url =~ /^https:(\/\/.*)$/) {
634 34         41 $active_url_secure=$active_url;
635 34         67 $active_url='http:' . $1;
636 34         47 $active_is_secure=1;
637             }
638             else {
639 0         0 dprint "Wrong active URL ($active_url)";
640 0         0 $active_url_secure=$active_url;
641             }
642              
643             # Storing active URLs
644             #
645 82         229 $clipboard->put(active_url => $active_url);
646 82         1384 $clipboard->put(active_url_secure => $active_url_secure);
647              
648             # Checking if we have base_url, assuming active_url if not.
649             # Ensuring that URL does not end with '/'.
650             #
651 82 50       2483 if($siteconfig->defined('base_url')) {
652 82         2388 my $url=$siteconfig->get('base_url');
653 82 50       2827 $url=~/^http:/i ||
654             throw XAO::E::Web "- bad base_url ($url) for sitename=$sitename";
655 82         122 my $nu=$url;
656 82         157 chop($nu) while $nu =~ /\/$/;
657 82 50       141 $siteconfig->put(base_url => $nu) if $nu ne $url;
658              
659 82         1172 $url=$siteconfig->get('base_url_secure');
660 82 50       2566 if(!$url) {
661 0         0 $url=$siteconfig->get('base_url');
662 0         0 $url=~s/^http:/https:/i;
663             }
664 82         102 $nu=$url;
665 82         151 chop($nu) while $nu =~ /\/$/;
666 82         1199 $siteconfig->put(base_url_secure => $nu);
667             }
668             else {
669 0         0 $siteconfig->put(base_url => $active_url);
670 0         0 $siteconfig->put(base_url_secure => $active_url_secure);
671 0         0 dprint "No base_url for sitename '$sitename'; assuming base_url=$active_url, base_url_secure=$active_url_secure";
672             }
673              
674             # Checking if we're running under mod_perl
675             #
676 82 50 33     1542 my $mod_perl=($apache || $ENV{'MOD_PERL'}) ? 1 : 0;
677 82         173 $clipboard->put(mod_perl => $mod_perl);
678 82         1112 $clipboard->put(mod_perl_request => $apache);
679              
680             # Checking if a charset is known for the site. If it is, setting
681             # it up for CGI-params decoding and for output.
682             #
683 82         2116 my $charset=$siteconfig->get('charset');
684 82 50       2641 if($charset) {
685 82 50       173 if($cgi->can('set_param_charset')) {
686 82         151 $cgi->set_param_charset($charset);
687             }
688             else {
689 0         0 eprint "CGI object we have does not support set_param_charset";
690             }
691 82         1243 $siteconfig->header_args(
692             -Charset => $charset,
693             );
694             }
695              
696             # Putting CGI object into site configuration. The special case is
697             # 'no_cgi' in the path_mapping_table which means that the object is
698             # going to handle CGI arguments itself. It can be useful if it needs
699             # raw query string.
700             #
701 82         186 $siteconfig->embedded('web')->enable_special_access;
702 82         1281 $siteconfig->cgi($cgi);
703 82         133 $siteconfig->embedded('web')->disable_special_access;
704              
705             # Traditionally URLs that do not end with .foo are considered
706             # directories and get an internal redirect to path/index.html
707             # Sometimes it is desirable to be able to pass down any URLs without
708             # a forced redirect -- this is controlled by 'urlstyle' parameter
709             # set to 'raw'.
710             #
711 82   100     263 my $urlstyle=$pd->{'urlstyle'} || 'files';
712 82 100       192 if($urlstyle eq 'files') {
    50          
713 81 100       292 if($pd->{'patharr'}->[-1] !~ /\.\w+$/) {
714 2         10 my $pd=$self->analyze([ @{$pd->{'patharr'}},'index.html' ]);
  2         12  
715             #use Data::Dumper; dprint "pd=",Dumper($pd);
716 2 100       20 if($pd->{'objname'} ne 'Default') {
717 1 50       19 my $newpath=$siteconfig->get($active_is_secure ? 'base_url_secure' : 'base_url') . $path . '/';
718 1         44 dprint "Redirecting $path to $newpath";
719 1         22 $siteconfig->header_args(
720             -Location => $newpath,
721             -Status => 301,
722             );
723 1         7 return "Directory index redirection\n";
724             }
725             }
726             }
727             elsif($urlstyle eq 'raw') {
728             # nothing
729             }
730             else {
731 0         0 eprint "Unknown urlstyle '$urlstyle' for $path";
732             }
733              
734             # Separator for the error_log :)
735             #
736 81 50 33     178 if(XAO::Utils::get_debug() && !$args->{'quieter'}) {
737 0         0 my @d=localtime;
738 0         0 my $date=sprintf("%02u:%02u:%02u %u/%02u/%04u",$d[2],$d[1],$d[0],$d[4]+1,$d[3],$d[5]+1900);
739 0         0 undef(@d);
740 0         0 dprint "============ date=$date, mod_perl=$mod_perl, " .
741             "path='$path', translated='$pd->{path}'";
742             }
743              
744             # Putting path decription into the site clipboard
745             #
746 81         363 $clipboard->put(pagedesc => $pd);
747              
748             # Setting expiration time in the page header to immediate
749             # expiration. If that's not what the page wants -- it can override
750             # these.
751             #
752 81         2325 $siteconfig->header_args(
753             -expires => 'now',
754             -cache_control => 'no-cache',
755             );
756              
757             # Do we need to run any objects before executing? A good place to
758             # turn on debug mode if required using Debug object.
759             #
760 81         1247 my $pageheader=$self->_expand_list($siteconfig->get('auto_before'));
761              
762             # If the header issued a final output (commonly a redirect), then
763             # nothing else needs to be done.
764             #
765 81         107 my $pagebody='';
766 81         118 my $pagefooter='';
767 81 100       143 if(!$clipboard->get('_no_more_output')) {
768              
769             # Preparing object arguments out of standard ones, object specific
770             # once from template paths and supplied hash (in that order of
771             # preference).
772             #
773             my $objargs={
774             path => $pd->{'path'},
775             fullpath => $pd->{'fullpath'},
776 80         1613 prefix => $pd->{'prefix'},
777             };
778              
779 80         209 $objargs=merge_refs($objargs,$pd->{'objargs'},$args->{'objargs'});
780              
781             # Loading page displaying object and executing it.
782             #
783 80         1387 my $obj=XAO::Objects->new(objname => 'Web::' . $pd->{'objname'});
784 80         4534 $pagebody=$obj->expand($objargs);
785              
786             # Do we need to run any objects after executing? A good place to
787             # dump benchmark statistics for example.
788             #
789 80         1349 $pagefooter=$self->_expand_list($siteconfig->get('auto_after'));
790             }
791              
792             # Done! Somewhat convoluted way of joining strings is here because
793             # the page header would be a unicode character string (even if
794             # it is really an empty string) and that would contaminate the
795             # concatenation and convert the resulting page text into a character
796             # string. That is not desirable if the output is a binary document.
797             #
798             my $pagetext=join('',map {
799 81 100 50     183 Encode::is_utf8($_) ? Encode::encode($charset || 'utf8',$_) : $_;
  243         746  
800             } ($pageheader,$pagebody,$pagefooter));
801              
802             ### dprint "---length(pageheader)=".length($pageheader).", utf8=".Encode::is_utf8($pageheader);
803             ### dprint "---length(pagebody)= ".length($pagebody).", utf8=".Encode::is_utf8($pagebody);
804             ### dprint "---length(pagefooter)=".length($pagefooter).", utf8=".Encode::is_utf8($pagefooter);
805             ### dprint "---length(pagetext)= ".length($pagetext).", utf8=".Encode::is_utf8($pagetext);
806              
807 81         1363 $siteconfig->header_args(
808             -content_length => length($pagetext),
809             );
810              
811 81         231 return $pagetext;
812             }
813              
814             ###############################################################################
815              
816             =item new (%)
817              
818             Creates or loads a context for the named site. The only required
819             argument is 'sitename' which provides the name of the site.
820              
821             =cut
822              
823             sub new ($%) {
824 38     38 1 319 my $proto=shift;
825 38         886 my $args=get_args(\@_);
826              
827             ##
828             # Getting site name
829             #
830 38   33     1628 my $sitename=$args->{'sitename'} ||
831             throw XAO::E::Web "new - required parameter missing (sitename)";
832              
833             ##
834             # Loading or creating site configuration object.
835             #
836 38         463 my $siteconfig=XAO::Projects::get_project($sitename);
837 38 50       818 if(!$siteconfig) {
838             ##
839             # Creating configuration.
840             #
841 38         577 $siteconfig=XAO::Objects->new(
842             sitename => $sitename,
843             objname => 'Config',
844             );
845              
846             ##
847             # Always embedding at least web config and a hash
848             #
849 38         129644 $siteconfig->embed(web => new XAO::Objects objname => 'Web::Config');
850 38         8838 $siteconfig->embed(hash => new XAO::SimpleHash);
851              
852             ##
853             # Running initialization, this is where parameters are inserted and
854             # normally FS::Config gets embedded.
855             #
856 38   33     10844 $siteconfig->init($args->{'init_args'} || ());
857              
858             ##
859             # Creating an entry in in-memory projects repository
860             #
861 38         11547 XAO::Projects::create_project(
862             name => $sitename,
863             object => $siteconfig,
864             );
865             }
866              
867             # CGI in args is not supported any more, needs to be passed in execute
868             #
869 38 50       1898 $args->{'cgi'} &&
870             throw XAO::E::Web "- 'cgi' argument to 'new' is not supported, pass it to 'execute'";
871              
872             # This helps Mailer to be called outside of web context.
873             # TODO: Probably need some better initialization strategy, this does
874             # not feel as the Right Thing
875             #
876 38         886 my $url=$siteconfig->get('base_url');
877 38 50       3302 if($url) {
878 38 50       345 $url=~/^http:/i ||
879             throw XAO::E::Web "new - bad base_url ($url) for sitename=$sitename";
880 38         89 my $nu=$url;
881 38         501 chop($nu) while $nu =~ /\/$/;
882 38 50       154 $siteconfig->put(base_url => $nu) if $nu ne $url;
883              
884 38         744 $url=$siteconfig->get('base_url_secure');
885 38 50       1056 if(!$url) {
886 38         604 $url=$siteconfig->get('base_url');
887 38         1593 $url=~s/^http:/https:/i;
888             }
889 38         80 $nu=$url;
890 38         136 chop($nu) while $nu =~ /\/$/;
891 38         754 $siteconfig->put(base_url_secure => $nu);
892             }
893              
894             # Done
895             #
896             bless {
897 38   33     1666 sitename => $sitename,
898             siteconfig => $siteconfig,
899             }, ref($proto) || $proto;
900             }
901              
902             ###############################################################################
903              
904             sub check_uri_access ($$) {
905 0     0 0 0 my ($self,$uri)=@_;
906              
907             # By convention we disallow access to /bits/ and /CVS/ for security
908             # reasons. If needed the site can override these or add other
909             # regex'es into path_deny_table
910             #
911 0         0 my $pdtc=$self->config->get('path_deny_table_compiled');
912 0 0       0 if(!$pdtc) {
913 0   0     0 my $pdt=merge_refs({
914             '/bits/' => 1,
915             '/CVS/' => 1,
916             },$self->config->get('path_deny_table') || { });
917 0         0 $pdtc=[ map { qr/$_/ } grep { $pdt->{$_} } keys %$pdt ];
  0         0  
  0         0  
918 0         0 $self->config->put('path_deny_table_compiled' => $pdtc);
919             }
920              
921 0         0 return ! grep { $uri =~ $_ } @$pdtc;
  0         0  
922             }
923              
924             ###############################################################################
925              
926             =item set_current ()
927              
928             Sets the current site as the current project in the sense of XAO::Projects.
929              
930             =cut
931              
932             sub set_current ($) {
933 120     120 1 170 my $self=shift;
934              
935 120         296 XAO::Projects::set_current_project($self->sitename);
936              
937             # Cleaning up the configuration. Useful even if it was just created
938             # as it will unlock tables in the database for instance.
939             # We call it here because cleanup code may rely on the project being
940             # active.
941             #
942 120         1250 $self->config->cleanup(mode => 'before');
943             }
944              
945             ###############################################################################
946              
947             =item sitename ()
948              
949             Returns site name.
950              
951             =cut
952              
953             sub sitename ($) {
954 202     202 1 249 my $self=shift;
955 202 50       721 $self->{'sitename'} || throw XAO::E::Web "sitename - no site name";
956             }
957              
958             ###############################################################################
959             1;
960             __END__