]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/WWW/EGWeb.pm
make better use of media_prefix; only apply ses cookie in secure context; updated...
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / WWW / EGWeb.pm
1 package OpenILS::WWW::EGWeb;
2 use strict; use warnings;
3 use Template;
4 use XML::Simple;
5 use XML::LibXML;
6 use File::stat;
7 use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR);
8 use Apache2::Log;
9 use OpenSRF::EX qw(:try);
10 use OpenILS::Utils::CStoreEditor;
11
12 use constant OILS_HTTP_COOKIE_SKIN => 'oils:skin';
13 use constant OILS_HTTP_COOKIE_THEME => 'oils:theme';
14 use constant OILS_HTTP_COOKIE_LOCALE => 'oils:locale';
15
16 my $web_config;
17 my $web_config_file;
18 my $web_config_edit_time;
19
20 sub import {
21     my $self = shift;
22     $web_config_file = shift;
23     unless(-r $web_config_file) {
24         warn "Invalid web config $web_config_file";
25         return;
26     }
27     check_web_config();
28 }
29
30
31 sub handler {
32     my $r = shift;
33     check_web_config($r); # option to disable this
34     my $ctx = load_context($r);
35     my $base = $ctx->{base_path};
36
37     $r->content_type('text/html; encoding=utf8');
38     my $stat = run_context_loader($r, $ctx);
39     return $stat unless $stat == Apache2::Const::OK;
40
41     my($template, $page_args, $as_xml) = find_template($r, $base, $ctx);
42     return Apache2::Const::DECLINED unless $template;
43
44     $template = $ctx->{skin} . "/$template";
45     $ctx->{page_args} = $page_args;
46
47     my $tt = Template->new({
48         OUTPUT => ($as_xml) ?  sub { parse_as_xml($r, $ctx, @_); } : $r,
49         INCLUDE_PATH => $ctx->{template_paths},
50         DEBUG => $ctx->{debug_template}
51     });
52
53     unless($tt->process($template, {ctx => $ctx})) {
54         $r->log->warn('Template error: ' . $tt->error);
55         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
56     }
57
58     return Apache2::Const::OK;
59 }
60
61
62 sub run_context_loader {
63     my $r = shift;
64     my $ctx = shift;
65
66     my $stat = Apache2::Const::OK;
67
68     my $loader = $r->dir_config('OILSWebContextLoader');
69     return $stat unless $loader;
70
71     eval {
72         $loader->use;
73         $stat = $loader->new($r, $ctx)->load;
74     };
75
76     if($@) {
77         $r->log->error("Context Loader error: $@");
78         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
79     }
80
81     $r->log->warn("context loader resulted in status $stat");
82     return $stat;
83 }
84
85 sub parse_as_xml {
86     my $r = shift;
87     my $ctx = shift;
88     my $data = shift;
89
90     my $success = 0;
91
92     try { 
93         my $doc = XML::LibXML->new->parse_string($data); 
94         $data = $doc->documentElement->toStringC14N;
95         $data = $ctx->{final_dtd} . "\n" . $data;
96         $success = 1;
97     } otherwise {
98             my $e = shift;
99         my $err = "Invalid XML: $e";
100         $r->log->error($err);
101         $r->content_type('text/plain; encoding=utf8');
102         $r->print("\n$err\n\n$data");
103     };
104
105     $r->print($data) if ($success);
106 }
107
108
109 sub load_context {
110     my $r = shift;
111     my $cgi = CGI->new;
112     my $ctx = {}; # new context for each page load
113     $ctx->{$_} = $web_config->{base_ctx}->{$_} for keys %{$web_config->{base_ctx}};
114     $ctx->{hostname} = $r->hostname;
115     $ctx->{base_url} = $cgi->url(-base => 1);
116     $ctx->{skin} = $cgi->cookie(OILS_HTTP_COOKIE_SKIN) || 'default';
117     $ctx->{theme} = $cgi->cookie(OILS_HTTP_COOKIE_THEME) || 'default';
118     $ctx->{locale} = 
119         $cgi->cookie(OILS_HTTP_COOKIE_LOCALE) || 
120         parse_accept_lang($r->headers_in->get('Accept-Language')) || 'en-US';
121     $r->log->debug('skin = ' . $ctx->{skin} . ' : theme = ' . 
122         $ctx->{theme} . ' : locale = ' . $ctx->{locale});
123
124     my $mprefix = $ctx->{media_prefix};
125     if($mprefix !~ /^http/ and $mprefix !~ /^\//) {
126         # if a hostname is provided /w no protocol, match the protocol to the current page
127         $ctx->{media_prefix} = ($cgi->https) ? "https://$mprefix" : "http://$mprefix";
128     }
129
130
131     return $ctx;
132 }
133
134 # turn Accept-Language into sometihng EG can understand
135 sub parse_accept_lang {
136     my $al = shift;
137     return undef unless $al;
138     my ($locale) = split(/,/, $al);
139     ($locale) = split(/;/, $locale);
140     return undef unless $locale;
141     $locale =~ s/-(.*)/eval '-'.uc("$1")/e;
142     return $locale;
143 }
144
145 # Given a URI, finds the configured template and any extra page 
146 # arguments (trailing path info).  Any extra data is returned
147 # as page arguments, in the form of an array, one item per 
148 # /-separated URI component
149 sub find_template {
150     my $r = shift;
151     my $base = shift;
152     my $ctx = shift;
153     my $skin = $ctx->{skin};
154     my $path = $r->uri;
155     $path =~ s/$base//og;
156     my @parts = split('/', $path);
157     my $template = '';
158     my $page_args = [];
159     my $as_xml = $ctx->{force_valid_xml};
160     my $handler = $web_config->{handlers};
161
162     while(@parts) {
163         my $part = shift @parts;
164         next unless $part;
165         my $t = $handler->{$part};
166         if(ref($t) eq 'PathConfig') {
167             $template = $t->{template};
168             $as_xml = ($t->{as_xml} and $t->{as_xml} =~ /true/io) || $as_xml;
169             $page_args = [@parts];
170             last;
171         } else {
172             $handler = $t;
173         }
174     }
175
176     unless($template) { # no template configured
177
178         # see if we can magically find the template based on the path and default extension
179         my $ext = $ctx->{default_template_extension};
180
181         my @parts = split('/', $path);
182         my $localpath = $path;
183         my @args;
184         while(@parts) {
185             last unless $localpath;
186             for my $tpath (@{$ctx->{template_paths}}) {
187                 my $fpath = "$tpath/$skin/$localpath.$ext";
188                 $r->log->debug("looking at possible template $fpath");
189                 if(-r $fpath) {
190                     $template = "$localpath.$ext";
191                     last;
192                 }
193             }
194             last if $template;
195             push(@args, pop @parts);
196             $localpath = '/'.join('/', @parts);
197         } 
198
199         $page_args = [@args];
200
201         # no template configured or found
202         unless($template) {
203             $r->log->warn("No template configured for path $path");
204             return ();
205         }
206     }
207
208     $r->log->debug("template = $template : page args = @$page_args");
209     return ($template, $page_args, $as_xml);
210 }
211
212 # if the web configuration file has never been loaded or has
213 # changed since the last load, reload it
214 sub check_web_config {
215     my $r = shift;
216     my $epoch = stat($web_config_file)->mtime;
217     unless($web_config_edit_time and $web_config_edit_time == $epoch) {
218         $r->log->debug("Reloading web config after edit...") if $r;
219         $web_config_edit_time = $epoch;
220         $web_config = parse_config($web_config_file);
221     }
222 }
223
224 sub parse_config {
225     my $cfg_file = shift;
226     my $data = XML::Simple->new->XMLin($cfg_file);
227     my $ctx = {};
228     my $handlers = {};
229
230     $ctx->{media_prefix} = (ref $data->{media_prefix}) ? '' : $data->{media_prefix};
231     $ctx->{base_path} = (ref $data->{base_path}) ? '' : $data->{base_path};
232     $ctx->{template_paths} = [];
233     $ctx->{force_valid_xml} = ($data->{force_valid_xml} =~ /true/io) ? 1 : 0;
234     $ctx->{debug_template} = ($data->{debug_template} =~ /true/io) ? 1 : 0;
235     $ctx->{default_template_extension} = $data->{default_template_extension} || 'tt2';
236     $ctx->{web_dir} = $data->{web_dir};
237
238     my $tpaths = $data->{template_paths}->{path};
239     $tpaths = [$tpaths] unless ref $tpaths;
240     push(@{$ctx->{template_paths}}, $_) for @$tpaths;
241
242     for my $handler (@{$data->{handlers}->{handler}}) {
243         my @parts = split('/', $handler->{path});
244         my $h = $handlers;
245         my $pcount = scalar(@parts);
246         for(my $i = 0; $i < $pcount; $i++) {
247             my $p = $parts[$i];
248             unless(defined $h->{$p}) {
249                 if($i == $pcount - 1) {
250                     $h->{$p} = PathConfig->new(%$handler);
251                     last;
252                 } else {
253                     $h->{$p} = {};
254                 }
255             }
256             $h = $h->{$p};
257         }
258     }
259
260     return {base_ctx => $ctx, handlers => $handlers};
261 }
262
263 package PathConfig;
264 sub new {
265     my($class, %args) = @_;
266     return bless(\%args, $class);
267 }
268
269
270 1;