]>
Commit | Line | Data |
---|---|---|
2990391c | 1 | package BSE::Regen; |
1953ce1c TC |
2 | use strict; |
3 | use vars qw(@ISA @EXPORT_OK); | |
4 | require Exporter; | |
5 | @ISA = qw(Exporter); | |
bd7cf9d4 | 6 | @EXPORT_OK = qw(generate_article generate_all generate_button |
f00a461d | 7 | regen_and_refresh generate_extras pregenerate_list generate_one_extra generate_base content_one_extra response_one_extra); |
5abe2da5 | 8 | use Constants qw($GENERATE_BUTTON $SHOPID $AUTO_GENERATE); |
aefcabcb | 9 | use Carp qw(confess); |
41f10371 | 10 | use BSE::WebUtil qw(refresh_to_admin); |
c4f0aab9 | 11 | use BSE::Util::HTML; |
6c8fac02 | 12 | use BSE::DummyArticle; |
1953ce1c | 13 | |
e0ed81d7 | 14 | our $VERSION = "1.015"; |
cb7fd78d | 15 | |
37ca1e13 TC |
16 | # returns non-zero if the Regenerate button should work |
17 | sub generate_button { | |
18 | if ($GENERATE_BUTTON) { | |
19 | if (my $ref = ref $GENERATE_BUTTON) { | |
20 | if ($ref eq 'CODE') { | |
21 | return $GENERATE_BUTTON->(); | |
22 | } | |
23 | else { | |
24 | # assumed to be an object | |
25 | return $GENERATE_BUTTON->want_button(); | |
26 | } | |
27 | } | |
28 | else { | |
29 | return 1; | |
30 | } | |
31 | } | |
32 | return 0; | |
33 | } | |
34 | ||
1953ce1c TC |
35 | # regenerate an individual article |
36 | sub generate_low { | |
531fb3bc | 37 | my ($articles, $article, $cfg) = @_; |
1953ce1c | 38 | |
28c82e2d | 39 | $cfg ||= BSE::Cfg->single; |
efcc5a30 TC |
40 | |
41 | my $outname; | |
42 | if ($article->is_dynamic) { | |
43 | my $debug_jit = $cfg->entry('debug', 'jit_dynamic_regen'); | |
16901a2a | 44 | $outname = $article->cached_filename($cfg); |
efcc5a30 TC |
45 | if ($article->{flags} !~ /R/ && |
46 | $cfg->entry('basic', 'jit_dynamic_pregen')) { | |
47 | $debug_jit and print STDERR "JIT: $article->{id} - deleting $outname\n"; | |
48 | # just delete the file, page.pl will make it if needed | |
49 | unlink $outname; | |
50 | return; | |
51 | } | |
52 | } | |
53 | else { | |
8f84f3f1 TC |
54 | $outname = $article->link_to_filename($cfg) |
55 | or return; # no output for this article | |
efcc5a30 TC |
56 | } |
57 | ||
538b7aee TC |
58 | if ($article->flags =~ /P/ && $article->{parentid} != -1) { |
59 | # link to parent, remove the file | |
60 | unlink $outname; | |
61 | return; | |
62 | } | |
63 | ||
52fe5612 | 64 | unless ($article->should_generate) { |
971b8203 TC |
65 | # don't generate unlisted pages and remove any old content |
66 | unlink $outname; | |
67 | return; | |
68 | } | |
69 | ||
1953ce1c | 70 | my $genname = $article->{generator}; |
1953ce1c TC |
71 | eval "use $genname"; |
72 | $@ && die $@; | |
d09682dd | 73 | my $gen = $genname->new(articles=>$articles, cfg=>$cfg, top=>$article); |
1953ce1c | 74 | |
d09682dd | 75 | my $content = $gen->generate($article, $articles); |
1953ce1c TC |
76 | my $tempname = $outname . ".work"; |
77 | unlink $tempname; | |
3f9c8a96 | 78 | _write_text($tempname, $content, $cfg); |
bd7cf9d4 | 79 | unlink $outname; |
1953ce1c TC |
80 | rename($tempname, $outname) |
81 | or die "Cannot rename $tempname to $outname: $!"; | |
82 | } | |
83 | ||
84 | sub generate_article { | |
531fb3bc | 85 | my ($articles, $article, $cfg) = @_; |
1953ce1c TC |
86 | |
87 | while ($article) { | |
531fb3bc | 88 | generate_low($articles, $article, $cfg) |
1953ce1c TC |
89 | if $article->{link} && $article->{template}; |
90 | ||
91 | if ($article->{parentid} != -1) { | |
92 | $article = $articles->getByPkey($article->{parentid}); | |
93 | } | |
94 | else { | |
95 | undef $article; | |
96 | } | |
97 | } | |
98 | } | |
99 | ||
f00a461d TC |
100 | sub _cfg_presets { |
101 | my ($cfg, $article, $type) = @_; | |
aefcabcb | 102 | |
f00a461d TC |
103 | my $section = "$type settings"; |
104 | ||
e0ed81d7 AO |
105 | require BSE::TB::Articles; |
106 | for my $field (BSE::TB::Article->columns) { | |
f00a461d TC |
107 | if ($cfg->entry($section, $field)) { |
108 | $article->{$field} = $cfg->entryVar($section, $field); | |
109 | } | |
110 | } | |
111 | } | |
112 | ||
113 | sub _search_presets { | |
114 | my ($cfg) = @_; | |
e70f7e4b | 115 | |
e70f7e4b TC |
116 | # build a dummy article |
117 | use Constants qw($SEARCH_TITLE $SEARCH_TITLE_IMAGE $CGI_URI); | |
e0ed81d7 AO |
118 | require BSE::TB::Articles; |
119 | my %article = map { $_, '' } BSE::TB::Article->columns; | |
507a9ac3 | 120 | @article{qw(id parentid title titleImage displayOrder link level listed force_dynamic)} = |
b1f66a92 | 121 | (-4, -1, $SEARCH_TITLE, $SEARCH_TITLE_IMAGE, 0, $CGI_URI."/search.pl", 0, 1, 1); |
e70f7e4b | 122 | |
2417a52b | 123 | $article{link} = $cfg->entryErr('site', 'url') . $article{link}; |
d09682dd | 124 | |
f00a461d TC |
125 | _cfg_presets($cfg, \%article, "search"); |
126 | ||
3b263562 | 127 | return _dummy_article(\%article); |
1953ce1c TC |
128 | } |
129 | ||
f00a461d TC |
130 | sub _shop_presets { |
131 | my ($cfg) = @_; | |
132 | ||
e0ed81d7 AO |
133 | require BSE::TB::Articles; |
134 | my $shop_base = BSE::TB::Articles->getByPkey($SHOPID); | |
6866b8dd TC |
135 | my $shop = { map { $_ => $shop_base->{$_} } $shop_base->columns }; |
136 | $shop->{link} =~ /^\w+:/ | |
137 | or $shop->{link} = $cfg->entryErr('site', 'url') . $shop->{link}; | |
b1f66a92 | 138 | $shop->{id} = -3; # some random negative number |
f00a461d TC |
139 | |
140 | _cfg_presets($cfg, $shop, "shop"); | |
141 | ||
3b263562 | 142 | return _dummy_article($shop); |
f00a461d TC |
143 | } |
144 | ||
145 | sub _extras_presets { | |
146 | my ($cfg, $presets) = @_; | |
147 | ||
e0ed81d7 AO |
148 | require BSE::TB::Articles; |
149 | my %article = map { $_, '' } BSE::TB::Article->columns; | |
f00a461d TC |
150 | $article{displayOrder} = 1; |
151 | $article{id} = -5; | |
152 | $article{parentid} = -1; | |
153 | $article{link} = $cfg->entryErr('site', 'url'); | |
154 | _cfg_presets($cfg, \%article, $presets); | |
155 | ||
3b263562 | 156 | return _dummy_article(\%article); |
f00a461d TC |
157 | } |
158 | ||
159 | my %builtin_extras = | |
160 | ( | |
161 | search => [ "search" ], | |
162 | shop => | |
163 | [ | |
c24a8a7b | 164 | 'cart', 'checkoutnew', 'checkoutfinal', |
f00a461d TC |
165 | 'checkoutpay', |
166 | ], | |
167 | ); | |
168 | ||
169 | my %abs_urls = | |
170 | ( | |
171 | shop => 1, | |
172 | search => 0, | |
173 | ); | |
174 | ||
175 | my %builtin_lookup; | |
176 | ||
177 | { | |
178 | for my $type (keys %builtin_extras) { | |
179 | for my $name (@{$builtin_extras{$type}}) { | |
180 | $builtin_lookup{$name} = $type; | |
181 | } | |
182 | } | |
183 | } | |
184 | ||
185 | sub _extras_cfg { | |
186 | my ($cfg, $extra) = @_; | |
187 | ||
188 | my %result; | |
189 | if ($builtin_extras{$extra->{set}}) { | |
190 | $result{abs_urls} = $abs_urls{$extra->{set}}; | |
191 | } | |
192 | else { | |
193 | $result{abs_urls} = $cfg->entry("$extra->{type} settings", "abs_urls", 0); | |
194 | } | |
195 | ||
196 | return \%result; | |
197 | } | |
198 | ||
199 | sub pregenerate_list { | |
200 | my ($cfg) = @_; | |
201 | ||
202 | my $template_dir = $cfg->entryVar('paths', 'templates'); | |
203 | ||
204 | # this will change to a directory that can be safely blown away | |
205 | my $pregen_path = $template_dir; | |
206 | ||
207 | my @result = | |
208 | ( | |
209 | ( | |
210 | map | |
211 | +{ | |
212 | name => "$_.tmpl", | |
213 | base => $_ . "_base.tmpl", | |
214 | type => $builtin_lookup{$_}, | |
215 | set => $builtin_lookup{$_}, | |
216 | sort => 0, | |
217 | outpath => $pregen_path, | |
218 | abs_urls => $abs_urls{$builtin_lookup{$_}}, | |
219 | dynamic => 1, | |
220 | }, keys %builtin_lookup | |
221 | ), | |
222 | ); | |
223 | ||
224 | # cfg pregen | |
225 | my %pregen = $cfg->entries('pregenerate'); | |
226 | for my $out (keys %pregen) { | |
227 | my ($type, $input) = split ',', $pregen{$out}, 2; | |
228 | push @result, | |
229 | +{ | |
230 | name => $out, | |
231 | base => $input, | |
232 | type => $type, | |
233 | set => "pregen", | |
234 | sort => 1, | |
235 | outpath => $pregen_path, | |
236 | dynamic => 1, | |
237 | }; | |
238 | } | |
239 | ||
240 | # extras file | |
241 | if (open my $extras, "$template_dir/extras.txt") { | |
242 | while (<$extras>) { | |
243 | chomp; | |
244 | next if /^\s*#/; | |
245 | if (/^(\S+)\s+(\S+)/) { | |
246 | push @result, | |
247 | { | |
248 | name => $2, | |
249 | base => $1, | |
250 | type => "extras", | |
251 | set => "extras", | |
252 | sort => 2, | |
253 | outpath => $cfg->content_base_path, | |
254 | dynamic => 0, | |
255 | }; | |
256 | } | |
257 | } | |
258 | close $extras; | |
259 | } | |
260 | ||
261 | return sort { | |
262 | $a->{sort} <=> $b->{sort} | |
263 | || $a->{set} cmp $b->{set} | |
264 | || $a->{type} cmp $b->{type} | |
265 | || lc $a->{name} cmp lc $b->{name} | |
266 | } @result; | |
267 | } | |
268 | ||
269 | sub _make_extra_art { | |
270 | my ($cfg, $extra) = @_; | |
271 | ||
272 | if ($extra->{set} eq "shop") { | |
273 | return _shop_presets($cfg); | |
274 | } | |
275 | elsif ($extra->{set} eq "search") { | |
276 | return _search_presets($cfg); | |
277 | } | |
278 | elsif ($extra->{set} eq "pregen" | |
279 | || $extra->{set} eq "extras") { | |
280 | return _extras_presets($cfg, $extra->{type}); | |
281 | } | |
282 | else { | |
283 | confess "Unknown extras set $extra->{set}"; | |
284 | } | |
285 | } | |
286 | ||
287 | sub _make_extra_gen { | |
288 | my ($cfg, $extra) = @_; | |
289 | ||
290 | my $article = _make_extra_art($cfg, $extra); | |
39e87dbd | 291 | require BSE::Generate::Article; |
f00a461d TC |
292 | my %opts = |
293 | ( | |
294 | cfg => $cfg, | |
295 | top => $article, | |
296 | ); | |
89ffbadb | 297 | if ($extra->{dynamic}) { |
f00a461d TC |
298 | $opts{force_dynamic} = 1; |
299 | } | |
39e87dbd AO |
300 | require BSE::Generate::Article; |
301 | my $gen = BSE::Generate::Article->new(%opts); | |
f00a461d TC |
302 | |
303 | return ($article, $gen); | |
304 | } | |
305 | ||
306 | sub _common_one_extra { | |
307 | my ($articles, $extra, $cfg) = @_; | |
308 | ||
309 | my ($article, $gen) = _make_extra_gen($cfg, $extra); | |
310 | my %acts; | |
311 | %acts = $gen->baseActs($articles, \%acts, $article); | |
312 | if (_extras_cfg($cfg, $extra)->{abs_urls}) { | |
1953ce1c TC |
313 | my $oldurl = $acts{url}; |
314 | $acts{url} = | |
315 | sub { | |
316 | my $value = $oldurl->(@_); | |
54c97cf6 | 317 | $value =~ /^<:/ and return $value; |
1953ce1c TC |
318 | unless ($value =~ /^\w+:/) { |
319 | # put in the base site url | |
b19047a6 | 320 | $value = $cfg->entryErr('site', 'url').$value; |
1953ce1c TC |
321 | } |
322 | return $value; | |
323 | }; | |
1953ce1c | 324 | } |
f00a461d | 325 | |
599fe373 TC |
326 | my $content = BSE::Template->get_page($extra->{base}, $cfg, \%acts, |
327 | undef, undef, $gen->variables); | |
328 | ||
f00a461d | 329 | return wantarray ? ( $content, $article ) : $content; |
1953ce1c TC |
330 | } |
331 | ||
f00a461d TC |
332 | sub response_one_extra { |
333 | my ($articles, $extra) = @_; | |
1953ce1c | 334 | |
f00a461d TC |
335 | my $cfg = BSE::Cfg->single; |
336 | my $content = _common_one_extra($articles, $extra, $cfg); | |
edc5d096 | 337 | |
f00a461d TC |
338 | return BSE::Template->make_response($content, BSE::Template->get_type($cfg, $extra->{template})); |
339 | } | |
edc5d096 | 340 | |
f00a461d TC |
341 | sub content_one_extra { |
342 | my ($articles, $extra) = @_; | |
343 | ||
344 | my $cfg = BSE::Cfg->single; | |
345 | return _common_one_extra($articles, $extra, $cfg); | |
346 | } | |
347 | ||
348 | sub generate_one_extra { | |
349 | my ($articles, $extra) = @_; | |
350 | ||
351 | my $cfg = BSE::Cfg->single; | |
352 | my $content = _common_one_extra($articles, $extra, $cfg); | |
353 | my $outname = $extra->{outpath} . "/". $extra->{name}; | |
354 | my $workname = $outname . ".work"; | |
355 | _write_text($workname, $content, $cfg); | |
356 | unlink $outname; | |
357 | rename $workname, $outname | |
358 | or die "Cannot rename $workname to $outname: $!"; | |
359 | } | |
360 | ||
361 | sub generate_base { | |
362 | my %opts = @_; | |
363 | ||
364 | my $cfg = delete $opts{cfg} || BSE::Cfg->single; | |
365 | ||
e0ed81d7 | 366 | my $articles = delete $opts{articles} || "BSE::TB::Articles"; |
f00a461d TC |
367 | my $extras = delete $opts{extras} || [ pregenerate_list($cfg) ]; |
368 | ||
369 | my $progress = delete $opts{progress} || sub {}; | |
370 | ||
371 | my @extras = sort | |
372 | { | |
373 | $a->{sort} <=> $b->{sort} | |
374 | || $a->{type} cmp $b->{type} | |
375 | || lc $a->{name} cmp lc $b->{name} | |
376 | } @$extras; | |
377 | ||
378 | my $count = @extras; | |
379 | $progress->({ type => "extras", count => $count, info => "count" }, "$count base pages"); | |
380 | my $set = ""; | |
381 | my $type = ""; | |
382 | my ($gen, $article); | |
383 | my %acts; | |
e4793ddd | 384 | for my $extra (@extras) { |
f00a461d TC |
385 | if ($extra->{set} ne $set || $extra->{type} ne $type) { |
386 | ($article, $gen) = _make_extra_gen($cfg, $extra); | |
387 | %acts = $gen->baseActs($articles, \%acts, $article); | |
388 | if (_extras_cfg($cfg, $extra)->{abs_urls}) { | |
389 | my $oldurl = $acts{url}; | |
390 | $acts{url} = | |
391 | sub { | |
392 | my $value = $oldurl->(@_); | |
393 | $value =~ /^<:/ and return $value; | |
394 | unless ($value =~ /^\w+:/) { | |
395 | # put in the base site url | |
396 | $value = $cfg->entryErr('site', 'url').$value; | |
397 | } | |
398 | return $value; | |
399 | }; | |
edc5d096 | 400 | } |
f00a461d | 401 | $progress->($extra, "Generating $extra->{name}"); |
599fe373 TC |
402 | my $content = BSE::Template->get_page($extra->{base}, $cfg, \%acts, |
403 | undef, undef, $gen->variables); | |
f00a461d TC |
404 | my $outname = $extra->{outpath} . "/". $extra->{name}; |
405 | my $workname = $outname . ".work"; | |
406 | _write_text($workname, $content, $cfg); | |
407 | unlink $outname; | |
408 | rename $workname, $outname | |
409 | or die "Cannot rename $workname to $outname: $!"; | |
edc5d096 TC |
410 | } |
411 | } | |
1953ce1c TC |
412 | } |
413 | ||
414 | sub generate_all { | |
531fb3bc | 415 | my ($articles, $cfg, $callback) = @_; |
1953ce1c | 416 | |
531fb3bc TC |
417 | my @articleids = $articles->allids; |
418 | my $pc = 0; | |
419 | $callback->("Generating articles (".scalar(@articleids)." to do)") | |
420 | if $callback; | |
421 | my $index; | |
422 | my $total = 0; | |
531fb3bc | 423 | Squirrel::Table->caching(1); |
c5493735 | 424 | my $allstart = time; |
531fb3bc | 425 | for my $articleid (@articleids) { |
531fb3bc TC |
426 | my $article = $articles->getByPkey($articleid); |
427 | ++$index; | |
531fb3bc | 428 | my $newpc = $index / @articleids * 100; |
c5493735 | 429 | my $now = time; |
531fb3bc | 430 | if ($callback && $newpc >= $pc + 1 || abs($newpc-100) < 0.01) { |
c5493735 | 431 | $callback->(sprintf("%5d: %.1f%% done - elapsed: %.1f", $articleid, $newpc, $now - $allstart)) if $callback; |
531fb3bc TC |
432 | $pc = int $newpc; |
433 | } | |
7797165e TC |
434 | if ($article->{link} && $article->{template}) { |
435 | #$callback->("Article $articleid"); | |
436 | generate_low($articles, $article, $cfg); | |
437 | } | |
1953ce1c | 438 | } |
1953ce1c | 439 | |
f00a461d TC |
440 | my $last_section = ""; |
441 | my $progress = $callback ? sub { | |
442 | my $data = shift; | |
443 | if (!$data->{count} && $data->{set} ne $last_section) { | |
444 | $callback->("Regenerating $data->{set} pages"); | |
445 | $last_section = $data->{set}; | |
bd7cf9d4 | 446 | } |
f00a461d TC |
447 | $callback->(" @_") |
448 | } : undef; | |
449 | generate_base(cfg => $cfg, articles => $articles, progress => $progress); | |
bd7cf9d4 | 450 | |
f00a461d | 451 | $callback->("Total of ".(time()-$allstart)." seconds") if $callback; |
bd7cf9d4 TC |
452 | } |
453 | ||
3f9c8a96 TC |
454 | sub _write_text { |
455 | my ($filename, $data, $cfg) = @_; | |
456 | ||
457 | open my $fh, ">", $filename | |
458 | or die "Cannot create $filename: $!"; | |
459 | if ($cfg->utf8) { | |
460 | my $charset = $cfg->charset; | |
461 | binmode $fh, ":encoding($charset)"; | |
462 | } | |
463 | print $fh $data | |
464 | or die "Cannot write $filename: $!"; | |
465 | close $fh | |
466 | or die "Cannot close $filename: $!"; | |
467 | } | |
468 | ||
3b263562 TC |
469 | sub _dummy_article { |
470 | my ($data) = @_; | |
471 | ||
6c8fac02 | 472 | return bless $data, "BSE::DummyArticle"; |
139d9a14 TC |
473 | } |
474 | ||
1953ce1c | 475 | 1; |