]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
Merge remote branch 'working/user/dbs/fix-nonfiling-titles'
[Evergreen.git] / Open-ILS / src / perlmods / lib / 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 Encode;
8 use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR);
9 use Apache2::Log;
10 use OpenSRF::EX qw(:try);
11 use OpenILS::Utils::CStoreEditor;
12
13 use constant OILS_HTTP_COOKIE_SKIN => 'eg_skin';
14 use constant OILS_HTTP_COOKIE_THEME => 'eg_theme';
15 use constant OILS_HTTP_COOKIE_LOCALE => 'eg_locale';
16
17 # cache string bundles
18 my @registered_locales;
19
20 sub handler {
21     my $r = shift;
22     my $ctx = load_context($r);
23     my $base = $ctx->{base_path};
24
25     $r->content_type('text/html; encoding=utf8');
26
27     my($template, $page_args, $as_xml) = find_template($r, $base, $ctx);
28     $ctx->{page_args} = $page_args;
29
30     my $stat = run_context_loader($r, $ctx);
31
32     return $stat unless $stat == Apache2::Const::OK;
33     return Apache2::Const::DECLINED unless $template;
34
35     my $text_handler = set_text_handler($ctx, $r);
36
37     my $tt = Template->new({
38         ENCODING => 'utf-8',
39         OUTPUT => ($as_xml) ?  sub { parse_as_xml($r, $ctx, @_); } : $r,
40         INCLUDE_PATH => $ctx->{template_paths},
41         DEBUG => $ctx->{debug_template},
42         PLUGINS => {
43             EGI18N => 'OpenILS::WWW::EGWeb::I18NFilter',
44             CGI_utf8 => 'OpenILS::WWW::EGWeb::CGI_utf8'
45         },
46         FILTERS => {
47             # Register a dynamic filter factory for our locale::maketext generator
48             l => [
49                 sub {
50                     my($ctx, @args) = @_;
51                     return sub { $text_handler->(shift(), @args); }
52                 }, 1
53             ]
54         }
55     });
56
57     if (!$tt) {
58         $r->log->error("Error creating template processor: $@");
59         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
60     }   
61
62     $ctx->{encode_utf8} = sub {return encode_utf8(shift())};
63
64     unless($tt->process($template, {ctx => $ctx, ENV => \%ENV, l => $text_handler})) {
65         $r->log->warn('egweb: template error: ' . $tt->error);
66         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
67     }
68
69     return Apache2::Const::OK;
70 }
71
72 sub set_text_handler {
73     my $ctx = shift;
74     my $r = shift;
75
76     my $locale = $ctx->{locale};
77
78     $r->log->debug("egweb: messages locale = $locale");
79
80     return sub {
81         my $lh = OpenILS::WWW::EGWeb::I18N->get_handle($locale);
82         return $lh->maketext(@_);
83     };
84 }
85
86
87
88 sub run_context_loader {
89     my $r = shift;
90     my $ctx = shift;
91
92     my $stat = Apache2::Const::OK;
93
94     my $loader = $r->dir_config('OILSWebContextLoader');
95     return $stat unless $loader;
96
97     eval {
98         $loader->use;
99         $stat = $loader->new($r, $ctx)->load;
100     };
101
102     if($@) {
103         $r->log->error("egweb: Context Loader error: $@");
104         return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
105     }
106
107     $r->log->debug("egweb: context loader resulted in status $stat");
108     return $stat;
109 }
110
111 sub parse_as_xml {
112     my $r = shift;
113     my $ctx = shift;
114     my $data = shift;
115
116     my $success = 0;
117
118     try { 
119         my $doc = XML::LibXML->new->parse_string($data); 
120         $data = $doc->documentElement->toStringC14N;
121         $data = $ctx->{final_dtd} . "\n" . $data;
122         $success = 1;
123     } otherwise {
124             my $e = shift;
125         my $err = "Invalid XML: $e";
126         $r->log->error("egweb: $err");
127         $r->content_type('text/plain; encoding=utf8');
128         $r->print("\n$err\n\n$data");
129     };
130
131     $r->print($data) if ($success);
132 }
133
134 sub load_context {
135     my $r = shift;
136     my $cgi = CGI->new;
137     my $ctx = {}; # new context for each page load
138
139     $ctx->{base_path} = $r->dir_config('OILSWebBasePath');
140     $ctx->{web_dir} = $r->dir_config('OILSWebWebDir');
141     $ctx->{debug_template} = ($r->dir_config('OILSWebDebugTemplate') =~ /true/io);
142     $ctx->{media_prefix} = $r->dir_config('OILSWebMediaPrefix');
143     $ctx->{hostname} = $r->hostname;
144     $ctx->{base_url} = $cgi->url(-base => 1);
145     $ctx->{skin} = $cgi->cookie(OILS_HTTP_COOKIE_SKIN) || 'default';
146     $ctx->{theme} = $cgi->cookie(OILS_HTTP_COOKIE_THEME) || 'default';
147     $ctx->{proto} = $cgi->https ? 'https' : 'http';
148
149     my @template_paths = $r->dir_config->get('OILSWebTemplatePath');
150     $ctx->{template_paths} = [ reverse @template_paths ];
151
152     my %locales = $r->dir_config->get('OILSWebLocale');
153     load_locale_handlers($ctx, %locales);
154
155     $ctx->{locale} = 
156         $cgi->cookie(OILS_HTTP_COOKIE_LOCALE) || 
157         parse_accept_lang($r->headers_in->get('Accept-Language')) || 'en_us';
158
159     my $mprefix = $ctx->{media_prefix};
160     if($mprefix and $mprefix !~ /^http/ and $mprefix !~ /^\//) {
161         # if a hostname is provided /w no protocol, match the protocol to the current page
162         $ctx->{media_prefix} = ($cgi->https) ? "https://$mprefix" : "http://$mprefix";
163     }
164
165     return $ctx;
166 }
167
168 # turn Accept-Language into sometihng EG can understand
169 # TODO: try all langs, not just the first
170 sub parse_accept_lang {
171     my $al = shift;
172     return undef unless $al;
173     my ($locale) = split(/,/, $al);
174     ($locale) = split(/;/, $locale);
175     return undef unless $locale;
176     $locale =~ s/-/_/og;
177     return $locale;
178 }
179
180 # Given a URI, finds the configured template and any extra page 
181 # arguments (trailing path info).  Any extra data is returned
182 # as page arguments, in the form of an array, one item per 
183 # /-separated URI component
184 sub find_template {
185     my $r = shift;
186     my $base = shift;
187     my $ctx = shift;
188     my $path = $r->uri;
189     $path =~ s/$base\/?//og;
190     my $template = '';
191     my $page_args = [];
192     my $as_xml = $r->dir_config('OILSWebForceValidXML');
193     my $ext = $r->dir_config('OILSWebDefaultTemplateExtension');
194
195     my @parts = split('/', $path);
196     my $localpath = $path;
197     my @args;
198     while(@parts) {
199         last unless $localpath;
200         for my $tpath (@{$ctx->{template_paths}}) {
201             my $fpath = "$tpath/$localpath.$ext";
202             $r->log->debug("egweb: looking at possible template $fpath");
203             if(-r $fpath) {
204                 $template = "$localpath.$ext";
205                 last;
206             }
207         }
208         last if $template;
209         push(@args, pop @parts);
210         $localpath = join('/', @parts);
211     } 
212
213     $page_args = [@args];
214
215     # no template configured or found
216     unless($template) {
217         $r->log->debug("egweb: No template configured for path $path");
218         return ();
219     }
220
221     $r->log->debug("egweb: template = $template : page args = @$page_args");
222     return ($template, $page_args, $as_xml);
223 }
224
225 # Create an I18N sub-module for each supported locale
226 # Each module creates its own MakeText lexicon by parsing .po/.mo files
227 sub load_locale_handlers {
228     my $ctx = shift;
229     my %locales = @_;
230
231     my @locale_tags = sort { length($a) <=> length($b) } keys %locales;
232
233     # If no locales are defined, fall back to en_us so that at least 1 handler exists
234     push(@locale_tags, 'en_us') unless @registered_locales or @locale_tags;
235
236     for my $idx (0..$#locale_tags) {
237
238         my $tag = $locale_tags[$idx];
239         next if grep { $_ eq $tag } @registered_locales;
240
241         my $parent_tag = '';
242         my $sub_idx = $idx;
243
244         # find the parent locale if possible.  It will be 
245         # longest left-anchored substring of the current tag
246         while( --$sub_idx >= 0 ) {
247             my $ptag = $locale_tags[$sub_idx];
248             if( substr($tag, 0, length($ptag)) eq $ptag ) {
249                 $parent_tag = "::$ptag";
250                 last;
251             }
252         }
253
254         my $messages = $locales{$tag} || '';
255
256         # TODO Can we do this without eval?
257         my $eval = <<"        EVAL";
258             package OpenILS::WWW::EGWeb::I18N::$tag;
259             use base 'OpenILS::WWW::EGWeb::I18N$parent_tag';
260             if(\$messages) {
261                 use Locale::Maketext::Lexicon::Gettext;
262                 if(open F, '$messages') {
263                     our %Lexicon = (%Lexicon, %{ Locale::Maketext::Lexicon::Gettext->parse(<F>) });
264                     close F;
265                 } else {
266                     warn "EGWeb: unable to open messages file: $messages"; 
267                 }
268             }
269         EVAL
270         eval $eval;
271
272         if ($@) {
273             warn "$@\n" if $@;
274         } else {
275             push(@registered_locales, $tag);
276         }
277     }
278 }
279
280
281 # base class for all supported locales
282 package OpenILS::WWW::EGWeb::I18N;
283 use base 'Locale::Maketext';
284 our %Lexicon = (_AUTO => 1);
285
286 1;