allow use of the new template system from static pages
[bse.git] / site / cgi-bin / modules / BSE / Template.pm
CommitLineData
589b789c
TC
1package BSE::Template;
2use strict;
589b789c 3use Squirrel::Template;
3f9c8a96 4use Carp qw(confess cluck);
02d87eea 5use Config ();
589b789c 6
599fe373 7our $VERSION = "1.007";
cb7fd78d 8
ebc63b18
TC
9sub templater {
10 my ($class, $cfg, $rsets) = @_;
589b789c 11
4d764c34 12 my @conf_dirs = $class->template_dirs($cfg);
4d764c34
TC
13 my @dirs;
14 if ($rsets && @$rsets) {
15 for my $set (@$rsets) {
16 push @dirs, map "$_/$set", @conf_dirs;
17 }
18 push @dirs, @conf_dirs;
19 }
20 else {
21 @dirs = @conf_dirs;
22 }
ebc63b18 23
66cb0448
TC
24 my %opts =
25 (
26 template_dir => \@dirs,
27 utf8 => $cfg->utf8,
28 charset => $cfg->charset,
f0df8548
TC
29 formats =>
30 {
31 html => sub {
32 require BSE::Util::HTML;
33 return BSE::Util::HTML::escape_html($_[0]);
34 },
35 uri => sub {
36 require BSE::Util::HTML;
37 return BSE::Util::HTML::escape_uri($_[0]);
38 },
39 },
66cb0448 40 );
58f0ca94
TC
41 if ($cfg->entry("basic", "cache_templates")) {
42 require BSE::Cache;
43 $opts{cache} = BSE::Cache->load($cfg);
44 }
45
755fd5f3
TC
46 $opts{preload} = $cfg->entry("basic", "preload_template");
47
66cb0448 48 return Squirrel::Template->new(%opts);
ebc63b18
TC
49}
50
eb862ebb
TC
51sub _get_filename {
52 my ($class, $cfg, $template) = @_;
ebc63b18
TC
53
54 my $file = $cfg->entry('templates', $template) || $template;
55 $file =~ /\.\w+$/ or $file .= ".tmpl";
eb862ebb
TC
56
57 return $file;
58}
59
60sub get_page {
f0df8548 61 my ($class, $template, $cfg, $acts, $base_template, $rsets, $vars) = @_;
eb862ebb
TC
62
63 my $file = $class->_get_filename($cfg, $template);
ebc63b18 64 my $obj = $class->templater($cfg, $rsets);
589b789c 65
2a295ea9
TC
66 my $out;
67 if ($base_template) {
b9c373e6
TC
68 unless ($class->find_source($template, $cfg)) {
69 $template = $base_template;
eb862ebb 70 $file = $class->_get_filename($cfg, $template);
2a295ea9
TC
71 }
72 }
2a295ea9 73
f0df8548 74 return $obj->show_page(undef, $file, $acts, undef, undef, $vars);
aefcabcb
TC
75}
76
77sub replace {
599fe373 78 my ($class, $source, $cfg, $acts, $vars) = @_;
aefcabcb 79
ebc63b18 80 my $obj = $class->templater($cfg);
aefcabcb 81
599fe373 82 $obj->replace_template($source, $acts, undef, undef, $vars);
589b789c
TC
83}
84
3f9c8a96
TC
85sub charset {
86 my ($class, $cfg) = @_;
87
88 return $cfg->charset;
89}
90
91sub utf8 {
92 my ($class, $cfg) = @_;
93
94 return $cfg->utf8;
95}
96
ca9aa2bf
TC
97sub html_type {
98 my ($class, $cfg) = @_;
589b789c
TC
99
100 my $type = "text/html";
3f9c8a96 101 my $charset = $class->charset($cfg);
ca9aa2bf
TC
102 return $type . "; charset=$charset";
103}
104
ad48b8d4
TC
105sub get_type {
106 my ($class, $cfg, $template) = @_;
107
108 return $cfg->entry("template types", $template)
109 || $class->html_type($cfg);
110}
111
ca9aa2bf 112sub show_page {
2a295ea9 113 my ($class, $template, $cfg, $acts, $base_template) = @_;
ca9aa2bf 114
2a295ea9 115 $class->show_literal($class->get_page($template, $cfg, $acts, $base_template), $cfg);
aefcabcb
TC
116}
117
118sub show_replaced {
119 my ($class, $source, $cfg, $acts) = @_;
120
121 $class->show_literal($class->replace($source, $cfg, $acts), $cfg);
122}
123
124sub show_literal {
125 my ($class, $text, $cfg) = @_;
126
ca9aa2bf 127 my $type = $class->html_type($cfg);
589b789c
TC
128
129 print "Content-Type: $type\n\n";
aefcabcb 130 print $text;
589b789c
TC
131}
132
ca9aa2bf 133sub get_response {
f0df8548 134 my ($class, $template, $cfg, $acts, $base_template, $rsets, $vars) = @_;
ca9aa2bf 135
3f9c8a96 136 my $content = $class->get_page($template, $cfg, $acts,
f0df8548 137 $base_template, $rsets, $vars);
f00a461d
TC
138
139 return $class->make_response($content, $class->get_type($cfg, $template));
140}
141
142sub make_response {
143 my ($class, $content, $type) = @_;
144
145 if ($type =~ /\bcharset=([\w-]+)/) {
146 my $charset = $1;
3f9c8a96
TC
147
148 require Encode;
149 Encode->import();
f00a461d 150 my $cfg = BSE::Cfg->single;
3f9c8a96
TC
151 my $check = $cfg->entry("utf8", "check", Encode::FB_DEFAULT());
152 $check = oct($check) if $check =~ /^0/;
153
154 $content = Encode::encode($charset, $content, $check);
155 }
156
ca9aa2bf
TC
157 my $result =
158 {
f00a461d 159 type => $type,
3f9c8a96 160 content => $content,
ca9aa2bf
TC
161 };
162 push @{$result->{headers}}, "Content-Length: ".length($result->{content});
163
f00a461d 164 return $result;
ca9aa2bf
TC
165}
166
167sub get_refresh {
168 my ($class, $url, $cfg) = @_;
169
2076966c
TC
170 return
171 {
172 content => '',
173 headers => [
174 "Location: $url",
7fdea45c 175 "Status: 303"
2076966c
TC
176 ],
177 };
80f59f40
TC
178}
179
180sub get_moved {
181 my ($class, $url, $cfg) = @_;
2076966c 182
ca9aa2bf
TC
183 return
184 {
80f59f40
TC
185 content => '',
186 headers => [
187 "Location: $url",
188 "Status: 301"
189 ],
ca9aa2bf
TC
190 };
191}
192
aefcabcb
TC
193sub template_dirs {
194 my ($class, $cfg) = @_;
195
196 ref($cfg) eq 'BSE::Cfg'
220c179a 197 or confess "Invalid cfg $cfg supplied\n";
aefcabcb 198
02d87eea
TC
199 my $path_sep = $Config::Config{path_sep};
200
aefcabcb 201 my $base = $cfg->entryVar('paths', 'templates');
02d87eea 202 my @dirs = split /\Q$path_sep/, $base;
aefcabcb 203 my $local = $cfg->entry('paths', 'local_templates');
02d87eea
TC
204 if ($local) {
205 unshift @dirs, split /\Q$path_sep/,
206 $cfg->entryVar('paths', 'local_templates');
207 }
aefcabcb
TC
208
209 @dirs;
210}
211
212sub find_source {
213 my ($class, $template, $cfg) = @_;
214
215 my @dirs = $class->template_dirs($cfg);
216
217 my $file = $cfg->entry('templates', $template) || $template;
218 $file =~ /\.\w+$/ or $file .= ".tmpl";
219
220 for my $dir (@dirs) {
221 return "$dir/$file" if -f "$dir/$file";
222 }
223
224 return;
225}
226
227sub get_source {
228 my ($class, $template, $cfg) = @_;
229
230 my $path = $class->find_source($template, $cfg)
231 or confess "Cannot find template $template";
3f9c8a96 232 open my $source, "< $path"
aefcabcb 233 or confess "Cannot open template $path: $!";
3f9c8a96
TC
234 binmode $source;
235 if ($cfg->utf8) {
236 my $charset = $cfg->charset;
237 binmode $source, ":encoding($charset)";
238 }
239 my $html = do { local $/; <$source> };
240 close $source;
aefcabcb
TC
241
242 $html;
243}
244
daee3409
TC
245sub output_result {
246 my ($class, $req, $result) = @_;
247
b3c5188f
TC
248 $class->output_resultc($req->cfg, $result);
249}
250
251sub output_resultc {
252 my ($class, $cfg, $result) = @_;
253
4d764c34
TC
254 $result
255 or return;
256
daee3409
TC
257 select STDOUT;
258 $| = 1;
3b3b82d3
TC
259 push @{$result->{headers}}, "Content-Type: $result->{type}"
260 if $result->{type};
b3c5188f 261 my $add_cache_control = $cfg->entry('basic', 'no_cache_dynamic', 1);
3f36e485
TC
262 if (defined $result->{no_cache_dynamic}) {
263 $add_cache_control = $result->{no_cache_dynamic};
74b21f6d
TC
264 }
265 if ($add_cache_control) {
3f36e485
TC
266 for my $header (@{$result->{headers}}) {
267 if ($header =~ /^cache-control:/i) {
268 $add_cache_control = 0;
269 last;
270 }
271 }
272 if ($add_cache_control) {
273 push @{$result->{headers}}, "Cache-Control: no-cache";
274 }
74b21f6d 275 }
b3c5188f
TC
276
277 if ($result->{content_filename}) {
278 # at some point, if we have a FEP like perlbal of nginx we might
279 # get it to serve the file instead
280 if (open my $fh, "<", $result->{content_filename}) {
281 binmode $fh;
282 $result->{content_fh} = $fh;
283 }
284 else {
285 print STDERR "$ENV{SCRIPT_NAME}: ** cannot open file $result->{content_filename}: $!\n";
286 $result->{content} = "* Internal error";
287 }
288 }
289
290 if (!grep /^content-length:/i, @{$result->{headers}}) {
32696f84
TC
291 my $length;
292 if (defined $result->{content}) {
293 $length = length $result->{content};
294 }
b3c5188f 295 if (defined $result->{content_fh}) {
32696f84 296 # this may need to change if we support byte ranges
b3c5188f 297 $length += -s $result->{content_fh};
32696f84 298 }
b3c5188f 299
32696f84
TC
300 if (defined $length) {
301 push @{$result->{headers}}, "Content-Length: $length";
302 }
303 }
daee3409
TC
304 if (exists $ENV{GATEWAY_INTERFACE}
305 && $ENV{GATEWAY_INTERFACE} =~ /^CGI-Perl\//) {
f0543260 306 require Apache;
daee3409 307 my $r = Apache->request or die;
8f84f3f1 308 $r->send_cgi_header(join("\n", @{$result->{headers}})."\n\n");
daee3409
TC
309 }
310 else {
311 print "$_\n" for @{$result->{headers}};
312 print "\n";
313 }
32696f84 314 if (defined $result->{content}) {
3f9c8a96
TC
315 if ($result->{content} =~ /([^\x00-\xff])/) {
316 cluck "Wide character in content (\\x{", sprintf("%X", ord $1), "})";
317 }
32696f84
TC
318 print $result->{content};
319 }
320 elsif ($result->{content_fh}) {
321 # in the future this could be updated to support byte ranges
b3c5188f 322 local $/ = \16384;
32696f84
TC
323 my $fh = $result->{content_fh};
324 binmode $fh;
325 while (my $data = <$fh>) {
326 print $data;
327 }
328 }
329 else {
330 print STDERR "$ENV{SCRIPT_NAME}: ** No content supplied\n";
331 print "** Internal error\n";
332 }
8a3b8db8
TC
333
334 if ($result->{content}
335 && $result->{type} =~ m(text/html|application/xhtml\+xml)
336 && $cfg->entry("html", "validate", 0)) {
337 require BSE::Util::ValidateHTML;
338 BSE::Util::ValidateHTML->validate($cfg, $result);
339 }
daee3409
TC
340}
341
589b789c 3421;