1 package Generate::Article;
4 use Constants qw(%LEVEL_DEFAULTS $CGI_URI $ADMIN_URI $IMAGES_URI
5 $UNLISTED_LEVEL1_IN_CRUMBS);
9 use BSE::Regen qw(generate_button);
10 use BSE::Util::Tags qw(tag_article);
11 use BSE::TB::ArticleFiles;
16 use BSE::Util::Iterate;
18 our $VERSION = "1.003";
20 my $excerptSize = 300;
22 my %level_names = map { $_, $LEVEL_DEFAULTS{$_}{display} }
23 grep { $LEVEL_DEFAULTS{$_}{display} } keys %LEVEL_DEFAULTS;
26 my ($class, %opts) = @_;
28 $opts{top} or confess "Please supply 'top' to $class->new";
30 return $class->SUPER::new(%opts);
35 return "$CGI_URI/admin/add.pl?id=$id";
39 my ($self, $link, $text, $target) = @_;
41 my ($url, $query) = split /\?/, $link;
42 my $form = qq!<form action="$url"!;
43 $form .= qq! target="$target"! if $target;
45 if (defined $query && length $query) {
46 for my $attr (split /&/, $query) {
47 my ($name, $value) = split /=/, $attr, 2;
48 # I'm assuming none of the values are uri escaped
49 $value = escape_html($value);
50 $form .= qq!<input type=hidden name="$name" value="$value" />!
53 $form .= qq!<input type=submit value="!.escape_html($text).'" />';
60 my ($self, $template, $article, $articles, $embedded) = @_;
62 %acts = $self -> baseActs($articles, \%acts, $article, $embedded);
64 my $page = BSE::Template->replace($template, $self->{cfg}, \%acts);
66 %acts = (); # try to destroy any circular refs
72 my ($cfg, $article, $images, $args, $acts, $funcname, $templater) = @_;
74 my $which = $args || 'article';
76 exists $acts->{$which}
77 or return "** no such object $which **";
79 my $title = $templater->perform($acts, $which, 'title');
80 my $imagename = $which eq 'article' ? $article->{titleImage} :
81 $templater->perform($acts, $which, 'titleImage');
82 my $xhtml = $cfg->entry("basic", "xhtml", 1);
84 my $html = qq!<img src="/images/titles/$imagename"!;
85 $html .= ' border="0"' unless $xhtml;
86 $html .= qq! class="bse_image_title" alt="$title" />!;
89 if ($which eq 'article') {
90 ($im) = grep lc $_->{name} eq 'bse_title', @$images;
93 my $id = $templater->perform($acts, $which, 'id');
94 require BSE::TB::Images;
95 my @images = BSE::TB::Images->getBy(articleId=>$id);
96 ($im) = grep lc $_->{name} eq 'bse_title', @$images;
100 my $src = $im->{src} || "/images/$im->{image}";
101 $src = escape_html($src);
102 return qq!<img src="$src" width="$im->{width}"!
103 . qq! height="$im->{height}" alt="$title" class="bse_image_title" />!;
111 my ($self, $article, $embedded) = @_;
113 my $req = $self->{request};
116 <td><form action="$CGI_URI/admin/add.pl" name="edit">
117 <input type=submit value="Edit $level_names{$article->{level}}">
118 <input type=hidden name=id value="$article->{id}">
120 <td><form action="$ADMIN_URI">
121 <input type=submit value="Admin menu">
124 if (exists $level_names{1+$article->{level}}
125 && $req->user_can(edit_add_child=>$article)) {
127 <td><form action="$CGI_URI/admin/add.pl" name="addchild">
128 <input type=submit value="Add $level_names{1+$article->{level}}">
129 <input type=hidden name=parentid value="$article->{id}">
133 if (generate_button() && $req->user_can(regen_article=>$article)) {
135 <td><form action="$CGI_URI/admin/generate.pl" name="regen">
136 <input type=hidden name=id value="$article->{id}">
137 <input type=submit value="Regenerate">
141 $html .= "<td>".$self->link_to_form($article->{admin}."&admin=0",
142 "Display", "_blank")."</td>";
143 my $parent = $article->parent;
144 if ($article->{link}) {
146 . $self->link_to_form($article->{link}, "On site", "_blank")
148 } elsif ($parent && $parent->{link}) {
150 . $self->link_to_form($parent->{link}, "On site", "_blank")
153 if ($parent && $parent->{admin} ne $article->{admin} && !$embedded) {
155 .$self->link_to_form($parent->{admin}, "Parent")."</td>";
164 my ($self, $article) = @_;
166 my $top = $self->{top} || $article;
168 $article->{link} =~ /^\w+:/ || $top->{link} =~ /^\w+:/;
172 my ($self, $article, $default, $embedded, $arg) = @_;
174 $self->{admin} or return '';
175 $self->{request} or return '';
176 my $cfg = $self->{cfg};
178 my $name = $arg || $default;
179 my $template = "admin/adminmenu/$name";
181 unless (BSE::Template->find_source($template, $cfg)) {
182 return $self->_default_admin($article, $embedded);
185 my $parent = $article->parent;
189 $self->{request}->admin_tags,
190 article => [ \&tag_article, $article, $cfg ],
191 parent => [ \&tag_article, $parent, $cfg ],
193 ifEmbedded => $embedded,
196 return BSE::Template->get_page($template, $cfg, \%acts);
200 my ($self, $rcurrent, $images, $args, $acts, $funcname, $templater) = @_;
202 my ($geometry_id, $id, $field) =
203 DevHelp::Tags->get_parms($args, $acts, $templater);
205 return $self->do_thumbimage($geometry_id, $id, $field, $images, $$rcurrent);
209 my ($self, $images, $arg) = @_;
214 elsif ($arg eq 'named') {
215 return grep $_->{name} ne '', @$images;
217 elsif ($arg =~ m!^named\s+/([^/]+)/$!) {
219 return grep $_->{name} =~ /$re/i, @$images;
222 return grep $_->{name} eq '', @$images;
228 =item filen name field
234 Reference an article attached file by name.
236 C<filen name> will display a link to the file.
238 C<<filen name I<field> >> will display the given field from the file
239 record. A I<field> of C<url> will be a URL to the file.
241 If the file identifier given doesn't exist for the current article the
242 empty string is returned, allowing use as ifFilen.
244 The result is unspecified if the I<field> specified isn't one of the
245 image record field names and isn't C<url>.
250 my ($self, $files, $current, $arg, $acts, $funcname, $templater) = @_;
252 my ($name, $field, @rest) =
253 DevHelp::Tags->get_parms($arg, $acts, $templater);
256 or return '* name cannot be an empty string *';
261 or return "* filen - can only be used inside a files iterator *";
266 ($file) = grep $_->{name} eq $name, @$files
270 return $self->_format_file($file, $field, "@rest");
273 =item iterator begin files
275 =item iterator begin files named /foo/
277 =item iterator begin files filter: FILE[file_handler] eq 'flv'
281 Iterate over files attached to the current article.
283 <:file field:> can only access simple attributes.
285 <:filen - field:> can also access any inline representations.
290 my ($self, $files, $arg, $acts, $funcname, $templater) = @_;
295 if ($arg =~ m(^named\s+/([^/]+)/$)) {
297 return grep $_->{name} =~ /$re/i, @$files;
299 if ($arg =~ m(^filter: (.*)$)s) {
301 $expr =~ s/FILE\[(\w+)\]/\$file->$1/g;
302 my $sub = eval 'sub { my $file = shift; ' . $expr . '; }';
304 or die "* Cannot compile sub from filter $expr: $@ *";
305 return grep $sub->($_), @$files;
308 die "* Unknown type of file filter expression *";
311 =item iterator: crumbs/crumb
313 Iterators over the ancestor tree from the article parent to the root.
321 showtop - the top level article is included even if unlisted
325 listedonly - only listed articles in the tree are included
329 The default depends on the value of $Constants::UNLISTED_LEVEL1_IN_CRUMBS.
334 my ($self, $crumbs, $args) = @_;
336 $args ||= $UNLISTED_LEVEL1_IN_CRUMBS ? 'showtop' : 'listedonly';
337 if ($args eq 'showtop') {
341 return grep $_->{listed}, @$crumbs;
345 sub tag_ifUnderThreshold {
346 my ($self, $article, $args) = @_;
349 my $what = $args || '';
350 if ($self->{kids}{$article->{id}}{$what}) {
351 $count = @{$self->{kids}{$article->{id}}{$what}};
354 $count = @{$self->{kids}{$article->{id}}{children}};
357 return $count <= $article->{threshold};
361 my ($self, $articles, $acts, $article, $embedded) = @_;
363 my $cfg = $self->{cfg} || BSE::Cfg->single;
365 # used to generate the list (or not) of children to this article
366 my $child_index = -1;
367 my @children = $articles->listedChildren($article->{id});
369 # used to generate a navigation list for the article
370 # generate a list of ancester articles/sections
371 # Jason calls these breadcrumbs
375 while ($temp->{parentid} > 0
376 and my $crumb = $articles->getByPkey($temp->{parentid})) {
377 unshift(@ancestors, $crumb);
378 unshift(@crumbs, $crumb) if $crumb->{listed} == 1 || $crumb->{level} == 1;
381 #my $crumb_index = -1;
382 #my @work_crumbs; # set by the crumbs iterator
385 my $parent = $articles->getByPkey($article->{parentid});
386 my $section = @crumbs ? $crumbs[0] : $article;
388 my @images = BSE::TB::Images->getBy('articleId', $article->{id});
389 my @unnamed_images = grep $_->{name} eq '', @images;
391 my $image_index = -1;
392 my $had_image_tags = 0;
393 my @all_files = sort { $b->{displayOrder} <=> $a->{displayOrder} }
394 BSE::TB::ArticleFiles->getBy(articleId=>$article->{id});
395 my @files = grep !$_->{hide_from_list}, @all_files;
397 my $blank = qq!<img src="$IMAGES_URI/trans_pixel.gif" width="17" height="13" border="0" align="absbottom" alt="" />!;
399 my $top = $self->{top} || $article;
400 my $abs_urls = $self->abs_urls($article);
402 my $dynamic = $self->{force_dynamic}
403 || (UNIVERSAL::isa($top, 'Article') ? $top->is_dynamic : 0);
408 if (UNIVERSAL::isa($article, 'Article')) {
409 @stepkids = $article->visible_stepkids;
410 @allkids = $article->all_visible_kids;
411 @stepparents = $article->visible_step_parents;
413 $self->{kids}{$article->{id}}{stepkids} = \@stepkids;
414 $self->{kids}{$article->{id}}{allkids} = \@allkids;
415 $self->{kids}{$article->{id}}{children} = \@children;
420 my $art_it = BSE::Util::Iterate::Article->new(cfg =>$cfg, admin => $self->{admin}, top => $self->{top});
421 my $it = BSE::Util::Iterate->new;
422 # separate these so the closures can see %acts
425 $self->SUPER::baseActs($articles, $acts, $article, $embedded),
426 article=>[ \&tag_article, $article, $cfg ],
429 my $which = shift || 'article';
430 return $acts->{$which} && $acts->{$which}->('titleImage')
432 title => [ \&tag_title, $cfg, $article, \@images ],
435 my ($args, $acts, $name, $templater) = @_;
436 my ($which, $class) = split ' ', $args;
437 $which ||= 'article';
438 if ($acts->{$which} &&
439 (my $image = $templater->perform($acts, $which, 'thumbImage'))) {
440 my $width = $templater->perform($acts, $which, 'thumbWidth');
441 my $height = $templater->perform($acts, $which, 'thumbHeight');
442 my $result = '<img src="/images/'.$image
444 .'" height="'.$height.'"';
445 $result .= qq! class="$class"! if $class;
446 $result .= ' border="0" alt="" />';
455 my ($which, $acts, $name, $templater) = @_;
456 $which ||= 'article';
457 return $acts->{$which} &&
458 $templater->perform($acts, $which, 'thumbImage');
461 [ tag_ifUnderThreshold => $self, $article ],
462 ifChildren => sub { scalar @children },
463 iterate_children_reset => sub { $child_index = -1; },
466 return ++$child_index < @children;
470 return tag_article($children[$child_index], $cfg, $_[0]);
473 section=> [ \&tag_article, $section, $cfg ],
475 # these are mostly obsolete, use moveUp and moveDown instead
477 ifPrevChild => sub { $child_index > 0 },
478 ifNextChild => sub { $child_index < $#children },
480 # generate buttons for administration (only for admin generation)
481 admin=> [ tag_admin=>$self, $article, 'article', $embedded ],
483 # transform the article or response body (entities, images)
485 my ($args, $acts, $funcname, $templater) = @_;
486 return $self->format_body(acts => $acts,
487 article => $articles,
488 text => $article->{body},
489 imagepos => $article->{imagePos},
490 abs_urls => $abs_urls,
491 auto_images => !$had_image_tags,
492 templater => $templater,
494 files => \@all_files,
495 articles => $articles);
498 # used to display a navigation path of parent sections
499 $art_it->make_iterator([ iter_crumbs => $self, \@crumbs ],
500 'crumb', 'crumbs', undef, undef,
501 'nocache', \$current_crumb),
505 $cfg->entry('basic', 'warn_obsolete', 0)
506 and print STDERR "* crumbs tag obsolete *\n";
507 return tag_article($current_crumb, $cfg, $_[0]);
511 ifParent => sub { $parent },
513 sub { return $parent && tag_article($parent, $cfg, $_[0]) },
514 # for rearranging order in admin mode
517 @children > 1 or return '';
518 if ($self->{admin} && $child_index < $#children) {
520 <a href="$CGI_URI/admin/move.pl?id=$children[$child_index]{id}&d=down"><img src="$IMAGES_URI/admin/move_down.gif" width="17" height="13" border="0" alt="Move Down" align="bottom" /></a>
530 @children > 1 or return '';
531 if ($self->{admin} && $child_index > 0) {
533 <a href="$CGI_URI/admin/move.pl?id=$children[$child_index]{id}&d=up"><img src="$IMAGES_URI/admin/move_up.gif" width="17" height="13" border="0" alt="Move Up" align="bottom" /></a>
541 movekid => [ \&tag_movekid, $self, \$child_index, \@children, $article ],
544 my ($arg, $acts, $funcname, $templater) = @_;
546 return '' unless $self->{admin};
547 return '' unless @allkids > 1;
548 defined $allkids_index && $allkids_index >= 0 && $allkids_index < @allkids
549 or return '** movestepkid must be inside iterator allkids **';
550 my ($img_prefix, $urladd) =
551 DevHelp::Tags->get_parms($arg, $acts, $templater);
552 $img_prefix = '' unless defined $img_prefix;
553 $urladd = '' unless defined $urladd;
554 my $top = $self->{top} || $article;
555 my $refreshto = $ENV{SCRIPT_NAME} . "?id=$top->{id}$urladd";
557 if ($allkids_index < $#allkids) {
558 $down_url = "$CGI_URI/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids[$allkids_index]{id}&other=$allkids[$allkids_index+1]{id}";
561 if ($allkids_index > 0) {
562 $up_url = "$CGI_URI/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids[$allkids_index]{id}&other=$allkids[$allkids_index-1]{id}";
565 return make_arrows($self->{cfg}, $down_url, $up_url, $refreshto, $img_prefix);
570 $arg && $acts->{$arg} && $acts->{$arg}->('id') == $article->{id};
574 my ($arg, $acts, $name, $templater) = @_;
575 unless ($arg =~ /^\d+$/) {
576 $acts->{$arg} or die "ENOIMPL\n";
577 $arg = $acts->{$arg} && $templater->perform($acts, $arg, 'id')
580 scalar grep $_->{id} == $arg, @ancestors, $article;
582 ifStepAncestor => [ \&tag_ifStepAncestor, $article ],
583 # access to images, if any
584 $it->make_iterator([ iter_images => $self, \@images ], 'image', 'images', \@iter_images, \$image_index, 'nocache', \$current_image),
585 # override the generated image tag
588 my ($which, $align, $rest) = split ' ', $_[0], 3;
592 if (defined $which && $which =~ /^\d+$/ && $which >=1
593 && $which <= @images) {
594 $im = $images[$which-1];
597 $im = $current_image;
600 return $self->_format_image($im, $align, $rest);
604 my ($arg, $acts, $funcname, $templater) = @_;
605 my ($name, $align, @rest) =
606 DevHelp::Tags->get_parms($arg, $acts, $templater);
609 my ($im) = grep lc $name eq lc $_->{name}, @images
612 $self->_format_image($im, $align, $rest);
614 ifImage => sub { $_[0] >= 1 && $_[0] <= @images },
615 thumbimage => [ tag_thumbimage => $self, \$current_image, \@images ],
620 code => [ iter_files => $self, \@files ],
622 store => \$current_file,
624 filen => [ tag_filen => $self, \@files, \$current_file ],
625 BSE::Util::Tags->make_iterator(\@stepkids, 'stepkid', 'stepkids'),
626 $art_it->make_iterator(undef, 'allkid', 'allkids', \@allkids, \$allkids_index),
627 $art_it->make_iterator(undef, 'stepparent', 'stepparents', \@stepparents),
628 top => [ \&tag_article, $self->{top} || $article, $cfg ],
629 ifDynamic => $dynamic,
630 ifStatic => !$dynamic,
631 ifAccessControlled => [ \&tag_ifAccessControlled, $article ],
635 my $oldurl = $acts{url};
636 my $urlbase = $cfg->entryErr('site', 'url');
639 my $value = $oldurl->(@_);
640 return $value if $value =~ /^<:/; # handle "can't do it"
641 unless ($value =~ /^\w+:/) {
642 # put in the base site url
643 $value = $urlbase . $value;
648 if ($dynamic && $cfg->entry('basic', 'ajax', 0)) {
649 # make sure the ajax tags are left until we do dynamic replacement
650 delete @acts{qw/ajax ifAjax/};
656 sub tag_ifStepAncestor {
657 my ($article, $arg, $acts, $name, $templater) = @_;
659 unless ($arg =~ /^\d+$/) {
660 $acts->{$arg} or die "ENOIMPL\n";
661 $arg = $acts->{$arg} && $templater->perform($acts, $arg, 'id')
664 return 0 if $article->{id} < 0;
665 return $article->{id} == $arg || $article->is_step_ancestor($arg);
669 my ($self, $top) = @_;
671 # this is to support pregenerated pages being handled as dynamic pages
672 $self->{force_dynamic} and return 1;
674 UNIVERSAL::isa($top, 'Article') ? $top->is_dynamic : 0;
677 sub tag_ifAccessControlled {
678 my ($article, $arg, $acts, $funcname, $templater) = @_;
682 my $id = $templater->perform($acts, $arg, 'id');
683 $article = Articles->getByPkey($id);
685 print STDERR "** Unknown article $id from $arg in ifAccessControlled\n";
690 print STDERR "** Unknown article $arg in ifAccessControlled\n";
695 return UNIVERSAL::isa($article, 'Article') ?
696 $article->is_access_controlled : 0;
700 my ($self, $image_id, $images) = @_;
703 if ($image_id =~ /^\d+$/) {
704 $image_id >= 1 && $image_id <= @$images
705 or return ( undef, "* Out of range image index '$image_id' *" );
707 $im = $images->[$image_id-1];
709 elsif ($image_id =~ /^[^\W\d]\w*$/) {
710 ($im) = grep $_->{name} eq $image_id, @$images
711 or return ( undef, "* Unknown image identifier '$image_id' *" );
714 return ( undef, "* Unrecognized image '$image_id' *" );
721 my ($self, $image_id, $class, $images) = @_;
723 my ($im, $msg) = $self->get_image($image_id, $images);
727 return $self->do_popimage_low($im, $class);
730 # note: this is called by BSE::Formatter::thumbimage(), update that if
733 my ($self, $geo_id, $image_id, $field, $images, $current) = @_;
736 if ($image_id eq '-' && $current) {
738 or return "** No current image in images iterator **"
741 ($im, my $msg) = $self->get_image($image_id, $images);
746 return $self->_sthumbimage_low($geo_id, $im, $field);
750 my ($self, $article, $articles) = @_;
752 my $html = BSE::Template->get_source($article->{template}, $self->{cfg});
753 $html =~ s/<:\s*embed\s+(?:start|end)\s*:>//g;
755 return $self->generate_low($html, $article, $articles, 0);
759 my ($self, $rindex, $rchildren, $article, $args, $acts,
760 $funcname, $templater) = @_;
762 $self->{admin} or return '';
763 @$rchildren or return '';
765 my ($img_prefix, $urladd) =
766 DevHelp::Tags->get_parms($args, $acts, $templater);
767 defined $img_prefix or $img_prefix = '';
768 defined $urladd or $urladd = '';
770 my $top = $self->{top} || $article;
771 my $refreshto = $ENV{SCRIPT_NAME} . "?id=$top->{id}$urladd";
773 if ($$rindex < $#$rchildren) {
774 $down_url = "$CGI_URI/admin/move.pl?id=$rchildren->[$$rindex]{id}&d=down";
778 $up_url = "$CGI_URI/admin/move.pl?id=$rchildren->[$$rindex]{id}&d=up";
781 return make_arrows($self->{cfg}, $down_url, $up_url, $refreshto, $img_prefix);
785 my ($self, $article_id, $article, @rest) = @_;
787 if ($article_id eq 'article') {
790 elsif ($article_id eq 'children') {
791 return $article->all_visible_kids;
793 elsif ($article_id eq 'parent') {
794 return $article->parent;
797 return $self->SUPER::_find_articles($article_id, $article, @rest);
807 Generate::Article - generates articles.
818 In your HTML each tag will be preceded by <: and followed by :>
820 Tags marked as conditional will require a little more. Conditional
821 tags can be used in two ways:
823 <:ifName args:>true text<:or:>false text<:eif:>
827 <:if Name args:>true text<:or Name:>false text<:eif Name:>
829 Tags starting iterator ... are used as iterators, like:
831 <:iterator begin name:>
833 <:iterator separator name:>
835 <:iterator end name:>
837 In general, a parameter I<which> can be any one of 'article', 'parent'
838 or 'section'. In a child iterator it can also be 'child'. In a
839 crumbs iterator it can also be 'crumbs'. If I<which> is missing it
840 means the current article.
846 =item article I<name>
848 Access to fields of the article. See L<Article attributes>.
852 Access to fields of the parent article. See L<Article attributes>.
856 Conditional tag, true if there is a parent.
858 =item section I<name>
860 Access to the fields of the section containing the article. See
861 L<Article attributes>.
865 The title of the article presented as an image if there is a
866 titleImage or as text. See L<Tag notes> for values for which.
868 =item ifTitleImage I<which>
870 Conditional tag, true if the given article has a titleImage,
872 =item thumbnail I<which> I<class>
874 The thumbnail image as an <img> tag for object I<which> where I<which>
875 is one of the article objects defined. The optional I<class> adds a
876 class attribute to the tag with that class.
878 =item ifThumbnail I<which>
880 Conditional tag, true if the article specified by I<which> has a
883 =item ifUnderThreshold
885 =item ifUnderThreshold stepkids
887 =item ifUnderThreshold allkids
889 Conditional tag, true if the number of children/stepkids/allkids is
890 less or equal to the article's threshold.
894 The formatted body of the article.
900 =item iterator ... crumbs [option]
902 Iterates over the ancestors of the article. See the L</item crumbs>.
904 I<option> can be empty, "listedonly" or "showtop". If empty the
905 display of an unlisted level1 ancestor is controlled by
906 $UNLISTED_LEVEL1_IN_CRUMBS, if "listedonly" then an unlisted level1
907 article isn't shown in the crumbs, and it is if "showtop" is the
908 I<option>. This can be used in <: ifCrumbs :> too.
912 Access to the fields of the specific ancestor. I<name> can be any of
913 the L<Article attributes>.
915 =item ifCrumbs [options]
917 Conditional tag, true if there are any crumbs.
919 See L</iterator ... crumbs [option]> for information on I<option>.
923 Conditional tag, true if the article has any children.
925 =item iterator ... children
927 Iterates over the children of the article. See the L</item child>.
931 Access to the fields of the current child.
935 Produces a processed summary of the current child's body.
939 Conditional tag, true if there is a previous child. Originally used
940 for generating a move up link, but you can use the moveUp tag for
945 Conditional tag, true if there is a next child. Originally used to
946 generating a move down link, but you can use the moveDown tag for that
949 =item ifCurrentPage I<which>
951 Conditional tag, true if the given I<which> is the page currently
952 being generated. This can be used to apply special formatting if a
953 C<level1> or C<level2> article is the current page.
955 =item iterator ... images
957 Iterates over the unnamed images for the given article.
959 =item iterator ... images all
961 Iterates over all images for the article.
963 =item iterator ... images named
965 Iterates over the named images for the article.
967 =item iterator ... images named /regexp/
969 Iterates over images with names matching the given regular expression.
970 Note that if the expression matches an empty string then unnamed
971 images will be included.
973 =item image which field
975 Extracts the given field from the specified image.
977 I<which> in this can be either an image index to access a specific
978 image, or "-" to access the current image in the images iterator.
980 The image fields are:
986 The identifier of the article the image belongs to.
990 A partial url of the image, relative to /images/.
994 Alternative text of the image.
1000 dimensions of the image.
1004 the url if any associated with the image
1008 =item image which align
1014 Produces HTML to render the given image.
1016 I<which> can be an image index (1, 2, 3...) to select one of the
1017 images from the current article, or '-' or omitted to select the
1018 current image from the images iterator. If align is present then the
1019 C<align> attribute of the image is set.
1021 If the image has a URL that <a href="...">...</a> will also be
1024 =item ifImage imageindex
1026 Condition tag, true if an image exists at the given index.
1032 Conditional tag, true if the article has any images.
1034 =item ifImages named
1036 Conditional tag, true if the article has any named images.
1038 =item ifImages named /regexp/
1040 Conditional tag, true if the article has any named images, where the
1041 name matches the regular expression.
1043 =item ifImages unnamed
1045 Conditional tag, true if the article has any unnamed images.
1049 This has been made more general and been moved, see L<Generate/embed child>.
1053 Tests if the article is dynamically generated.
1057 Toplevel article being generated. This is the page that any articles
1058 are being embedded in.
1060 =item iterator ... files
1062 Iterates over the files attached to the article, setting the file tag.
1066 Information from the current file in the files iterator.
1068 The file fields are:
1074 id - identifier for this file
1078 articleId - article this file belongs to
1082 displayName - the filename of the file as displayed
1086 filename - filename of the file as stored on disk,
1090 sizeInBytes - size of the file in bytes.
1094 description - the entered description of the file
1098 contentType - the MIME content type of the file
1102 displayOrder - number used to control the listing order.
1106 forSale - non-zero if the file needs to be paid for to be downloaded.
1110 download - if this is non-zero BSE will attempt to make the browser
1111 download the file rather than display it.
1115 whenUploaded - date/time when the file was uploaded.
1119 requireUser - if non-zero the user must be logged on to download this
1124 notes - longer descriptive text.
1128 name - identifier for the file for filelink[]
1132 hide_from_list - if non-zero the file won't be listed by the files
1133 iterator, but will still be available to filelink[].
1139 =head2 Article attributes
1145 Identifies the article.
1149 The identifier of the parent article.
1153 The title of the article. See the title tag
1157 The name of the title image for the article, if any. See the title
1158 and ifTitleImage tags.
1162 The body of the article. See the body tag.
1170 The thumbnail image for the article, if any. See the thumbnail tag.
1176 The release and expiry dates of the article.
1180 Any keywords for the article. Indexed by the search engine.
1186 Links to the normal and adminstrative versions of the article. The
1187 url tag defined by Generate.pm will select the appropriate tag for the
1192 The maximum number of articles that should be embeded into the current
1193 article for display. See the ifUnderThreshold tag.
1197 The maximum amount of text displayed in the summary of an article.
1198 See the summary tag.
1202 The class used to generate the article. Should be one of
1203 Generate::Article, Generate::Catalog or Generate::Product.
1207 The level of the article. Sections are level1, etc
1211 How the article is listed. If zero then the article can only be found
1212 in a search. If 1 then the article is listed in menus and article
1213 contents, if 2 then the article is only listed in article contents.
1217 When the article was last modified. Currently only used for display
1220 =item lastModifiedBy
1222 Set the the current admin user logon if access_control is enabled in the cfg.
1226 Set to the current date time when a new article is created.
1230 Set to the current admin user logon if access_control is enabled in
1235 A user definable field for attributing the article author.
1239 An alternate article title which can be used to make search engine
1242 =item metaDescription
1244 Article metadata description, used as metadata in the generated HTML
1249 Article metadata keywords, used as metadata in the generated HTML
1254 The following attributes are unlikely to be used in a page:
1260 Used internally to control the ordering of articles within a section.
1264 The position of the first image in the body. The body tag will format
1265 images into the body as specified by this tag.
1269 The template used to format the article.
1273 Flags which can be checked by code or template tags to control behaviours
1274 specific the the article.
1278 Forces a page to be displayed dynamically with page.pl regardless of
1281 =item inherit_siteuser_rights
1283 Controls whether the article inherits its parents access controls.
1291 The following tags produce output only in admin mode.
1295 Produces buttons and links used for administering the article.
1299 Generates a move up link if there is a previous child for the current
1304 Generates a move down link if there is a next child for the current child.
1308 Produces buttons and links used for administering the article.
1310 This tag can use a specialized template if it's available. If you
1311 call it with a parameter, <:admin I<template>:> then it will use
1312 template C<< admin/adminmenu/I<template>.tmpl >>. When used in an
1313 article template C<< <:admin:> >> behaves like C<< <:admin article:>
1314 >>, when used in a catalog template C<< <:admin:> >> behaves like C<<
1315 <:admin catalog:> >>, when used in a product template C<< <:admin:> >>
1316 behaves like C<< <:admin product:> >>. See L<Admin Menu Templates>
1317 for the tags available in admin menu templates.
1319 If the template doesn't exist then the old behaviour applies.
1323 =head2 Admin Menu Templates
1325 These tags can be used in the templates included by the C<< <:admin:>
1328 The basic template tags and ifUserCan tag can be used in admin menu
1335 Retrieves a field from the current article.
1339 Retrieves a field from the parent of the current article.
1343 Conditional tag, true if the current article has a parent.
1347 Conditional tag, true if the current article is embedded in another
1348 article, in this context.