Commit | Line | Data |
---|---|---|
589b789c TC |
1 | package BSE::Template; |
2 | use strict; | |
589b789c | 3 | use Squirrel::Template; |
3f9c8a96 | 4 | use Carp qw(confess cluck); |
02d87eea | 5 | use Config (); |
589b789c | 6 | |
599fe373 | 7 | our $VERSION = "1.007"; |
cb7fd78d | 8 | |
ebc63b18 TC |
9 | sub 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 |
51 | sub _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 | ||
60 | sub 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 | ||
77 | sub 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 |
85 | sub charset { |
86 | my ($class, $cfg) = @_; | |
87 | ||
88 | return $cfg->charset; | |
89 | } | |
90 | ||
91 | sub utf8 { | |
92 | my ($class, $cfg) = @_; | |
93 | ||
94 | return $cfg->utf8; | |
95 | } | |
96 | ||
ca9aa2bf TC |
97 | sub 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 |
105 | sub get_type { |
106 | my ($class, $cfg, $template) = @_; | |
107 | ||
108 | return $cfg->entry("template types", $template) | |
109 | || $class->html_type($cfg); | |
110 | } | |
111 | ||
ca9aa2bf | 112 | sub 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 | ||
118 | sub show_replaced { | |
119 | my ($class, $source, $cfg, $acts) = @_; | |
120 | ||
121 | $class->show_literal($class->replace($source, $cfg, $acts), $cfg); | |
122 | } | |
123 | ||
124 | sub 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 | 133 | sub 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 | ||
142 | sub 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 | ||
167 | sub 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 | ||
180 | sub 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 |
193 | sub 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 | ||
212 | sub 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 | ||
227 | sub 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 |
245 | sub output_result { |
246 | my ($class, $req, $result) = @_; | |
247 | ||
b3c5188f TC |
248 | $class->output_resultc($req->cfg, $result); |
249 | } | |
250 | ||
251 | sub 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 | 342 | 1; |