Commit | Line | Data |
---|---|---|
41358dcc TC |
1 | package Generate; |
2 | use strict; | |
3 | use Articles; | |
aefcabcb | 4 | use Constants qw($IMAGEDIR $LOCAL_FORMAT $BODY_EMBED |
d38f3b10 | 5 | $EMBED_MAX_DEPTH $HAVE_HTML_PARSER); |
7928764a | 6 | use DevHelp::Tags; |
3f9c8a96 | 7 | use BSE::Util::HTML; |
c76e86ea | 8 | use BSE::Util::Tags qw(tag_article); |
00dd8d82 | 9 | use BSE::CfgInfo qw(custom_class); |
b873a8fa | 10 | use BSE::Util::Iterate; |
599fe373 | 11 | use BSE::TB::Site; |
b864cc90 | 12 | use base 'BSE::ThumbLow'; |
47c75494 | 13 | use base 'BSE::TagFormats'; |
41358dcc | 14 | |
599fe373 | 15 | our $VERSION = "1.007"; |
cb7fd78d | 16 | |
41358dcc TC |
17 | my $excerptSize = 300; |
18 | ||
19 | sub new { | |
20 | my ($class, %opts) = @_; | |
c5286ebe TC |
21 | unless ($opts{cfg}) { |
22 | require Carp; | |
23 | Carp->import('confess'); | |
24 | confess("cfg missing on generator->new call"); | |
25 | } | |
745d2b57 TC |
26 | $opts{maxdepth} = $EMBED_MAX_DEPTH unless exists $opts{maxdepth}; |
27 | $opts{depth} = 0 unless $opts{depth}; | |
599fe373 TC |
28 | $opts{vars} = |
29 | { | |
30 | cfg => $opts{cfg}, | |
31 | bse => | |
32 | { | |
33 | site => BSE::TB::Site->new, | |
34 | url => | |
35 | ($opts{admin} || $opts{admin_links} | |
36 | ? sub { $_[0]->admin } | |
37 | : sub { $_[0]->link } | |
38 | ), | |
39 | admin => $opts{admin}, | |
40 | admin_links => $opts{admin_links}, | |
41 | dumper => sub { | |
42 | require Data::Dumper; | |
43 | return escape_html(Data::Dumper::Dumper(shift)); | |
44 | }, | |
45 | }, | |
46 | }; | |
47 | my $self = bless \%opts, $class; | |
48 | $self->set_variable_class(articles => "Articles"); | |
49 | ||
50 | return $self; | |
41358dcc TC |
51 | } |
52 | ||
8a153d74 TC |
53 | sub cfg { |
54 | $_[0]{cfg}; | |
55 | } | |
56 | ||
599fe373 TC |
57 | sub set_variable { |
58 | my ($self, $name, $value) = @_; | |
59 | ||
60 | $self->{vars}{$name} = $value; | |
61 | ||
62 | return 1; | |
63 | } | |
64 | ||
65 | sub set_variable_class { | |
66 | my ($self, $name, $class) = @_; | |
67 | ||
68 | require Squirrel::Template; | |
69 | $self->set_variable($name => Squirrel::Template::Expr::WrapClass->new($class)); | |
70 | } | |
71 | ||
72 | sub variables { | |
73 | my ($self) = @_; | |
74 | ||
75 | return $self->{vars}; | |
76 | } | |
77 | ||
41358dcc TC |
78 | # replace commonly used characters |
79 | # like MS dumb-quotes | |
80 | # unfortunately some browsers^W^Wnetscape don't support the entities yet <sigh> | |
81 | sub make_entities { | |
82 | my $text = shift; | |
83 | ||
84 | $text =~ s/\226/-/g; # "--" looks ugly | |
85 | $text =~ s/\222/'/g; | |
86 | $text =~ s/\221/`/g; | |
d83fc359 | 87 | $text =~ s/\’/'/g; |
41358dcc TC |
88 | |
89 | return $text; | |
90 | } | |
91 | ||
721cd24c | 92 | sub summarize { |
00dd8d82 | 93 | my ($self, $articles, $text, $acts, $length) = @_; |
721cd24c TC |
94 | |
95 | # remove any block level formatting | |
00dd8d82 | 96 | $self->remove_block($articles, $acts, \$text); |
721cd24c TC |
97 | |
98 | $text =~ tr/\n\r / /s; | |
99 | ||
100 | if (length $text > $length) { | |
101 | $text = substr($text, 0, $length); | |
102 | $text =~ s/\s+\S+$//; | |
103 | ||
104 | # roughly balance [ and ] | |
105 | my $temp = $text; | |
106 | 1 while $temp =~ s/\s\[[^\]]*\]//; # eliminate matched | |
107 | my $count = 0; | |
108 | ++$count while $temp =~ s/\w\[[^\]]*$//; # count unmatched | |
109 | ||
110 | $text .= ']' x $count; | |
111 | $text .= '...'; | |
112 | } | |
113 | ||
85802bd5 TC |
114 | # the formatter now adds <p></p> around the text, but we don't |
115 | # want that here | |
c5286ebe TC |
116 | my $result = $self->format_body(articles => $articles, |
117 | text => $text); | |
85802bd5 TC |
118 | $result =~ s!<p>|</p>!!g; |
119 | ||
120 | return $result; | |
721cd24c TC |
121 | } |
122 | ||
41358dcc TC |
123 | # attempts to move the given position forward if it's within a HTML tag, |
124 | # entity or just a word | |
125 | sub adjust_for_html { | |
126 | my ($self, $text, $pos) = @_; | |
127 | ||
128 | # advance if in a tag | |
129 | return $pos + length $1 | |
130 | if substr($text, 0, $pos) =~ /<[^<>]*$/ | |
131 | && substr($text, $pos) =~ /^([^<>]*>)/; | |
132 | return $pos + length $1 | |
133 | if substr($text, 0, $pos) =~ /&[^;&]*$/ | |
134 | && substr($text, $pos) =~ /^([^;&]*;)/; | |
135 | return $pos + length $1 | |
136 | if $pos <= length $text | |
137 | && substr($text, $pos-1, 1) =~ /\w$/ | |
138 | && substr($text, $pos) =~ /^(\w+)/; | |
139 | ||
140 | return $pos; | |
141 | } | |
142 | ||
8aee8e95 TC |
143 | # raw html - this has some limitations |
144 | # the input text has already been escaped, so we need to unescape it | |
145 | # too bad if you want [] in your html (but you can use entities) | |
146 | sub _make_html { | |
918735d1 | 147 | return unescape_html($_[0]); |
8aee8e95 TC |
148 | } |
149 | ||
745d2b57 | 150 | sub _embed_low { |
5d88571c | 151 | my ($self, $acts, $articles, $what, $template, $maxdepth, $templater) = @_; |
745d2b57 TC |
152 | |
153 | $maxdepth = $self->{maxdepth} | |
154 | if !$maxdepth || $maxdepth > $self->{maxdepth}; | |
155 | #if ($self->{depth}) { | |
156 | # print STDERR "Embed depth $self->{depth}\n"; | |
157 | #} | |
158 | if ($self->{depth} > $self->{maxdepth}) { | |
342d1d4b TC |
159 | if ($self->{maxdepth} == $EMBED_MAX_DEPTH) { |
160 | return "** too many embedding levels **"; | |
161 | } | |
162 | else { | |
163 | return ''; | |
164 | } | |
745d2b57 TC |
165 | } |
166 | ||
201aed6f TC |
167 | my $embed; |
168 | if ($what =~ /^alias:([a-z]\w*)$/) { | |
169 | my $alias = $1; | |
170 | ($embed) = $articles->getBy(linkAlias => $alias) | |
171 | or return "** Cannot find article aliased $alias to be embedded **";; | |
745d2b57 TC |
172 | } |
173 | else { | |
201aed6f TC |
174 | my $id; |
175 | if ($what !~ /^\d+$/) { | |
176 | # not an article id, assume there's an article here we can use | |
177 | $id = $acts->{$what} && $templater->perform($acts, $what, 'id'); | |
178 | unless ($id && $id =~ /^\d+$/) { | |
179 | # save it for later | |
180 | defined $template or $template = "-"; | |
181 | return "<:embed $what $template $maxdepth:>"; | |
9fae8be7 | 182 | } |
201aed6f TC |
183 | } |
184 | else { | |
185 | $id = $what; | |
745d2b57 TC |
186 | } |
187 | ||
201aed6f TC |
188 | $embed = $articles->getByPkey($id) |
189 | or return "** Cannot find article $id to be embedded **";; | |
745d2b57 | 190 | } |
201aed6f TC |
191 | |
192 | my $gen = $self; | |
193 | if (ref($self) ne $embed->{generator}) { | |
194 | my $genname = $embed->{generator}; | |
195 | $genname =~ s#::#/#g; # broken on MacOS I suppose | |
196 | $genname .= ".pm"; | |
197 | eval { | |
198 | require $genname; | |
199 | }; | |
200 | if ($@) { | |
201 | print STDERR "Cannot load generator $embed->{generator}: $@\n"; | |
202 | return "** Cannot load generator $embed->{generator} for article $embed->{id} **"; | |
203 | } | |
204 | my $top = $self->{top} || $embed; | |
205 | $gen = $embed->{generator}->new | |
206 | ( | |
207 | admin=>$self->{admin}, | |
208 | admin_links => $self->{admin_links}, | |
209 | cfg=>$self->{cfg}, | |
210 | request=>$self->{request}, | |
211 | top=>$top | |
212 | ); | |
745d2b57 | 213 | } |
201aed6f TC |
214 | |
215 | my $olddepth = $gen->{depth}; | |
216 | $gen->{depth} = $self->{depth}+1; | |
217 | my $oldmaxdepth = $gen->{maxdepth}; | |
218 | $gen->{maxdepth} = $maxdepth; | |
219 | $template = "" if defined($template) && $template eq "-"; | |
220 | my $result = $gen->embed($embed, $articles, $template); | |
221 | $gen->{depth} = $olddepth; | |
222 | $gen->{maxdepth} = $oldmaxdepth; | |
223 | ||
224 | return $result; | |
745d2b57 TC |
225 | } |
226 | ||
227 | sub _body_embed { | |
99ef7979 | 228 | my ($self, $acts, $articles, $which, $template, $maxdepth) = @_; |
745d2b57 | 229 | |
99ef7979 | 230 | my $text = $self->_embed_low($acts, $articles, $which, $template, $maxdepth); |
745d2b57 TC |
231 | |
232 | return $text; | |
233 | } | |
234 | ||
2fc9c38a TC |
235 | sub formatter_class { |
236 | require BSE::Formatter::Article; | |
237 | return 'BSE::Formatter::Article' | |
238 | } | |
239 | ||
41358dcc TC |
240 | # replace markup, insert img tags |
241 | sub format_body { | |
c5286ebe TC |
242 | my $self = shift; |
243 | my (%opts) = | |
244 | ( | |
245 | abs_urls => 0, | |
246 | imagepos => 'tr', | |
247 | auto_images => 1, | |
248 | images => [], | |
249 | files => [], | |
250 | acts => {}, | |
251 | @_ | |
252 | ); | |
253 | ||
254 | my $acts = $opts{acts}; | |
255 | my $articles = $opts{articles}; | |
256 | my $body = $opts{text}; | |
257 | my $imagePos = $opts{imagepos}; | |
258 | my $abs_urls = $opts{abs_urls}; | |
259 | my $auto_images = $opts{auto_images}; | |
260 | my $templater = $opts{templater}; | |
261 | my $images = $opts{images}; | |
262 | my $files = $opts{files}; | |
41358dcc TC |
263 | |
264 | return substr($body, 6) if $body =~ /^<html>/i; | |
265 | ||
2fc9c38a | 266 | my $formatter_class = $self->formatter_class; |
4772671f | 267 | |
2fc9c38a TC |
268 | my $formatter = $formatter_class->new(gen => $self, |
269 | acts => $acts, | |
270 | articles => $articles, | |
271 | abs_urls => $abs_urls, | |
272 | auto_images => \$auto_images, | |
273 | images => $images, | |
274 | files => $files, | |
275 | templater => $templater); | |
4772671f TC |
276 | |
277 | $body = $formatter->format($body); | |
41358dcc | 278 | |
0ef1d253 TC |
279 | my $xhtml = $self->{cfg}->entry('basic', 'xhtml', 1); |
280 | ||
dbe0477f | 281 | # we don't format named images |
c5286ebe | 282 | my @images = grep $_->{name} eq '', @$images; |
1e60d3c4 TC |
283 | if ($auto_images |
284 | && @images | |
285 | && $self->{cfg}->entry('basic', 'auto_images', 1) | |
286 | && $imagePos ne 'xx') { | |
41358dcc TC |
287 | # the first image simply goes where we're told to put it |
288 | # the imagePos is [tb][rl] (top|bottom)(right|left) | |
289 | my $align = $imagePos =~ /r/ ? 'right' : 'left'; | |
290 | ||
291 | # Offset the end a bit so we don't get an image hanging as obviously | |
292 | # off the end. | |
293 | # Numbers determined by trial - it can still look pretty rough. | |
294 | my $len = length $body; | |
295 | if ($len > 1000) { | |
296 | $len -= 500; | |
297 | } | |
298 | elsif ($len > 800) { | |
299 | $len -= 200; | |
300 | } | |
301 | ||
302 | #my $incr = @images > 1 ? 2*$len / (2*@images+1) : 0; | |
303 | my $incr = $len / @images; | |
f0543260 TC |
304 | # inserting the image tags moves character positions around |
305 | # so we need the temp buffer | |
306 | if ($imagePos =~ /b/) { | |
307 | @images = reverse @images; | |
308 | if (@images % 2 == 0) { | |
309 | # starting at the bottom, swap it around | |
41358dcc TC |
310 | $align = $align eq 'right' ? 'left' : 'right'; |
311 | } | |
41358dcc | 312 | } |
f0543260 TC |
313 | my $output = ''; |
314 | for my $image (@images) { | |
315 | # adjust to make sure this isn't in the middle of a tag or entity | |
316 | my $pos = $self->adjust_for_html($body, $incr); | |
317 | ||
f40af7e2 | 318 | my $img = $image->inline(cfg => $self->{cfg}, align => $align); |
f0543260 TC |
319 | $output .= $img; |
320 | $output .= substr($body, 0, $pos); | |
321 | substr($body, 0, $pos) = ''; | |
322 | $align = $align eq 'right' ? 'left' : 'right'; | |
41358dcc | 323 | } |
f0543260 | 324 | $body = $output . $body; # don't forget the rest of it |
41358dcc | 325 | } |
f0543260 | 326 | |
41358dcc TC |
327 | return make_entities($body); |
328 | } | |
329 | ||
8aee8e95 TC |
330 | sub embed { |
331 | my ($self, $article, $articles, $template) = @_; | |
c76e86ea | 332 | |
60169321 TC |
333 | if (defined $template && $template =~ /\$/) { |
334 | $template =~ s/\$/$article->{template}/; | |
335 | } | |
336 | else { | |
337 | $template = $article->{template} | |
338 | unless defined($template) && $template =~ /\S/; | |
339 | } | |
8aee8e95 | 340 | |
aefcabcb | 341 | my $html = BSE::Template->get_source($template, $self->{cfg}); |
8aee8e95 TC |
342 | |
343 | # the template will hopefully contain <:embed start:> and <:embed end:> | |
344 | # tags | |
345 | # otherwise pull out the body content | |
346 | if ($html =~ /<:\s*embed\s*start\s*:>(.*)<:\s*embed\s*end\s*:>/s | |
347 | || $html =~ m"<\s*body[^>]*>(.*)<\s*/\s*body>"s) { | |
348 | $html = $1; | |
349 | } | |
350 | return $self->generate_low($html, $article, $articles, 1); | |
351 | } | |
352 | ||
7928764a | 353 | sub iter_kids_of { |
75677b30 | 354 | my ($self, $state, $args, $acts, $name, $templater) = @_; |
7d8d0f14 TC |
355 | |
356 | my $filter = $self->_get_filter(\$args); | |
7928764a | 357 | |
75677b30 | 358 | $state->{parentid} = undef; |
ab3c22ff | 359 | my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater); |
7928764a TC |
360 | for my $id (@ids) { |
361 | unless ($id =~ /^\d+$|^-1$/) { | |
362 | $id = $templater->perform($acts, $id, "id"); | |
363 | } | |
364 | } | |
365 | @ids = grep /^\d+$|^-1$/, @ids; | |
75677b30 TC |
366 | if (@ids == 1) { |
367 | $state->{parentid} = $ids[0]; | |
368 | } | |
8145d132 | 369 | $self->_do_filter($filter, map Articles->listedChildren($_), @ids); |
7928764a TC |
370 | } |
371 | ||
70789617 TC |
372 | my $cols_re; # cache for below |
373 | ||
0804976d TC |
374 | { |
375 | my %expr_cache; | |
376 | ||
377 | sub _get_filter { | |
378 | my ($self, $rargs) = @_; | |
379 | ||
380 | if ($$rargs =~ s/filter:\s+(.*)\z//s) { | |
381 | my $expr = $1; | |
382 | my $orig_expr = $expr; | |
383 | unless ($cols_re) { | |
384 | my $cols_expr = '(' . join('|', Article->columns) . ')'; | |
385 | $cols_re = qr/\[$cols_expr\]/; | |
386 | } | |
387 | $expr =~ s/$cols_re/\$article->{$1}/g; | |
388 | $expr =~ s/ARTICLE/\$article/g; | |
389 | #print STDERR "Expr $expr\n"; | |
390 | my $filter = $expr_cache{$expr}; | |
391 | unless ($filter) { | |
392 | $filter = eval 'sub { my $article = shift; '.$expr.'; }'; | |
393 | if ($@) { | |
394 | print STDERR "** Failed to compile filter expression >>$expr<< built from >>$orig_expr<<\n"; | |
395 | return; | |
396 | } | |
397 | $expr_cache{$expr} = $filter; | |
398 | } | |
399 | ||
400 | return $filter; | |
70789617 | 401 | } |
0804976d | 402 | else { |
70789617 TC |
403 | return; |
404 | } | |
70789617 TC |
405 | } |
406 | } | |
407 | ||
408 | sub _do_filter { | |
409 | my ($self, $filter, @articles) = @_; | |
410 | ||
411 | $filter | |
412 | or return @articles; | |
413 | ||
414 | return grep $filter->($_), @articles; | |
415 | } | |
416 | ||
aefcabcb | 417 | sub iter_all_kids_of { |
75677b30 | 418 | my ($self, $state, $args, $acts, $name, $templater) = @_; |
70789617 TC |
419 | |
420 | my $filter = $self->_get_filter(\$args); | |
aefcabcb | 421 | |
75677b30 | 422 | $state->{parentid} = undef; |
ab3c22ff | 423 | my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater); |
aefcabcb TC |
424 | for my $id (@ids) { |
425 | unless ($id =~ /^\d+$|^-1$/) { | |
426 | $id = $templater->perform($acts, $id, "id"); | |
427 | } | |
428 | } | |
429 | @ids = grep /^\d+$|^-1$/, @ids; | |
75677b30 TC |
430 | @ids == 1 and $state->{parentid} = $ids[0]; |
431 | ||
70789617 | 432 | $self->_do_filter($filter, map Articles->all_visible_kids($_), @ids); |
aefcabcb TC |
433 | } |
434 | ||
7928764a | 435 | sub iter_inlines { |
0316d3da AO |
436 | my ($self, $state, $args, $acts, $name, $templater) = @_; |
437 | ||
438 | my $filter = $self->_get_filter(\$args); | |
7928764a | 439 | |
0316d3da | 440 | $state->{parentid} = undef; |
ab3c22ff | 441 | my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater); |
7928764a TC |
442 | for my $id (@ids) { |
443 | unless ($id =~ /^\d+$/) { | |
444 | $id = $templater->perform($acts, $id, "id"); | |
445 | } | |
446 | } | |
447 | @ids = grep /^\d+$/, @ids; | |
0316d3da AO |
448 | @ids == 1 and $state->{parentid} = $ids[0]; |
449 | ||
450 | $self->_do_filter($filter, map Articles->getByPkey($_), @ids); | |
7928764a TC |
451 | } |
452 | ||
e6ce1340 TC |
453 | sub iter_gimages { |
454 | my ($self, $args) = @_; | |
455 | ||
456 | unless ($self->{gimages}) { | |
f40af7e2 TC |
457 | require BSE::TB::Images; |
458 | my @gimages = BSE::TB::Images->getBy(articleId => -1); | |
e6ce1340 TC |
459 | my %gimages = map { $_->{name} => $_ } @gimages; |
460 | $self->{gimages} = \%gimages; | |
461 | } | |
462 | ||
463 | my @gimages = | |
464 | sort { $a->{name} cmp $b->{name} } values %{$self->{gimages}}; | |
465 | if ($args =~ m!^named\s+/([^/]+)/$!) { | |
466 | my $re = $1; | |
467 | return grep $_->{name} =~ /$re/i, @gimages; | |
468 | } | |
469 | else { | |
470 | return @gimages; | |
471 | } | |
472 | } | |
473 | ||
9366cd70 TC |
474 | sub iter_gfiles { |
475 | my ($self, $args) = @_; | |
476 | ||
477 | unless ($self->{gfiles}) { | |
478 | my @gfiles = Articles->global_files; | |
479 | my %gfiles = map { $_->{name} => $_ } @gfiles; | |
480 | $self->{gfiles} = \%gfiles; | |
481 | } | |
482 | ||
483 | my @gfiles = | |
484 | sort { $a->{name} cmp $b->{name} } values %{$self->{gfiles}}; | |
485 | if ($args =~ m!^named\s+/([^/]+)/$!) { | |
486 | my $re = $1; | |
487 | return grep $_->{name} =~ /$re/i, @gfiles; | |
488 | } | |
d745f64f TC |
489 | elsif ($args =~ m(^filter: (.*)$)s) { |
490 | my $expr = $1; | |
491 | $expr =~ s/FILE\[(\w+)\]/\$file->$1/g; | |
492 | my $sub = eval 'sub { my $file = shift; ' . $expr . '; }'; | |
493 | $sub | |
494 | or die "* Cannot compile sub from filter $expr: $@ *"; | |
495 | return grep $sub->($_), @gfiles; | |
496 | } | |
9366cd70 TC |
497 | else { |
498 | return @gfiles; | |
499 | } | |
500 | } | |
501 | ||
7f05f584 TC |
502 | sub admin_tags { |
503 | my ($self) = @_; | |
504 | ||
505 | $self->{admin} or return; | |
506 | ||
507 | return BSE::Util::Tags->secure($self->{request}); | |
508 | } | |
509 | ||
8a153d74 TC |
510 | sub _static_images { |
511 | my ($self) = @_; | |
b902f9de TC |
512 | |
513 | my $static = $self->{cfg}->entry('basic', 'static_thumbnails', 1); | |
514 | $self->{admin} and $static = 0; | |
515 | $self->{dynamic} and $static = 0; | |
516 | ||
8a153d74 TC |
517 | return $static; |
518 | } | |
519 | ||
520 | # implements popimage and gpopimage | |
521 | sub do_popimage_low { | |
522 | my ($self, $im, $class) = @_; | |
523 | ||
3f23129e TC |
524 | return $im->popimage |
525 | ( | |
526 | cfg => $self->cfg, | |
527 | class => $class, | |
528 | static => $self->_static_images, | |
529 | ); | |
8a153d74 | 530 | |
8a153d74 TC |
531 | } |
532 | ||
533 | sub do_gpopimage { | |
534 | my ($self, $image_id, $class) = @_; | |
535 | ||
536 | my $im = $self->get_gimage($image_id) | |
537 | or return "* Unknown global image '$image_id' *"; | |
538 | ||
539 | return $self->do_popimage_low($im, $class); | |
540 | } | |
541 | ||
542 | sub _sthumbimage_low { | |
543 | my ($self, $geometry, $im, $field) = @_; | |
544 | ||
545 | return $self->_thumbimage_low($geometry, $im, $field, $self->{cfg}, $self->_static_images); | |
b902f9de TC |
546 | } |
547 | ||
195977cd | 548 | sub tag_gthumbimage { |
6c192fd2 | 549 | my ($self, $rcurrent, $args, $acts, $name, $templater) = @_; |
195977cd | 550 | |
6c192fd2 | 551 | my ($geometry_id, $id, $field) = DevHelp::Tags->get_parms($args, $acts, $templater); |
195977cd TC |
552 | |
553 | return $self->do_gthumbimage($geometry_id, $id, $field, $$rcurrent); | |
554 | } | |
555 | ||
a9b73dab TC |
556 | sub _find_image { |
557 | my ($self, $acts, $templater, $article_id, $image_tags, $msg) = @_; | |
47f841b5 | 558 | |
47f841b5 TC |
559 | my $article; |
560 | if ($article_id =~ /^\d+$/) { | |
561 | require Articles; | |
a9b73dab | 562 | $article = Articles->getByPkey($article_id); |
a62da880 TC |
563 | unless ($article) { |
564 | $$msg = "* no article $article_id found *"; | |
565 | return; | |
566 | } | |
47f841b5 | 567 | } |
c76e86ea | 568 | elsif ($acts->{$article_id}) { |
47f841b5 | 569 | my $id = $templater->perform($acts, $article_id, "id"); |
a9b73dab TC |
570 | $article = Articles->getByPkey($id); |
571 | unless ($article) { | |
572 | $$msg = "* article $article_id/$id not found *"; | |
573 | return; | |
574 | } | |
47f841b5 | 575 | } |
c76e86ea | 576 | else { |
a9b73dab TC |
577 | ($article) = Articles->getBy(linkAlias => $article_id); |
578 | unless ($article) { | |
579 | $$msg = "* no article $article_id found *"; | |
580 | return; | |
581 | } | |
c76e86ea | 582 | } |
47f841b5 | 583 | $article |
a9b73dab | 584 | or return; |
47f841b5 TC |
585 | |
586 | my @images = $article->images; | |
587 | my $im; | |
588 | for my $tag (split /,/, $image_tags) { | |
589 | if ($tag =~ m!^/(.*)/$!) { | |
590 | my $re = $1; | |
591 | ($im) = grep $_->{name} =~ /$re/i, @images | |
592 | and last; | |
593 | } | |
594 | elsif ($tag =~ /^\d+$/) { | |
595 | if ($tag >= 1 && $tag <= @images) { | |
596 | $im = $images[$tag-1]; | |
597 | last; | |
598 | } | |
599 | } | |
600 | elsif ($tag =~ /^[^\W\d]\w*$/) { | |
601 | ($im) = grep $_->{name} eq $tag, @images | |
602 | and last; | |
603 | } | |
604 | } | |
a62da880 TC |
605 | unless ($im) { |
606 | $$msg = "* no image matching $image_tags found *"; | |
607 | return; | |
608 | } | |
a9b73dab TC |
609 | |
610 | return $im; | |
611 | } | |
612 | ||
613 | sub tag_sthumbimage { | |
614 | my ($self, $args, $acts, $name, $templater) = @_; | |
615 | ||
616 | my ($article_id, $geometry, $image_tags, $field) = split ' ', $args; | |
617 | ||
618 | my $msg; | |
619 | my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg) | |
620 | or return $msg; | |
47f841b5 | 621 | |
b902f9de | 622 | return $self->_sthumbimage_low($geometry, $im, $field); |
47f841b5 TC |
623 | } |
624 | ||
a9b73dab TC |
625 | sub tag_simage { |
626 | my ($self, $args, $acts, $name, $templater) = @_; | |
627 | ||
628 | my ($article_id, $image_tags, $field, $rest) = split ' ', $args, 4; | |
629 | ||
630 | my $msg; | |
631 | my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg) | |
632 | or return $msg; | |
633 | ||
634 | return $self->_format_image($im, $field, $rest); | |
635 | } | |
636 | ||
47c75494 TC |
637 | =item iterator vimages I<articles> I<filter> |
638 | ||
639 | =item iterator vimages I<articles> | |
640 | ||
641 | Iterates over the images belonging to the articles specified. | |
642 | ||
643 | I<articles> can be any of: | |
644 | ||
645 | =over | |
646 | ||
647 | =item * | |
648 | ||
649 | article - the current article | |
650 | ||
651 | =item * | |
652 | ||
653 | children - all visible children (including stepkids) of the current | |
654 | article | |
655 | ||
656 | =item * | |
657 | ||
658 | parent - the parent of the current article | |
659 | ||
660 | =item * | |
661 | ||
662 | I<number> - a numeric article id, such as C<10>. | |
663 | ||
664 | =item * | |
665 | ||
666 | alias(I<alias>) - a link alias of an article | |
667 | ||
668 | =item * | |
669 | ||
670 | childrenof(I<articles>) - an articles that are children of | |
671 | I<articles>. I<articles> can be any normal article spec, so | |
672 | C<childrenof(childrenof(-1))> is valid. | |
673 | ||
674 | =item * | |
675 | ||
676 | I<tagname> - a tag name referring to an article. | |
677 | ||
678 | =back | |
679 | ||
6ed138ca TC |
680 | I<articles> has [] replacement done before parsing. |
681 | ||
47c75494 TC |
682 | I<filter> can be missing, or either of: |
683 | ||
684 | =over | |
685 | ||
686 | =item * | |
687 | ||
688 | named /I<regexp>/ - images with names matching the given regular | |
689 | expression | |
690 | ||
691 | =item * | |
692 | ||
693 | numbered I<number> - images with the given index. | |
694 | ||
695 | =back | |
696 | ||
697 | Items for this iterator are vimage and vthumbimage. | |
698 | ||
699 | =cut | |
700 | ||
701 | sub iter_vimages { | |
702 | my ($self, $article, $args, $acts, $name, $templater) = @_; | |
703 | ||
704 | my $re; | |
705 | my $num; | |
706 | if ($args =~ s!\s+named\s+/([^/]+)/$!!) { | |
707 | $re = $1; | |
708 | } | |
709 | elsif ($args =~ s!\s+numbered\s+(\d+)$!!) { | |
710 | $num = $1; | |
711 | } | |
712 | my @args = DevHelp::Tags->get_parms($args, $acts, $templater); | |
713 | my @images; | |
714 | for my $article_id (map { split /[, ]/ } @args) { | |
715 | my @articles = $self->_find_articles($article_id, $article, $acts, $name, $templater); | |
716 | for my $article (@articles) { | |
717 | my @aimages = $article->images; | |
718 | if (defined $re) { | |
6ed138ca | 719 | push @images, grep $_->{name} =~ /$re/, @aimages; |
47c75494 TC |
720 | } |
721 | elsif (defined $num) { | |
722 | if ($num >= 0 && $num <= @aimages) { | |
723 | push @images, $aimages[$num-1]; | |
724 | } | |
725 | } | |
726 | else { | |
727 | push @images, @aimages; | |
728 | } | |
729 | } | |
730 | } | |
731 | ||
732 | return @images; | |
733 | } | |
734 | ||
735 | =item vimage field | |
736 | ||
737 | =item vimage | |
738 | ||
739 | Retrieve the given field from the current vimage, or display the image. | |
740 | ||
741 | =cut | |
742 | ||
743 | sub tag_vimage { | |
744 | my ($self, $rvimage, $args) = @_; | |
745 | ||
746 | $$rvimage or return '** no current vimage **'; | |
747 | ||
748 | my ($field, $rest) = split ' ', $args, 2; | |
749 | ||
750 | return $self->_format_image($$rvimage, $field, $rest); | |
751 | } | |
752 | ||
753 | =item vthumbimage geometry field | |
754 | ||
755 | =item vthumbimage geometry | |
756 | ||
757 | Retrieve the given field from the thumbnail of the current vimage or | |
758 | display the thumbnail. | |
759 | ||
760 | =cut | |
761 | ||
762 | sub tag_vthumbimage { | |
763 | my ($self, $rvimage, $args) = @_; | |
764 | ||
765 | $$rvimage or return '** no current vimage **'; | |
766 | my ($geo, $field) = split ' ', $args; | |
767 | ||
768 | return $self->_sthumbimage_low($geo, $$rvimage, $field); | |
769 | } | |
770 | ||
771 | sub _find_articles { | |
772 | my ($self, $article_id, $article, $acts, $name, $templater) = @_; | |
773 | ||
774 | if ($article_id =~ /^\d+$/) { | |
775 | my $result = Articles->getByPkey($article_id); | |
776 | $result or print STDERR "** Unknown article id $article_id **\n"; | |
777 | return $result ? $result : (); | |
778 | } | |
779 | elsif ($article_id =~ /^alias\((\w+)\)$/) { | |
780 | my $result = Articles->getBy(linkAlias => $1); | |
781 | $result or print STDERR "** Unknown article alias $article_id **\n"; | |
782 | return $result ? $result : (); | |
783 | } | |
784 | elsif ($article_id =~ /^childrenof\((.*)\)$/) { | |
785 | my $id = $1; | |
786 | if ($id eq '-1') { | |
787 | return Articles->all_visible_kids(-1); | |
788 | } | |
789 | else { | |
790 | my @parents = $self->_find_articles($id) | |
791 | or return; | |
792 | return map $_->all_visible_kids, @parents; | |
793 | } | |
794 | } | |
795 | elsif ($acts->{$article_id}) { | |
796 | my $id = $templater->perform($acts, $article_id, 'id'); | |
797 | if ($id && $id =~ /^\d+$/) { | |
798 | return Articles->getByPkey($id); | |
799 | } | |
800 | } | |
801 | print STDERR "** Unknown article identifier $article_id **\n"; | |
802 | ||
803 | return; | |
804 | } | |
805 | ||
41358dcc | 806 | sub baseActs { |
283937a0 | 807 | my ($self, $articles, $acts, $article, $embedded) = @_; |
41358dcc TC |
808 | |
809 | # used to generate the side menu | |
810 | my $section_index = -1; | |
811 | my @sections = $articles->listedChildren(-1); | |
812 | #sort { $a->{displayOrder} <=> $b->{displayOrder} } | |
813 | #grep $_->{listed}, $articles->sections; | |
814 | my $subsect_index = -1; | |
815 | my @subsections; # filled as we move through the sections | |
816 | my @level3; # filled as we move through the subsections | |
817 | my $level3_index = -1; | |
818 | ||
f00a461d | 819 | my $cfg = $self->{cfg} || BSE::Cfg->single; |
b19047a6 | 820 | my %extras = $cfg->entriesCS('extra tags'); |
41358dcc | 821 | for my $key (keys %extras) { |
b19047a6 TC |
822 | # follow any links |
823 | my $data = $cfg->entryVar('extra tags', $key); | |
824 | $extras{$key} = sub { $data }; | |
41358dcc | 825 | } |
195977cd TC |
826 | |
827 | my $current_gimage; | |
47c75494 | 828 | my $current_vimage; |
b873a8fa | 829 | my $it = BSE::Util::Iterate->new; |
75677b30 TC |
830 | my $art_it = BSE::Util::Iterate::Article->new(cfg => $cfg, |
831 | admin => $self->{admin}, | |
832 | top => $self->{top}); | |
41358dcc TC |
833 | return |
834 | ( | |
835 | %extras, | |
836 | ||
00dd8d82 | 837 | custom_class($cfg)->base_tags($articles, $acts, $article, $embedded, $cfg), |
7f05f584 | 838 | $self->admin_tags(), |
531fb3bc | 839 | BSE::Util::Tags->static($acts, $self->{cfg}), |
8aee8e95 TC |
840 | # for embedding the content from children and other sources |
841 | ifEmbedded=> sub { $embedded }, | |
842 | embed => sub { | |
5d88571c TC |
843 | my ($args, $acts, $name, $templater) = @_; |
844 | my ($what, $template, $maxdepth) = split ' ', $args; | |
556509b1 | 845 | undef $maxdepth if defined $maxdepth && $maxdepth !~ /^\d+/; |
5d88571c | 846 | return $self->_embed_low($acts, $articles, $what, $template, $maxdepth, $templater); |
8aee8e95 | 847 | }, |
745d2b57 | 848 | ifCanEmbed=> sub { $self->{depth} <= $self->{maxdepth} }, |
8aee8e95 | 849 | |
721cd24c TC |
850 | summary => |
851 | sub { | |
48c6e0b6 TC |
852 | my ($args, $acts, $name, $templater) = @_; |
853 | my ($which, $limit) = DevHelp::Tags->get_parms($args, $acts, $templater); | |
721cd24c | 854 | $which or $which = "child"; |
48c6e0b6 | 855 | $limit or $limit = $article->{summaryLength}; |
721cd24c TC |
856 | $acts->{$which} |
857 | or return "<:summary $which Cannot find $which:>"; | |
a5e3fc4b | 858 | my $id = $templater->perform($acts, $which, "id") |
721cd24c TC |
859 | or return "<:summary $which No id returned :>"; |
860 | my $article = $articles->getByPkey($id) | |
861 | or return "<:summary $which Cannot find article $id:>"; | |
48c6e0b6 | 862 | return $self->summarize($articles, $article->{body}, $acts, $limit); |
721cd24c | 863 | }, |
41358dcc | 864 | ifAdmin => sub { $self->{admin} }, |
4d4fdaaa | 865 | ifAdminLinks => sub { $self->{admin_links} }, |
41358dcc TC |
866 | |
867 | # for generating the side menu | |
745d2b57 | 868 | iterate_level1_reset => sub { $section_index = -1 }, |
41358dcc TC |
869 | iterate_level1 => sub { |
870 | ++$section_index; | |
871 | if ($section_index < @sections) { | |
872 | #@subsections = grep $_->{listed}, | |
873 | # $articles->children($sections[$section_index]->{id}); | |
874 | @subsections = grep { $_->{listed} != 2 } | |
875 | $articles->listedChildren($sections[$section_index]->{id}); | |
876 | $subsect_index = -1; | |
877 | return 1; | |
878 | } | |
879 | else { | |
880 | return 0; | |
881 | } | |
882 | }, | |
883 | level1 => sub { | |
c76e86ea | 884 | return tag_article($sections[$section_index], $cfg, $_[0]); |
41358dcc TC |
885 | }, |
886 | ||
887 | # used to generate a list of subsections for the side-menu | |
888 | iterate_level2 => sub { | |
889 | ++$subsect_index; | |
890 | if ($subsect_index < @subsections) { | |
891 | @level3 = grep { $_->{listed} != 2 } | |
892 | $articles->listedChildren($subsections[$subsect_index]{id}); | |
893 | $level3_index = -1; | |
894 | return 1; | |
895 | } | |
896 | return 0; | |
897 | }, | |
898 | level2 => sub { | |
c76e86ea | 899 | return tag_article($subsections[$subsect_index], $cfg, $_[0]); |
41358dcc TC |
900 | }, |
901 | ifLevel2 => | |
902 | sub { | |
903 | return scalar @subsections; | |
904 | }, | |
905 | ||
906 | # possibly level3 items | |
907 | iterate_level3 => sub { | |
908 | return ++$level3_index < @level3; | |
909 | }, | |
c76e86ea TC |
910 | level3 => sub { |
911 | tag_article($level3[$level3_index], $cfg, $_[0]) | |
912 | }, | |
41358dcc TC |
913 | ifLevel3 => sub { scalar @level3 }, |
914 | ||
915 | # generate an admin or link url, depending on admin state | |
916 | url=> | |
917 | sub { | |
7928764a | 918 | my ($name, $acts, $func, $templater) = @_; |
4d4fdaaa | 919 | my $item = $self->{admin_links} ? 'admin' : 'link'; |
76c6b28e TC |
920 | $acts->{$name} |
921 | or die "ENOIMPL\n"; | |
4d4fdaaa TC |
922 | my $url = $templater->perform($acts, $name, $item); |
923 | if (!$self->{admin} && $self->{admin_links}) { | |
924 | $url .= $url =~ /\?/ ? "&" : "?"; | |
925 | $url .= "admin=0&admin_links=1"; | |
926 | } | |
927 | return $url; | |
41358dcc | 928 | }, |
41358dcc TC |
929 | ifInMenu => |
930 | sub { | |
931 | $acts->{$_[0]} or return 0; | |
932 | return $acts->{$_[0]}->('listed') == 1; | |
933 | }, | |
934 | titleImage=> | |
935 | sub { | |
936 | my ($image, $text) = split ' ', $_[0]; | |
937 | if (-e $IMAGEDIR."/titles/".$image) { | |
938 | return qq!<img src="/images/titles/!.$image .qq!" border=0>! | |
939 | } | |
940 | else { | |
918735d1 | 941 | return escape_html($text); |
41358dcc TC |
942 | } |
943 | }, | |
75677b30 TC |
944 | $art_it->make( code => [ iter_kids_of => $self ], |
945 | single => 'ofchild', | |
946 | plural => 'children_of', | |
947 | nocache => 1, | |
948 | state => 1 ), | |
949 | $art_it->make( code => [ iter_kids_of => $self ], | |
950 | single => 'ofchild2', | |
951 | plural => 'children_of2', | |
952 | nocache => 1, | |
953 | state => 1 ), | |
954 | $art_it->make( code => [ iter_kids_of => $self ], | |
955 | single => 'ofchild3', | |
956 | plural => 'children_of3', | |
957 | nocache => 1, | |
958 | state => 1 ), | |
959 | $art_it->make( code => [ iter_all_kids_of => $self ], | |
960 | single => 'ofallkid', | |
961 | plural => 'allkids_of', | |
962 | state => 1 ), | |
963 | $art_it->make( code => [ iter_all_kids_of => $self ], | |
964 | single => 'ofallkid2', | |
965 | plural => 'allkids_of2', | |
966 | nocache => 1, | |
967 | state => 1 ), | |
968 | $art_it->make( code => [ iter_all_kids_of => $self ], | |
969 | single => 'ofallkid3', | |
970 | plural => 'allkids_of3', | |
971 | nocache => 1, | |
972 | state => 1 ), | |
973 | $art_it->make( code => [ iter_all_kids_of => $self ], | |
974 | single => 'ofallkid4', | |
975 | plural => 'allkids_of4', | |
976 | nocache => 1, | |
977 | state => 1 ), | |
978 | $art_it->make( code => [ iter_all_kids_of => $self ], | |
979 | single => 'ofallkid5', | |
980 | plural => 'allkids_of5', | |
981 | nocache => 1, | |
982 | state => 1 ), | |
0316d3da AO |
983 | $art_it->make( code => [ iter_inlines => $self ], |
984 | single => 'inline', | |
985 | plural => 'inlines', | |
986 | nocache => 1, | |
987 | state => 1 ), | |
daee3409 TC |
988 | gimage => |
989 | sub { | |
97469012 | 990 | my ($args, $acts, $func, $templater) = @_; |
4013e482 TC |
991 | my ($name, $align, @rest) = |
992 | DevHelp::Tags->get_parms($args, $acts, $templater); | |
993 | my $rest = "@rest"; | |
daee3409 | 994 | |
4233a71c TC |
995 | my $im; |
996 | if ($name eq '-') { | |
997 | $im = $current_gimage | |
998 | or return ''; | |
999 | } | |
1000 | else { | |
1001 | $im = $self->get_gimage($name) | |
1002 | or return ''; | |
1003 | } | |
daee3409 TC |
1004 | |
1005 | $self->_format_image($im, $align, $rest); | |
1006 | }, | |
195977cd TC |
1007 | $it->make_iterator( [ \&iter_gimages, $self ], 'gimagei', 'gimages', |
1008 | undef, undef, undef, \$current_gimage), | |
9366cd70 TC |
1009 | gfile => |
1010 | sub { | |
1011 | my ($name, $field) = split ' ', $_[0], 3; | |
1012 | ||
1013 | my $file = $self->get_gfile($name) | |
1014 | or return ''; | |
1015 | ||
1016 | $self->_format_file($file, $field); | |
1017 | }, | |
1018 | $it->make_iterator( [ \&iter_gfiles, $self ], 'gfilei', 'gfiles'), | |
195977cd | 1019 | gthumbimage => [ tag_gthumbimage => $self, \$current_gimage ], |
47f841b5 | 1020 | sthumbimage => [ tag_sthumbimage => $self ], |
a9b73dab | 1021 | simage => [ tag_simage => $self ], |
47c75494 TC |
1022 | $it->make_iterator( [ iter_vimages => $self, $article ], 'vimage', 'vimages', undef, undef, undef, \$current_vimage), |
1023 | vimage => [ tag_vimage => $self, \$current_vimage ], | |
1024 | vthumbimage => [ tag_vthumbimage => $self, \$current_vimage ], | |
41358dcc TC |
1025 | ); |
1026 | } | |
1027 | ||
1028 | sub find_terms { | |
61693c75 | 1029 | my ($body, $case_sensitive, $terms) = @_; |
41358dcc | 1030 | |
41358dcc TC |
1031 | # locate the terms |
1032 | my @found; | |
1033 | if ($case_sensitive) { | |
61693c75 | 1034 | for my $term (@$terms) { |
41358dcc TC |
1035 | if ($$body =~ /^(.*?)\Q$term/s) { |
1036 | push(@found, [ length($1), $term ]); | |
1037 | } | |
1038 | } | |
1039 | } | |
1040 | else { | |
61693c75 | 1041 | for my $term (@$terms) { |
41358dcc TC |
1042 | if ($$body =~ /^(.*?)\Q$term/is) { |
1043 | push(@found, [ length($1), $term ]); | |
1044 | } | |
1045 | } | |
1046 | } | |
1047 | ||
1048 | return @found; | |
1049 | } | |
1050 | ||
9f5d6afa TC |
1051 | # this takes the same inputs as _make_table(), but eliminates any |
1052 | # markup instead | |
1053 | sub _cleanup_table { | |
1054 | my ($opts, $data) = @_; | |
1055 | my @lines = split /\n/, $data; | |
1056 | for (@lines) { | |
1057 | s/^[^|]*\|//; | |
1058 | tr/|/ /s; | |
1059 | } | |
1060 | return join(' ', @lines); | |
1061 | } | |
1062 | ||
41358dcc TC |
1063 | # produce a nice excerpt for a found article |
1064 | sub excerpt { | |
61693c75 TC |
1065 | my ($self, $article, $found, $case_sensitive, $terms, $type, $body) = @_; |
1066 | ||
1067 | if (!$body) { | |
1068 | $body = $article->{body}; | |
1069 | ||
1070 | # we remove any formatting tags here, otherwise we get wierd table | |
1071 | # rubbish or other formatting in the excerpt. | |
5e104f12 TC |
1072 | my @files = $article->files; |
1073 | $self->remove_block('Articles', [], \$body, \@files); | |
61693c75 TC |
1074 | 1 while $body =~ s/[bi]\[([^\]\[]+)\]/$1/g; |
1075 | } | |
1076 | ||
e3d242f7 TC |
1077 | $body = escape_html($body); |
1078 | ||
61693c75 TC |
1079 | $type ||= 'body'; |
1080 | ||
1081 | my @found = find_terms(\$body, $case_sensitive, $terms); | |
41358dcc | 1082 | |
61693c75 | 1083 | my @reterms = @$terms; |
41358dcc TC |
1084 | for (@reterms) { |
1085 | tr/ / /s; | |
1086 | $_ = quotemeta; | |
26094672 | 1087 | s/\\?\s+/\\s+/g; |
41358dcc TC |
1088 | } |
1089 | # do a reverse sort so that the longer terms (and composite | |
1090 | # terms) are replaced first | |
1091 | my $re_str = join("|", reverse sort @reterms); | |
6e3d2da5 TC |
1092 | my $re; |
1093 | my $cfg = $self->{cfg}; | |
61693c75 | 1094 | if ($cfg->entryBool('search', 'highlight_partial', 1)) { |
6e3d2da5 TC |
1095 | $re = $case_sensitive ? qr/\b($re_str)/ : qr/\b($re_str)/i; |
1096 | } | |
1097 | else { | |
1098 | $re = $case_sensitive ? qr/\b($re_str)\b/ : qr/\b($re_str)\b/i; | |
1099 | } | |
41358dcc | 1100 | |
8aee8e95 TC |
1101 | # this used to try searching children as well, but it broke more |
1102 | # than it fixed | |
1103 | if (!@found) { | |
1104 | # we tried hard and failed | |
1105 | # return a generic article | |
1106 | if (length $body > $excerptSize) { | |
1107 | $body = substr($body, 0, $excerptSize); | |
1108 | $body =~ s/\S+\s*$/.../; | |
41358dcc | 1109 | } |
8aee8e95 TC |
1110 | $$found = 0; |
1111 | return $body; | |
41358dcc TC |
1112 | } |
1113 | ||
1114 | # only the first 5 | |
1115 | splice(@found, 5,-1) if @found > 5; | |
1116 | my $itemSize = $excerptSize / @found; | |
1117 | ||
1118 | # try to combine any that are close | |
1119 | @found = sort { $a->[0] <=> $b->[0] } @found; | |
1120 | for my $i (reverse 0 .. $#found-1) { | |
1121 | if ($found[$i+1][0] - $found[$i][0] < $itemSize) { | |
1122 | my @losing = @{$found[$i+1]}; | |
1123 | shift @losing; | |
1124 | push(@{$found[$i]}, @losing); | |
1125 | splice(@found, $i+1, 1); # remove it | |
1126 | } | |
1127 | } | |
1128 | ||
61693c75 TC |
1129 | my $highlight_prefix = |
1130 | $cfg->entry('search highlight', "${type}_prefix", "<b>"); | |
1131 | my $highlight_suffix = | |
1132 | $cfg->entry('search highlight', "${type}_suffix", "</b>"); | |
41358dcc TC |
1133 | my $termSize = $excerptSize / @found; |
1134 | my $result = ''; | |
1135 | for my $term (@found) { | |
1136 | my ($pos, @terms) = @$term; | |
1137 | my $start = $pos - $termSize/2; | |
1138 | my $part; | |
1139 | if ($start < 0) { | |
1140 | $start = 0; | |
1141 | $part = substr($body, 0, $termSize); | |
1142 | } | |
1143 | else { | |
1144 | $result .= "..."; | |
1145 | $part = substr($body, $start, $termSize); | |
1146 | $part =~ s/^\w+//; | |
1147 | } | |
1148 | if ($start + $termSize < length $body) { | |
1149 | $part =~ s/\s*\S*$/... /; | |
1150 | } | |
1151 | $result .= $part; | |
1152 | } | |
61693c75 | 1153 | $result =~ s{$re}{$highlight_prefix$1$highlight_suffix}ig; |
41358dcc TC |
1154 | $$found = 1; |
1155 | ||
1156 | return $result; | |
1157 | } | |
1158 | ||
1159 | sub visible { | |
1160 | return 1; | |
1161 | } | |
1162 | ||
d38f3b10 | 1163 | |
8aee8e95 TC |
1164 | # make whatever text $body points at safe for summarizing by removing most |
1165 | # block level formatting | |
1166 | sub remove_block { | |
5e104f12 | 1167 | my ($self, $articles, $acts, $body, $files) = @_; |
8aee8e95 | 1168 | |
2fc9c38a | 1169 | my $formatter_class = $self->formatter_class; |
d38f3b10 | 1170 | |
5e104f12 TC |
1171 | $files ||= []; |
1172 | ||
2fc9c38a | 1173 | my $formatter = $formatter_class->new(gen => $self, |
c5286ebe | 1174 | acts => $acts, |
fe4a482b | 1175 | article => $articles, |
5e104f12 TC |
1176 | articles => $articles, |
1177 | files => $files); | |
00dd8d82 TC |
1178 | |
1179 | $$body = $formatter->remove_format($$body); | |
8aee8e95 | 1180 | } |
41358dcc | 1181 | |
daee3409 TC |
1182 | sub get_gimage { |
1183 | my ($self, $name) = @_; | |
1184 | ||
1185 | unless ($self->{gimages}) { | |
f40af7e2 TC |
1186 | require BSE::TB::Images; |
1187 | my @gimages = BSE::TB::Images->getBy(articleId => -1); | |
daee3409 TC |
1188 | my %gimages = map { $_->{name} => $_ } @gimages; |
1189 | $self->{gimages} = \%gimages; | |
1190 | } | |
1191 | ||
1192 | return $self->{gimages}{$name}; | |
1193 | } | |
1194 | ||
9366cd70 TC |
1195 | sub get_gfile { |
1196 | my ($self, $name) = @_; | |
1197 | ||
1198 | unless ($self->{gfiles}) { | |
1199 | my @gfiles = Articles->global_files; | |
1200 | my %gfiles = map { $_->{name} => $_ } @gfiles; | |
1201 | $self->{gfiles} = \%gfiles; | |
1202 | } | |
1203 | ||
1204 | return $self->{gfiles}{$name}; | |
1205 | } | |
1206 | ||
195977cd TC |
1207 | # note: this is called by BSE::Formatter::thumbimage(), update that if |
1208 | # this is changed | |
1209 | sub do_gthumbimage { | |
1210 | my ($self, $geo_id, $image_id, $field, $current) = @_; | |
1211 | ||
1212 | my $im; | |
1213 | if ($image_id eq '-' && $current) { | |
1214 | $im = $current; | |
1215 | } | |
1216 | else { | |
1217 | $im = $self->get_gimage($image_id); | |
1218 | } | |
1219 | $im | |
1220 | or return '** unknown global image id **'; | |
1221 | ||
b902f9de | 1222 | return $self->_sthumbimage_low($geo_id, $im, $field); |
195977cd TC |
1223 | } |
1224 | ||
74b21f6d TC |
1225 | sub get_real_article { |
1226 | my ($self, $article) = @_; | |
1227 | ||
1228 | return $article; | |
1229 | } | |
1230 | ||
41358dcc TC |
1231 | 1; |
1232 | ||
8aee8e95 TC |
1233 | __END__ |
1234 | ||
41358dcc TC |
1235 | =head1 NAME |
1236 | ||
1237 | Generate - provides base Squirel::Template actions for use in generating | |
1238 | pages. | |
1239 | ||
1240 | =head1 SYNOPSIS | |
1241 | ||
1242 | =head1 DESCRIPTION | |
1243 | ||
fa304705 | 1244 | This is probably better documented in L<templates.pod>. |
8aee8e95 | 1245 | |
d2730773 | 1246 | =head1 COMMON TAGS |
8aee8e95 | 1247 | |
d2730773 TC |
1248 | These tags can be used anywhere, including in admin templates. It's |
1249 | possible some admin code has been missed, if you find a place where | |
1250 | these cannot be used let us know. | |
8aee8e95 | 1251 | |
8aee8e95 | 1252 | |
d2730773 | 1253 | =over |
8aee8e95 | 1254 | |
957a90ca TC |
1255 | =item kb I<data tag> |
1256 | ||
1257 | Formats the give value in kI<whatevers>. If you have a number that | |
1258 | could go over 1000 and you want it to use the 'k' metric prefix when | |
1259 | it does, use this tag. eg. <:kb file sizeInBytes:> | |
1260 | ||
1261 | =item date I<data tag> | |
1262 | ||
1263 | =item date "I<format>" I<data tag> | |
1264 | ||
1265 | Formats a date or date/time value from the database into something | |
1266 | more human readable. If you don't supply a format then the default | |
1267 | format of "%d-%b-%Y" is used ("20-Mar-2002"). | |
1268 | ||
1269 | The I<format> is a strftime() format specification, if that means | |
1270 | anything to you. If it doesn't, each code starts with % and are | |
1271 | replaced as follows: | |
1272 | ||
1273 | =over | |
1274 | ||
1275 | =item %a | |
1276 | ||
1277 | abbreviated weekday name | |
1278 | ||
1279 | =item %A | |
1280 | ||
1281 | full weekday name | |
1282 | ||
1283 | =item %b | |
1284 | ||
1285 | abbreviated month name | |
1286 | ||
1287 | =item %B | |
1288 | ||
1289 | full month name | |
1290 | ||
1291 | =item %c | |
1292 | ||
1293 | "preferred" date and time representation | |
1294 | ||
1295 | =item %d | |
1296 | ||
1297 | day of the month as a 2 digit number | |
1298 | ||
1299 | =item %H | |
1300 | ||
1301 | hour (24-hour clock) | |
1302 | ||
1303 | =item %I | |
1304 | ||
1305 | hour (12-hour clock) | |
1306 | ||
1307 | =item %j | |
1308 | ||
1309 | day of year as a 3-digit number | |
1310 | ||
1311 | =item %m | |
1312 | ||
1313 | month as a 2 digit number | |
1314 | ||
1315 | =item %M | |
1316 | ||
1317 | minute as a 2 digit number | |
1318 | ||
1319 | =item %p | |
1320 | ||
1321 | AM or PM or their equivalents | |
1322 | ||
1323 | =item %S | |
1324 | ||
1325 | seconds as a 2 digit number | |
1326 | ||
1327 | =item %U | |
1328 | ||
1329 | week number as a 2 digit number (first Sunday as the first day of week 1) | |
1330 | ||
1331 | =item %w | |
1332 | ||
1333 | weekday as a decimal number (0-6) | |
1334 | ||
1335 | =item %W | |
1336 | ||
1337 | week number as a 2 digit number (first Monday as the first day of week 1) | |
1338 | ||
1339 | =item %x | |
1340 | ||
1341 | the locale's appropriate date representation | |
1342 | ||
1343 | =item %X | |
1344 | ||
1345 | the locale's appropriate time representation | |
1346 | ||
1347 | =item %y | |
1348 | ||
1349 | 2-digit year without century | |
1350 | ||
1351 | =item %Y | |
1352 | ||
1353 | the full year | |
1354 | ||
1355 | =item %Z | |
1356 | ||
1357 | time zone name or abbreviation | |
1358 | ||
1359 | =item %% | |
1360 | ||
1361 | just '%' | |
1362 | ||
1363 | =back | |
1364 | ||
1365 | Your local strftime() implementation may implement some extensions to | |
1366 | the above, if your server is on a Unix system try running "man | |
1367 | strftime" for more information. | |
1368 | ||
1369 | =item bodytext I<data tag> | |
1370 | ||
1371 | Formats the text from the given tag in the same way that body text is. | |
1372 | ||
1373 | =item ifEq I<data1> I<data2> | |
1374 | ||
1375 | Checks if the 2 values are exactly equal. This is a string | |
1376 | comparison. | |
1377 | ||
1378 | The 2 data parameters can either be a tag reference in [], a literal | |
1379 | string inside "" or a single word. | |
1380 | ||
1381 | =item ifMatch I<data1> I<data2> | |
1382 | ||
1383 | Treats I<data2> as a perl regular expression and attempts to match | |
1384 | I<data1> against it. | |
1385 | ||
1386 | The 2 data parameters can either be a tag reference in [], a literal | |
1387 | string inside "" or a single word. | |
1388 | ||
1389 | =item cfg I<section> I<key> | |
1390 | ||
1391 | =item cfg I<section> I<key> I<default> | |
1392 | ||
1393 | Retrieves a value from the BSE configuration file. | |
1394 | ||
1395 | If you don't supply a default then a default will be the empty string. | |
1396 | ||
d2730773 TC |
1397 | =item release |
1398 | ||
1399 | The release number of BSE. | |
1400 | ||
1401 | =back | |
1402 | ||
1403 | =head1 TAGS | |
1404 | ||
1405 | =over 4 | |
1406 | ||
1407 | =item ifAdmin | |
1408 | ||
1409 | Conditional tag, true if generating in admin mode. | |
1410 | ||
1411 | =item iterator ... level1 | |
1412 | ||
1413 | Iterates over the listed level 1 articles. | |
1414 | ||
1415 | =item level1 I<name> | |
1416 | ||
1417 | The value of the I<name> field of the current level 1 article. | |
1418 | ||
1419 | =item iterator ... level2 | |
1420 | ||
1421 | Iterates over the listed level 2 children of the current level 1 article. | |
1422 | ||
1423 | =item level2 I<name> | |
1424 | ||
1425 | The value of the I<name> field of the current level 2 article. | |
1426 | ||
1427 | =item ifLevel2 I<name> | |
1428 | ||
1429 | Conditional tag, true if the current level 1 article has any listed | |
1430 | level 2 children. | |
1431 | ||
1432 | =item iterator ... level3 | |
1433 | ||
1434 | Iterates over the listed level 3 children of the current level 2 article. | |
1435 | ||
1436 | =item level3 I<name> | |
1437 | ||
1438 | The value of the I<name> field of the current level 3 article. | |
1439 | ||
1440 | =item ifLevel3 I<name> | |
1441 | ||
1442 | Conditional tag, true if the current level 2 article has any listed | |
1443 | level 3 children. | |
1444 | ||
1445 | =item url I<which> | |
1446 | ||
1447 | Returns a link to the specified article . Due to the way the action | |
1448 | list is built, this can be article types defined in derived classes of | |
1449 | Generate, like the C<parent> article in Generate::Article. | |
1450 | ||
1451 | =item money I<data tag> | |
1452 | ||
1453 | Formats the given value as a monetary value. This does not include a | |
1454 | currency symbol. Internally BSE stores monetary values as integers to | |
1455 | prevent the loss of accuracy inherent in floating point numbers. You | |
1456 | need to use this tag to display any monetary value. | |
1457 | ||
1458 | =item ifInMenu I<which> | |
1459 | ||
1460 | Conditional tag, true if the given item can appear in a menu. | |
1461 | ||
1462 | =item titleImage I<imagename> I<text> | |
1463 | ||
1464 | Generates an IMG tag if the given I<imagename> is in the title image | |
1465 | directory ($IMAGEDIR/titles). If it doesn't exists, produces the | |
1466 | I<text>. | |
1467 | ||
1468 | =item embed I<which> | |
1469 | ||
1470 | =item embed I<which> I<template> | |
1471 | ||
1472 | =item embed I<which> I<template> I<maxdepth> | |
1473 | ||
1474 | =item embed child | |
1475 | ||
1476 | Embeds the article specified by which using either the specified | |
1477 | template or the articles template. | |
1478 | ||
1479 | In this case I<which> can also be an article ID. | |
1480 | ||
1481 | I<template> is a filename relative to the templates directory. If | |
1482 | this is "-" then the articles template is used (so you can set | |
1483 | I<maxdepth> without setting the template.) If I<template> contains a | |
1484 | C<$> sign it will be replaced with the name of the original template. | |
1485 | ||
1486 | If I<maxdepth> is supplied and is less than the current maximum depth | |
1487 | then it becomes the new maximum depth. This can be used with ifCanEmbed. | |
1488 | ||
1489 | =item embed start ... embed end | |
1490 | ||
1491 | Marks the range of text that would be embedded in a parent that used | |
1492 | C<embed child>. | |
1493 | ||
1494 | =item ifEmbedded | |
1495 | ||
1496 | Conditional tag, true if the current article is being embedded. | |
1497 | ||
41358dcc TC |
1498 | =back |
1499 | ||
1500 | =head1 BUGS | |
1501 | ||
1502 | Needs more documentation. | |
1503 | ||
1504 | =cut |