1 package OpenILS::WWW::EGWeb;
2 use strict; use warnings;
8 use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR);
10 use OpenSRF::EX qw(:try);
11 use OpenILS::Utils::CStoreEditor;
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';
17 # cache string bundles
18 my @registered_locales;
22 my $ctx = load_context($r);
23 my $base = $ctx->{base_path};
25 $r->content_type('text/html; encoding=utf8');
27 my($template, $page_args, $as_xml) = find_template($r, $base, $ctx);
28 $ctx->{page_args} = $page_args;
30 my $stat = run_context_loader($r, $ctx);
32 return $stat unless $stat == Apache2::Const::OK;
33 return Apache2::Const::DECLINED unless $template;
35 my $text_handler = set_text_handler($ctx, $r);
37 my $tt = Template->new({
38 OUTPUT => ($as_xml) ? sub { parse_as_xml($r, $ctx, @_); } : $r,
39 INCLUDE_PATH => $ctx->{template_paths},
40 DEBUG => $ctx->{debug_template},
42 EGI18N => 'OpenILS::WWW::EGWeb::I18NFilter',
43 CGI_utf8 => 'OpenILS::WWW::EGWeb::CGI_utf8'
46 # Register a dynamic filter factory for our locale::maketext generator
50 return sub { $text_handler->(shift(), @args); }
57 $r->log->error("Error creating template processor: $@");
58 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
61 $ctx->{encode_utf8} = sub {return encode_utf8(shift())};
63 unless($tt->process($template, {ctx => $ctx, ENV => \%ENV, l => $text_handler})) {
64 $r->log->warn('egweb: template error: ' . $tt->error);
65 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
68 return Apache2::Const::OK;
71 sub set_text_handler {
75 my $locale = $ctx->{locale};
77 $r->log->debug("egweb: messages locale = $locale");
80 my $lh = OpenILS::WWW::EGWeb::I18N->get_handle($locale);
81 return $lh->maketext(@_);
87 sub run_context_loader {
91 my $stat = Apache2::Const::OK;
93 my $loader = $r->dir_config('OILSWebContextLoader');
94 return $stat unless $loader;
98 $stat = $loader->new($r, $ctx)->load;
102 $r->log->error("egweb: Context Loader error: $@");
103 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
106 $r->log->debug("egweb: context loader resulted in status $stat");
118 my $doc = XML::LibXML->new->parse_string($data);
119 $data = $doc->documentElement->toStringC14N;
120 $data = $ctx->{final_dtd} . "\n" . $data;
124 my $err = "Invalid XML: $e";
125 $r->log->error("egweb: $err");
126 $r->content_type('text/plain; encoding=utf8');
127 $r->print("\n$err\n\n$data");
130 $r->print($data) if ($success);
136 my $ctx = {}; # new context for each page load
138 $ctx->{base_path} = $r->dir_config('OILSWebBasePath');
139 $ctx->{web_dir} = $r->dir_config('OILSWebWebDir');
140 $ctx->{debug_template} = ($r->dir_config('OILSWebDebugTemplate') =~ /true/io);
141 $ctx->{media_prefix} = $r->dir_config('OILSWebMediaPrefix');
142 $ctx->{hostname} = $r->hostname;
143 $ctx->{base_url} = $cgi->url(-base => 1);
144 $ctx->{skin} = $cgi->cookie(OILS_HTTP_COOKIE_SKIN) || 'default';
145 $ctx->{theme} = $cgi->cookie(OILS_HTTP_COOKIE_THEME) || 'default';
146 $ctx->{proto} = $cgi->https ? 'https' : 'http';
148 my @template_paths = $r->dir_config->get('OILSWebTemplatePath');
149 $ctx->{template_paths} = [ reverse @template_paths ];
151 my %locales = $r->dir_config->get('OILSWebLocale');
152 load_locale_handlers($ctx, %locales);
155 $cgi->cookie(OILS_HTTP_COOKIE_LOCALE) ||
156 parse_accept_lang($r->headers_in->get('Accept-Language')) || 'en_us';
158 my $mprefix = $ctx->{media_prefix};
159 if($mprefix and $mprefix !~ /^http/ and $mprefix !~ /^\//) {
160 # if a hostname is provided /w no protocol, match the protocol to the current page
161 $ctx->{media_prefix} = ($cgi->https) ? "https://$mprefix" : "http://$mprefix";
167 # turn Accept-Language into sometihng EG can understand
168 # TODO: try all langs, not just the first
169 sub parse_accept_lang {
171 return undef unless $al;
172 my ($locale) = split(/,/, $al);
173 ($locale) = split(/;/, $locale);
174 return undef unless $locale;
179 # Given a URI, finds the configured template and any extra page
180 # arguments (trailing path info). Any extra data is returned
181 # as page arguments, in the form of an array, one item per
182 # /-separated URI component
188 $path =~ s/$base\/?//og;
191 my $as_xml = $r->dir_config('OILSWebForceValidXML');
192 my $ext = $r->dir_config('OILSWebDefaultTemplateExtension');
194 my @parts = split('/', $path);
195 my $localpath = $path;
198 last unless $localpath;
199 for my $tpath (@{$ctx->{template_paths}}) {
200 my $fpath = "$tpath/$localpath.$ext";
201 $r->log->debug("egweb: looking at possible template $fpath");
203 $template = "$localpath.$ext";
208 push(@args, pop @parts);
209 $localpath = join('/', @parts);
212 $page_args = [@args];
214 # no template configured or found
216 $r->log->debug("egweb: No template configured for path $path");
220 $r->log->debug("egweb: template = $template : page args = @$page_args");
221 return ($template, $page_args, $as_xml);
224 # Create an I18N sub-module for each supported locale
225 # Each module creates its own MakeText lexicon by parsing .po/.mo files
226 sub load_locale_handlers {
230 my @locale_tags = sort { length($a) <=> length($b) } keys %locales;
232 # If no locales are defined, fall back to en_us so that at least 1 handler exists
233 push(@locale_tags, 'en_us') unless @registered_locales or @locale_tags;
235 for my $idx (0..$#locale_tags) {
237 my $tag = $locale_tags[$idx];
238 next if grep { $_ eq $tag } @registered_locales;
243 # find the parent locale if possible. It will be
244 # longest left-anchored substring of the current tag
245 while( --$sub_idx >= 0 ) {
246 my $ptag = $locale_tags[$sub_idx];
247 if( substr($tag, 0, length($ptag)) eq $ptag ) {
248 $parent_tag = "::$ptag";
253 my $messages = $locales{$tag} || '';
255 # TODO Can we do this without eval?
256 my $eval = <<" EVAL";
257 package OpenILS::WWW::EGWeb::I18N::$tag;
258 use base 'OpenILS::WWW::EGWeb::I18N$parent_tag';
260 use Locale::Maketext::Lexicon::Gettext;
261 if(open F, '$messages') {
262 our %Lexicon = (%Lexicon, %{ Locale::Maketext::Lexicon::Gettext->parse(<F>) });
265 warn "EGWeb: unable to open messages file: $messages";
274 push(@registered_locales, $tag);
280 # base class for all supported locales
281 package OpenILS::WWW::EGWeb::I18N;
282 use base 'Locale::Maketext';
283 our %Lexicon = (_AUTO => 1);