allow use of the new template system from static pages
[bse.git] / site / cgi-bin / modules / Generate.pm
CommitLineData
41358dcc
TC
1package Generate;
2use strict;
3use Articles;
aefcabcb 4use Constants qw($IMAGEDIR $LOCAL_FORMAT $BODY_EMBED
d38f3b10 5 $EMBED_MAX_DEPTH $HAVE_HTML_PARSER);
7928764a 6use DevHelp::Tags;
3f9c8a96 7use BSE::Util::HTML;
c76e86ea 8use BSE::Util::Tags qw(tag_article);
00dd8d82 9use BSE::CfgInfo qw(custom_class);
b873a8fa 10use BSE::Util::Iterate;
599fe373 11use BSE::TB::Site;
b864cc90 12use base 'BSE::ThumbLow';
47c75494 13use base 'BSE::TagFormats';
41358dcc 14
599fe373 15our $VERSION = "1.007";
cb7fd78d 16
41358dcc
TC
17my $excerptSize = 300;
18
19sub 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
53sub cfg {
54 $_[0]{cfg};
55}
56
599fe373
TC
57sub set_variable {
58 my ($self, $name, $value) = @_;
59
60 $self->{vars}{$name} = $value;
61
62 return 1;
63}
64
65sub 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
72sub 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>
81sub 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/\&#8217;/'/g;
41358dcc
TC
88
89 return $text;
90}
91
721cd24c 92sub 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
125sub 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)
146sub _make_html {
918735d1 147 return unescape_html($_[0]);
8aee8e95
TC
148}
149
745d2b57 150sub _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
227sub _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
235sub formatter_class {
236 require BSE::Formatter::Article;
237 return 'BSE::Formatter::Article'
238}
239
41358dcc
TC
240# replace markup, insert img tags
241sub 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
330sub 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 353sub 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
372my $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
408sub _do_filter {
409 my ($self, $filter, @articles) = @_;
410
411 $filter
412 or return @articles;
413
414 return grep $filter->($_), @articles;
415}
416
aefcabcb 417sub 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 435sub 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
453sub 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
474sub 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
502sub admin_tags {
503 my ($self) = @_;
504
505 $self->{admin} or return;
506
507 return BSE::Util::Tags->secure($self->{request});
508}
509
8a153d74
TC
510sub _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
521sub 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
533sub 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
542sub _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 548sub 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
556sub _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
613sub 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
625sub 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
641Iterates over the images belonging to the articles specified.
642
643I<articles> can be any of:
644
645=over
646
647=item *
648
649article - the current article
650
651=item *
652
653children - all visible children (including stepkids) of the current
654article
655
656=item *
657
658parent - the parent of the current article
659
660=item *
661
662I<number> - a numeric article id, such as C<10>.
663
664=item *
665
666alias(I<alias>) - a link alias of an article
667
668=item *
669
670childrenof(I<articles>) - an articles that are children of
671I<articles>. I<articles> can be any normal article spec, so
672C<childrenof(childrenof(-1))> is valid.
673
674=item *
675
676I<tagname> - a tag name referring to an article.
677
678=back
679
6ed138ca
TC
680I<articles> has [] replacement done before parsing.
681
47c75494
TC
682I<filter> can be missing, or either of:
683
684=over
685
686=item *
687
688named /I<regexp>/ - images with names matching the given regular
689expression
690
691=item *
692
693numbered I<number> - images with the given index.
694
695=back
696
697Items for this iterator are vimage and vthumbimage.
698
699=cut
700
701sub 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
739Retrieve the given field from the current vimage, or display the image.
740
741=cut
742
743sub 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
757Retrieve the given field from the thumbnail of the current vimage or
758display the thumbnail.
759
760=cut
761
762sub 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
771sub _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 806sub 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
1028sub 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
1053sub _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
1064sub 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
1159sub 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
1166sub 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
1182sub 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
1195sub 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
1209sub 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
1225sub get_real_article {
1226 my ($self, $article) = @_;
1227
1228 return $article;
1229}
1230
41358dcc
TC
12311;
1232
8aee8e95
TC
1233__END__
1234
41358dcc
TC
1235=head1 NAME
1236
1237Generate - provides base Squirel::Template actions for use in generating
1238pages.
1239
1240=head1 SYNOPSIS
1241
1242=head1 DESCRIPTION
1243
fa304705 1244This is probably better documented in L<templates.pod>.
8aee8e95 1245
d2730773 1246=head1 COMMON TAGS
8aee8e95 1247
d2730773
TC
1248These tags can be used anywhere, including in admin templates. It's
1249possible some admin code has been missed, if you find a place where
1250these cannot be used let us know.
8aee8e95 1251
8aee8e95 1252
d2730773 1253=over
8aee8e95 1254
957a90ca
TC
1255=item kb I<data tag>
1256
1257Formats the give value in kI<whatevers>. If you have a number that
1258could go over 1000 and you want it to use the 'k' metric prefix when
1259it 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
1265Formats a date or date/time value from the database into something
1266more human readable. If you don't supply a format then the default
1267format of "%d-%b-%Y" is used ("20-Mar-2002").
1268
1269The I<format> is a strftime() format specification, if that means
1270anything to you. If it doesn't, each code starts with % and are
1271replaced as follows:
1272
1273=over
1274
1275=item %a
1276
1277abbreviated weekday name
1278
1279=item %A
1280
1281full weekday name
1282
1283=item %b
1284
1285abbreviated month name
1286
1287=item %B
1288
1289full month name
1290
1291=item %c
1292
1293"preferred" date and time representation
1294
1295=item %d
1296
1297day of the month as a 2 digit number
1298
1299=item %H
1300
1301hour (24-hour clock)
1302
1303=item %I
1304
1305hour (12-hour clock)
1306
1307=item %j
1308
1309day of year as a 3-digit number
1310
1311=item %m
1312
1313month as a 2 digit number
1314
1315=item %M
1316
1317minute as a 2 digit number
1318
1319=item %p
1320
1321AM or PM or their equivalents
1322
1323=item %S
1324
1325seconds as a 2 digit number
1326
1327=item %U
1328
1329week number as a 2 digit number (first Sunday as the first day of week 1)
1330
1331=item %w
1332
1333weekday as a decimal number (0-6)
1334
1335=item %W
1336
1337week number as a 2 digit number (first Monday as the first day of week 1)
1338
1339=item %x
1340
1341the locale's appropriate date representation
1342
1343=item %X
1344
1345the locale's appropriate time representation
1346
1347=item %y
1348
13492-digit year without century
1350
1351=item %Y
1352
1353the full year
1354
1355=item %Z
1356
1357time zone name or abbreviation
1358
1359=item %%
1360
1361just '%'
1362
1363=back
1364
1365Your local strftime() implementation may implement some extensions to
1366the above, if your server is on a Unix system try running "man
1367strftime" for more information.
1368
1369=item bodytext I<data tag>
1370
1371Formats the text from the given tag in the same way that body text is.
1372
1373=item ifEq I<data1> I<data2>
1374
1375Checks if the 2 values are exactly equal. This is a string
1376comparison.
1377
1378The 2 data parameters can either be a tag reference in [], a literal
1379string inside "" or a single word.
1380
1381=item ifMatch I<data1> I<data2>
1382
1383Treats I<data2> as a perl regular expression and attempts to match
1384I<data1> against it.
1385
1386The 2 data parameters can either be a tag reference in [], a literal
1387string inside "" or a single word.
1388
1389=item cfg I<section> I<key>
1390
1391=item cfg I<section> I<key> I<default>
1392
1393Retrieves a value from the BSE configuration file.
1394
1395If you don't supply a default then a default will be the empty string.
1396
d2730773
TC
1397=item release
1398
1399The release number of BSE.
1400
1401=back
1402
1403=head1 TAGS
1404
1405=over 4
1406
1407=item ifAdmin
1408
1409Conditional tag, true if generating in admin mode.
1410
1411=item iterator ... level1
1412
1413Iterates over the listed level 1 articles.
1414
1415=item level1 I<name>
1416
1417The value of the I<name> field of the current level 1 article.
1418
1419=item iterator ... level2
1420
1421Iterates over the listed level 2 children of the current level 1 article.
1422
1423=item level2 I<name>
1424
1425The value of the I<name> field of the current level 2 article.
1426
1427=item ifLevel2 I<name>
1428
1429Conditional tag, true if the current level 1 article has any listed
1430level 2 children.
1431
1432=item iterator ... level3
1433
1434Iterates over the listed level 3 children of the current level 2 article.
1435
1436=item level3 I<name>
1437
1438The value of the I<name> field of the current level 3 article.
1439
1440=item ifLevel3 I<name>
1441
1442Conditional tag, true if the current level 2 article has any listed
1443level 3 children.
1444
1445=item url I<which>
1446
1447Returns a link to the specified article . Due to the way the action
1448list is built, this can be article types defined in derived classes of
1449Generate, like the C<parent> article in Generate::Article.
1450
1451=item money I<data tag>
1452
1453Formats the given value as a monetary value. This does not include a
1454currency symbol. Internally BSE stores monetary values as integers to
1455prevent the loss of accuracy inherent in floating point numbers. You
1456need to use this tag to display any monetary value.
1457
1458=item ifInMenu I<which>
1459
1460Conditional tag, true if the given item can appear in a menu.
1461
1462=item titleImage I<imagename> I<text>
1463
1464Generates an IMG tag if the given I<imagename> is in the title image
1465directory ($IMAGEDIR/titles). If it doesn't exists, produces the
1466I<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
1476Embeds the article specified by which using either the specified
1477template or the articles template.
1478
1479In this case I<which> can also be an article ID.
1480
1481I<template> is a filename relative to the templates directory. If
1482this is "-" then the articles template is used (so you can set
1483I<maxdepth> without setting the template.) If I<template> contains a
1484C<$> sign it will be replaced with the name of the original template.
1485
1486If I<maxdepth> is supplied and is less than the current maximum depth
1487then it becomes the new maximum depth. This can be used with ifCanEmbed.
1488
1489=item embed start ... embed end
1490
1491Marks the range of text that would be embedded in a parent that used
1492C<embed child>.
1493
1494=item ifEmbedded
1495
1496Conditional tag, true if the current article is being embedded.
1497
41358dcc
TC
1498=back
1499
1500=head1 BUGS
1501
1502Needs more documentation.
1503
1504=cut