]> git.imager.perl.org - bse.git/blob - site/cgi-bin/modules/Generate/Article.pm
almost double the speed of template generation
[bse.git] / site / cgi-bin / modules / Generate / Article.pm
1 package Generate::Article;
2 use strict;
3 use BSE::Template;
4 use Constants qw(%LEVEL_DEFAULTS $CGI_URI $ADMIN_URI $IMAGES_URI 
5                  $UNLISTED_LEVEL1_IN_CRUMBS);
6 use Images;
7 use vars qw(@ISA);
8 use Generate;
9 use Util qw(generate_button);
10 use BSE::Util::Tags qw(tag_article);
11 use ArticleFiles;
12 @ISA = qw/Generate/;
13 use DevHelp::HTML;
14 use BSE::Arrows;
15 use Carp 'confess';
16 use BSE::Util::Iterate;
17
18 my $excerptSize = 300;
19
20 my %level_names = map { $_, $LEVEL_DEFAULTS{$_}{display} }
21   grep { $LEVEL_DEFAULTS{$_}{display} } keys %LEVEL_DEFAULTS;
22
23 sub new {
24   my ($class, %opts) = @_;
25
26   $opts{top} or confess "Please supply 'top' to $class->new";
27
28   return $class->SUPER::new(%opts);
29 }
30
31 sub edit_link {
32   my ($self, $id) = @_;
33   return "$CGI_URI/admin/add.pl?id=$id";
34 }
35
36 sub link_to_form {
37   my ($self, $link, $text, $target) = @_;
38
39   my ($url, $query) = split /\?/, $link;
40   my $form = qq!<form action="$url"!;
41   $form .= qq! target="$target"! if $target;
42   $form .= '>';
43   if (defined $query && length $query) {
44     for my $attr (split /&/, $query) {
45       my ($name, $value) = split /=/, $attr, 2;
46       # I'm assuming none of the values are uri escaped
47       $value = escape_html($value);
48       $form .= qq!<input type=hidden name="$name" value="$value" />!
49     }
50   }
51   $form .= qq!<input type=submit value="!.escape_html($text).'" />';
52   $form .= "</form>";
53
54   return $form;
55 }
56
57 sub generate_low {
58   my ($self, $template, $article, $articles, $embedded) = @_;
59   my %acts;
60   %acts = $self -> baseActs($articles, \%acts, $article, $embedded);
61
62   my $page = BSE::Template->replace($template, $self->{cfg}, \%acts);
63
64   %acts = (); # try to destroy any circular refs
65
66   return $page;
67 }
68
69 sub tag_title {
70   my ($cfg, $article, $images, $args, $acts, $funcname, $templater) = @_;
71
72   my $which = $args || 'article';
73
74   exists $acts->{$which} 
75     or return "** no such object $which **";
76
77   my $title = $templater->perform($acts, $which, 'title');
78   my $imagename = $which eq 'article' ? $article->{titleImage} : 
79     $templater->perform($acts, $which, 'titleImage');
80   my $xhtml = $cfg->entry("basic", "xhtml", 1);
81   if ($imagename) {
82     my $html = qq!<img src="/images/titles/$imagename"!;
83     $html .= ' border="0"' unless $xhtml;
84     $html .= qq! class="bse_image_title" alt="$title" />!;
85   }
86   my $im;
87   if ($which eq 'article') {
88     ($im) = grep lc $_->{name} eq 'bse_title', @$images;
89   }
90   else {
91     my $id = $templater->perform($acts, $which, 'id');
92     require Images;
93     my @images = Images->getBy(articleId=>$id);
94     ($im) = grep lc $_->{name} eq 'bse_title', @$images;
95   }
96
97   if ($im) {
98     my $src = $im->{src} || "/images/$im->{image}";
99     $src = escape_html($src);
100     return qq!<img src="$src" width="$im->{width}"!
101       . qq! height="$im->{height}" alt="$title" class="bse_image_title" />!;
102   }
103   else {
104     return $title;
105   }
106 }
107
108 sub _default_admin {
109   my ($self, $article, $embedded) = @_;
110
111   my $req = $self->{request};
112   my $html = <<HTML;
113 <table><tr>
114 <td><form action="$CGI_URI/admin/add.pl" name="edit">
115 <input type=submit value="Edit $level_names{$article->{level}}">
116 <input type=hidden name=id value="$article->{id}">
117 </form></td>
118 <td><form action="$ADMIN_URI">
119 <input type=submit value="Admin menu">
120 </form></td>
121 HTML
122   if (exists $level_names{1+$article->{level}}
123       && $req->user_can(edit_add_child=>$article)) {
124     $html .= <<HTML;
125 <td><form action="$CGI_URI/admin/add.pl" name="addchild">
126 <input type=submit value="Add $level_names{1+$article->{level}}">
127 <input type=hidden name=parentid value="$article->{id}">
128 </form></td>
129 HTML
130   }
131   if (generate_button() && $req->user_can(regen_article=>$article)) {
132     $html .= <<HTML;
133 <td><form action="$CGI_URI/admin/generate.pl" name="regen">
134 <input type=hidden name=id value="$article->{id}">
135 <input type=submit value="Regenerate">
136 </form></td>
137 HTML
138   }
139   $html .= "<td>".$self->link_to_form($article->{admin}."&admin=0",
140                                       "Display", "_blank")."</td>";
141   my $parent = $article->parent;
142   if ($article->{link}) {
143     $html .= "<td>"
144       . $self->link_to_form($article->{link}, "On site", "_blank")
145         . "</td>";
146   } elsif ($parent && $parent->{link}) {
147     $html .= "<td>"
148       . $self->link_to_form($parent->{link}, "On site", "_blank")
149         . "</td>";
150   }
151   if ($parent && $parent->{admin} ne $article->{admin} && !$embedded) {
152     $html .= "<td>"
153       .$self->link_to_form($parent->{admin}, "Parent")."</td>";
154   }
155   $html .= <<HTML;
156 </tr></table>
157 HTML
158   return $html;
159 }
160
161 sub abs_urls {
162   my ($self, $article) = @_;
163
164   my $top = $self->{top} || $article;
165
166   $article->{link} =~ /^\w+:/ || $top->{link} =~ /^\w+:/;
167 }
168
169 sub tag_admin {
170   my ($self, $article, $default, $embedded, $arg) = @_;
171
172   $self->{admin} or return '';
173   $self->{request} or return '';
174   my $cfg = $self->{cfg};
175
176   my $name = $arg || $default;
177   my $template = "admin/adminmenu/$name";
178
179   unless (BSE::Template->find_source($template, $cfg)) {
180     return $self->_default_admin($article, $embedded);
181   }
182
183   my $parent = $article->parent;
184   my %acts;
185   %acts =
186     (
187      BSE::Util::Tags->static(\%acts, $cfg),
188      BSE::Util::Tags->admin(\%acts, $cfg),
189      BSE::Util::Tags->secure($self->{request}),
190      article => [ \&tag_article, $article, $cfg ],
191      parent => [ \&tag_article, $parent, $cfg ],
192      ifParent => $parent,
193      ifEmbedded => $embedded,
194     );
195
196   return BSE::Template->get_page($template, $cfg, \%acts);
197 }
198
199 sub tag_thumbimage {
200   my ($self, $rcurrent, $images, $args, $acts, $funcname, $templater) = @_;
201
202   my ($geometry_id, $id, $field) = 
203     DevHelp::Tags->get_parms($args, $acts, $templater);
204
205   return $self->do_thumbimage($geometry_id, $id, $field, $images, $$rcurrent);
206 }
207
208 sub iter_images {
209   my ($self, $images, $arg) = @_;
210
211   if ($arg eq 'all') {
212     return @$images;
213   }
214   elsif ($arg eq 'named') {
215     return grep $_->{name} ne '', @$images;
216   }
217   elsif ($arg =~ m!^named\s+/([^/]+)/$!) {
218     my $re = $1;
219     return grep $_->{name} =~ /$re/i, @$images;
220   }
221   else {
222     return grep $_->{name} eq '', @$images;
223   }
224 }
225
226 =item filen name
227
228 =item filen name field
229
230 Reference an article attached file by name.
231
232 C<filen name> will display a link to the file.
233
234 C<<filen name I<field> >> will display the given field from the file
235 record.  A I<field> of C<url> will be a URL to the file.
236
237 If the file identifier given doesn't exist for the current article the
238 empty string is returned, allowing use as ifFilen.
239
240 The result is unspecified if the I<field> specified isn't one of the
241 image record field names and isn't C<url>.
242
243 =cut
244
245 sub tag_filen {
246   my ($self, $files, $arg, $acts, $funcname, $templater) = @_;
247
248   my ($name, $field, @rest) = 
249     DevHelp::Tags->get_parms($arg, $acts, $templater);
250
251   length $name
252     or return '* name cannot be an empty string *';
253
254   my ($file) = grep $_->{name} eq $name, @$files
255     or return '';
256
257   return $self->_format_file($file, $field);
258 }
259
260 =item iterator: crumbs/crumb
261
262 Iterators over the ancestor tree from the article parent to the root.
263
264 Parameters include:
265
266 =over
267
268 =item *
269
270 showtop - the top level article is included even if unlisted
271
272 =item *
273
274 listedonly - only listed articles in the tree are included
275
276 =back
277
278 The default depends on the value of $Constants::UNLISTED_LEVEL1_IN_CRUMBS.
279
280 =cut
281
282 sub iter_crumbs {
283   my ($self, $crumbs, $args) = @_;
284
285   $args ||= $UNLISTED_LEVEL1_IN_CRUMBS ? 'showtop' : 'listedonly';
286   if ($args eq 'showtop') {
287     return @$crumbs;
288   }
289   else {
290     return grep $_->{listed}, @$crumbs;
291   }
292 }
293
294 sub baseActs {
295   my ($self, $articles, $acts, $article, $embedded) = @_;
296
297   my $cfg = $self->{cfg} || BSE::Cfg->new;
298
299   # used to generate the list (or not) of children to this article
300   my $child_index = -1;
301   my @children = $articles->listedChildren($article->{id});
302
303   # used to generate a navigation list for the article
304   # generate a list of ancester articles/sections
305   # Jason calls these breadcrumbs
306   my @crumbs;
307   my @ancestors;
308   my $temp = $article;
309   while ($temp->{parentid} > 0
310         and my $crumb = $articles->getByPkey($temp->{parentid})) {
311     unshift(@ancestors, $crumb);
312     unshift(@crumbs, $crumb) if $crumb->{listed} == 1 || $crumb->{level} == 1;
313     $temp = $crumb;
314   }
315   #my $crumb_index = -1;
316   #my @work_crumbs; # set by the crumbs iterator
317   my $current_crumb;
318
319   my $parent = $articles->getByPkey($article->{parentid});
320   my $section = @crumbs ? $crumbs[0] : $article;
321
322   my @images = Images->getBy('articleId', $article->{id});
323   my @unnamed_images = grep $_->{name} eq '', @images;
324   my @iter_images;
325   my $image_index = -1;
326   my $had_image_tags = 0;
327   my @all_files = sort { $b->{displayOrder} <=> $a->{displayOrder} }
328     ArticleFiles->getBy(articleId=>$article->{id});
329   my @files = grep !$_->{hide_from_list}, @all_files;
330   
331   my $blank = qq!<img src="$IMAGES_URI/trans_pixel.gif"  width="17" height="13" border="0" align="absbottom" alt="" />!;
332
333   my $top = $self->{top} || $article;
334   my $abs_urls = $self->abs_urls($article);
335
336   my $dynamic = $self->{force_dynamic}
337     || (UNIVERSAL::isa($top, 'Article') ? $top->is_dynamic : 0);
338
339   my @stepkids;
340   my @allkids;
341   my @stepparents;
342   if (UNIVERSAL::isa($article, 'Article')) {
343     @stepkids     = $article->visible_stepkids;
344     @allkids      = $article->all_visible_kids;
345     @stepparents  = $article->visible_step_parents;
346   }
347   my $allkids_index;
348   my $current_image;
349   my $art_it = BSE::Util::Iterate::Article->new(cfg =>$cfg, admin => $self->{admin}, top => $self->{top});
350   my $it = BSE::Util::Iterate->new;
351   # separate these so the closures can see %acts
352   my %acts =
353     (
354      $self->SUPER::baseActs($articles, $acts, $article, $embedded),
355      article=>[ \&tag_article, $article, $cfg ],
356      ifTitleImage => 
357      sub { 
358        my $which = shift || 'article';
359        return $acts->{$which} && $acts->{$which}->('titleImage')
360      },
361      title => [ \&tag_title, $cfg, $article, \@images ],
362      thumbnail =>
363      sub {
364        my ($args, $acts, $name, $templater) = @_;
365        my ($which, $class) = split ' ', $args;
366        $which ||= 'article';
367        if ($acts->{$which} && 
368            (my $image = $templater->perform($acts, $which, 'thumbImage'))) {
369          my $width = $templater->perform($acts, $which, 'thumbWidth');
370          my $height = $templater->perform($acts, $which, 'thumbHeight');
371          my $result = '<img src="/images/'.$image
372            .'" width="'.$width
373              .'" height="'.$height.'"';
374          $result .= qq! class="$class"! if $class;
375          $result .= ' border="0" alt="" />';
376          return $result;
377        }
378        else {
379          return '';
380        }
381      },
382      ifThumbnail =>
383      sub {
384        my ($which, $acts, $name, $templater) = @_;
385        $which ||= 'article';
386        return $acts->{$which} && 
387          $templater->perform($acts, $which, 'thumbImage');
388      },
389      ifUnderThreshold => 
390      sub { 
391        my $count;
392        my $what = $_[0] || '';
393        if ($what eq 'stepkids') {
394          $count = @stepkids;
395        }
396        elsif ($what eq 'allkids') {
397          $count = @allkids;
398        }
399        else {
400          $count = @children;
401        }
402        $count <= $article->{threshold};
403      },
404      ifChildren => sub { scalar @children },
405      iterate_children_reset => sub { $child_index = -1; },
406      iterate_children =>
407      sub {
408        return ++$child_index < @children;
409      },
410      child =>
411      sub {
412        return tag_article($children[$child_index], $cfg, $_[0]);
413      },
414
415      section=> [ \&tag_article, $section, $cfg ],
416
417      # these are mostly obsolete, use moveUp and moveDown instead
418      # where possible
419      ifPrevChild => sub { $child_index > 0 },
420      ifNextChild => sub { $child_index < $#children },
421
422      # generate buttons for administration (only for admin generation)
423      admin=> [ tag_admin=>$self, $article, 'article', $embedded ],
424
425      # transform the article or response body (entities, images)
426      body=>sub {
427        my ($args, $acts, $funcname, $templater) = @_;
428        return $self->format_body(acts => $acts, 
429                                  article => $articles, 
430                                  text => $article->{body},
431                                  imagepos => $article->{imagePos}, 
432                                  abs_urls => $abs_urls,
433                                  auto_images => !$had_image_tags, 
434                                  templater => $templater, 
435                                  images => \@images,
436                                  files => \@all_files,
437                                  articles => $articles);
438      },
439
440      # used to display a navigation path of parent sections
441      $art_it->make_iterator([ iter_crumbs => $self, \@crumbs ],
442                             'crumb', 'crumbs', undef, undef,
443                             'nocache', \$current_crumb),
444      crumbs =>
445      sub {
446        # this is obsolete
447        $cfg->entry('basic', 'warn_obsolete', 0)
448          and print STDERR "* crumbs tag obsolete *\n";
449        return tag_article($current_crumb, $cfg, $_[0]);
450      },
451      
452      # access to parent
453      ifParent => sub { $parent },
454      parent =>
455      sub { return $parent && tag_article($parent, $cfg, $_[0]) },
456      # for rearranging order in admin mode
457      moveDown=>
458      sub {
459        @children > 1 or return '';
460        if ($self->{admin} && $child_index < $#children) {
461          my $html = <<HTML;
462 <a href="$CGI_URI/admin/move.pl?id=$children[$child_index]{id}&amp;d=down"><img src="$IMAGES_URI/admin/move_down.gif" width="17" height="13" border="0" alt="Move Down" align="bottom" /></a>
463 HTML
464          chop $html;
465          return $html;
466        } else {
467          return $blank;
468        }
469      },
470      moveUp=>
471      sub {
472        @children > 1 or return '';
473        if ($self->{admin} && $child_index > 0) {
474          my $html = <<HTML;
475 <a href="$CGI_URI/admin/move.pl?id=$children[$child_index]{id}&amp;d=up"><img src="$IMAGES_URI/admin/move_up.gif" width="17" height="13" border="0" alt="Move Up" align="bottom" /></a>
476 HTML
477          chop $html;
478          return $html;
479        } else {
480          return $blank;
481        }
482      },
483      movekid => [ \&tag_movekid, $self, \$child_index, \@children, $article ],
484      movestepkid =>
485      sub {
486        my ($arg, $acts, $funcname, $templater) = @_;
487        my $html = '';
488        return '' unless $self->{admin};
489        return '' unless @allkids > 1;
490        defined $allkids_index && $allkids_index >= 0 && $allkids_index < @allkids
491          or return '** movestepkid must be inside iterator allkids **';
492        my ($img_prefix, $urladd) = 
493          DevHelp::Tags->get_parms($arg, $acts, $templater);
494        $img_prefix = '' unless defined $img_prefix;
495        $urladd = '' unless defined $urladd;
496        my $top = $self->{top} || $article;
497        my $refreshto = $ENV{SCRIPT_NAME} . "?id=$top->{id}$urladd";
498        my $down_url = "";
499        if ($allkids_index < $#allkids) {
500          $down_url = "$CGI_URI/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids[$allkids_index]{id}&other=$allkids[$allkids_index+1]{id}";
501        }
502        my $up_url = "";
503        if ($allkids_index > 0) {
504          $up_url = "$CGI_URI/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids[$allkids_index]{id}&other=$allkids[$allkids_index-1]{id}";
505        }
506        
507        return make_arrows($self->{cfg}, $down_url, $up_url, $refreshto, $img_prefix);
508      },
509      ifCurrentPage=>
510      sub {
511        my $arg = shift;
512        $arg && $acts->{$arg} && $acts->{$arg}->('id') == $article->{id};
513      },
514      ifAncestor =>
515      sub {
516        my ($arg, $acts, $name, $templater) = @_;
517        unless ($arg =~ /^\d+$/) {
518          $acts->{$arg} or die "ENOIMPL\n";
519          $arg = $acts->{$arg} && $templater->perform($acts, $arg, 'id')
520            or return;
521        }
522        scalar grep $_->{id} == $arg, @ancestors, $article;
523      },
524      ifStepAncestor => [ \&tag_ifStepAncestor, $article ],
525      # access to images, if any
526      $it->make_iterator([ iter_images => $self, \@images ], 'image', 'images', \@iter_images, \$image_index, 'nocache', \$current_image),
527      # override the generated image tag
528      image =>
529      sub {
530        my ($which, $align, $rest) = split ' ', $_[0], 3;
531
532        $had_image_tags = 1;
533        my $im;
534        if (defined $which && $which =~ /^\d+$/ && $which >=1 
535            && $which <= @images) {
536          $im = $images[$which-1];
537        }
538        else {
539          $im = $current_image;
540        }
541
542        return $self->_format_image($im, $align, $rest);
543      },
544      imagen => 
545      sub {
546        my ($arg, $acts, $funcname, $templater) = @_;
547        my ($name, $align, @rest) =
548          DevHelp::Tags->get_parms($arg, $acts, $templater);
549        my $rest = "@rest";
550
551        my ($im) = grep lc $name eq lc $_->{name}, @images
552          or return '';
553
554        $self->_format_image($im, $align, $rest);
555      },
556      ifImage => sub { $_[0] >= 1 && $_[0] <= @images },
557      thumbimage => [ tag_thumbimage => $self, \$current_image, \@images ],
558      BSE::Util::Tags->make_iterator(\@files, 'file', 'files'),
559      filen => [ tag_filen => $self, \@files ],
560      BSE::Util::Tags->make_iterator(\@stepkids, 'stepkid', 'stepkids'),
561      $art_it->make_iterator(undef, 'allkid', 'allkids', \@allkids, \$allkids_index),
562      $art_it->make_iterator(undef, 'stepparent', 'stepparents', \@stepparents),
563      top => [ \&tag_article, $self->{top} || $article, $cfg ],
564      ifDynamic => $dynamic,
565      ifStatic => !$dynamic,
566      ifAccessControlled => [ \&tag_ifAccessControlled, $article ],
567     );
568
569   if ($abs_urls) {
570     my $oldurl = $acts{url};
571     my $urlbase = $cfg->entryErr('site', 'url');
572     $acts{url} =
573       sub {
574         my $value = $oldurl->(@_);
575         return $value if $value =~ /^<:/; # handle "can't do it"
576         unless ($value =~ /^\w+:/) {
577           # put in the base site url
578           $value = $urlbase . $value;
579         }
580         return $value;
581       };
582   }
583   if ($dynamic && $cfg->entry('basic', 'ajax', 0)) {
584     # make sure the ajax tags are left until we do dynamic replacement
585     delete @acts{qw/ajax ifAjax/};
586   }
587
588   return %acts;
589 }
590
591 sub tag_ifStepAncestor {
592   my ($article, $arg, $acts, $name, $templater) = @_;
593
594   unless ($arg =~ /^\d+$/) {
595     $acts->{$arg} or die "ENOIMPL\n";
596     $arg = $acts->{$arg} && $templater->perform($acts, $arg, 'id')
597       or return;
598   }
599   return 0 if $article->{id} < 0;
600   return $article->{id} == $arg || $article->is_step_ancestor($arg);
601 }
602
603 sub tag_ifDynamic {
604   my ($self, $top) = @_;
605
606   # this is to support pregenerated pages being handled as dynamic pages
607   $self->{force_dynamic} and return 1;
608
609   UNIVERSAL::isa($top, 'Article') ? $top->is_dynamic : 0;
610 }
611
612 sub tag_ifAccessControlled {
613   my ($article, $arg, $acts, $funcname, $templater) = @_;
614
615   if ($arg) {
616     if ($acts->{$arg}) {
617       my $id = $templater->perform($acts, $arg, 'id');
618       $article = Articles->getByPkey($id);
619       unless ($article) {
620         print STDERR "** Unknown article $id from $arg in ifAccessControlled\n";
621         return 0;
622       }
623     }
624     else {
625       print STDERR "** Unknown article $arg in ifAccessControlled\n";
626       return 0;
627     }
628   }
629
630   return UNIVERSAL::isa($article, 'Article') ? 
631     $article->is_access_controlled : 0;
632 }
633
634 sub get_image {
635   my ($self, $image_id, $images) = @_;
636
637   my $im;
638   if ($image_id =~ /^\d+$/) {
639     $image_id >= 1 && $image_id <= @$images
640       or return ( undef, "* Out of range image index '$image_id' *" );
641     
642     $im = $images->[$image_id-1];
643   }
644   elsif ($image_id =~ /^[^\W\d]\w*$/) {
645     ($im) = grep $_->{name} eq $image_id, @$images
646       or return ( undef, "* Unknown image identifier '$image_id' *" );
647   }
648   else {
649     return ( undef, "* Unrecognized image '$image_id' *" );
650   }
651   
652   return $im;
653 }
654
655 sub do_popimage {
656   my ($self, $image_id, $class, $images) = @_;
657
658   my ($im, $msg) = $self->get_image($image_id, $images);
659   $im
660     or return $msg;
661
662   return $self->do_popimage_low($im, $class);
663 }
664
665 # note: this is called by BSE::Formatter::thumbimage(), update that if
666 # this is changed
667 sub do_thumbimage {
668   my ($self, $geo_id, $image_id, $field, $images, $current) = @_;
669
670   my $im;
671   if ($image_id eq '-' && $current) {
672     $im = $current
673       or return "** No current image in images iterator **"
674   }
675   else {
676     ($im, my $msg) = $self->get_image($image_id, $images);
677     $im
678       or return $msg;
679   }
680
681   return $self->_sthumbimage_low($geo_id, $im, $field);
682 }
683
684 sub generate {
685   my ($self, $article, $articles) = @_;
686
687   my $html = BSE::Template->get_source($article->{template}, $self->{cfg});
688   $html =~ s/<:\s*embed\s+(?:start|end)\s*:>//g;
689
690   return $self->generate_low($html, $article, $articles, 0);
691 }
692
693 sub tag_movekid {
694   my ($self, $rindex, $rchildren, $article, $args, $acts, 
695       $funcname, $templater) = @_;
696
697   $self->{admin} or return '';
698   @$rchildren or return '';
699
700   my ($img_prefix, $urladd) = 
701     DevHelp::Tags->get_parms($args, $acts, $templater);
702   defined $img_prefix or $img_prefix = '';
703   defined $urladd or $urladd = '';
704
705   my $top = $self->{top} || $article;
706   my $refreshto = $ENV{SCRIPT_NAME} . "?id=$top->{id}$urladd";
707   my $down_url = "";
708   if ($$rindex < $#$rchildren) {
709     $down_url = "$CGI_URI/admin/move.pl?id=$rchildren->[$$rindex]{id}&d=down";
710   }
711   my $up_url = "";
712   if ($$rindex > 0) {
713     $up_url = "$CGI_URI/admin/move.pl?id=$rchildren->[$$rindex]{id}&d=up";
714   }
715
716   return make_arrows($self->{cfg}, $down_url, $up_url, $refreshto, $img_prefix);
717 }
718
719 sub _find_articles {
720   my ($self, $article_id, $article, @rest) = @_;
721
722   if ($article_id eq 'article') {
723     return $article;
724   }
725   elsif ($article_id eq 'children') {
726     return $article->all_visible_kids;
727   }
728   elsif ($article_id eq 'parent') {
729     return $article->parent;
730   }
731   else {
732     return $self->SUPER::_find_articles($article_id, $article, @rest);
733   }
734 }
735
736 1;
737
738 __END__
739
740 =head1 NAME
741
742   Generate::Article - generates articles.
743
744 =head1 SYNOPSIS
745
746 =head1 DESCRIPTION
747
748 =head1 TAGS
749
750
751 =head2 Tag notes
752
753 In your HTML each tag will be preceded by <: and followed by :>
754
755 Tags marked as conditional will require a little more.  Conditional
756 tags can be used in two ways:
757
758 <:ifName args:>true text<:or:>false text<:eif:>
759
760 or:
761
762 <:if Name args:>true text<:or Name:>false text<:eif Name:>
763
764 Tags starting iterator ... are used as iterators, like:
765
766 <:iterator begin name:>
767 repeated text
768 <:iterator separator name:>
769 separator text
770 <:iterator end name:>
771
772 In general, a parameter I<which> can be any one of 'article', 'parent'
773 or 'section'.  In a child iterator it can also be 'child'.  In a
774 crumbs iterator it can also be 'crumbs'.  If I<which> is missing it
775 means the current article.
776
777 =head2 Normal tags
778
779 =over 4
780
781 =item article I<name>
782
783 Access to fields of the article. See L<Article attributes>.
784
785 =item parent I<name>
786
787 Access to fields of the parent article. See L<Article attributes>.
788
789 =item ifParent
790
791 Conditional tag, true if there is a parent.
792
793 =item section I<name>
794
795 Access to the fields of the section containing the article.  See
796 L<Article attributes>.
797
798 =item title I<which>
799
800 The title of the article presented as an image if there is a
801 titleImage or as text.  See L<Tag notes> for values for which.
802
803 =item ifTitleImage I<which>
804
805 Conditional tag, true if the given article has a titleImage,
806
807 =item thumbnail I<which> I<class>
808
809 The thumbnail image as an <img> tag for object I<which> where I<which>
810 is one of the article objects defined.  The optional I<class> adds a
811 class attribute to the tag with that class.
812
813 =item ifThumbnail I<which>
814
815 Conditional tag, true if the article specified by I<which> has a
816 thumbnail.
817
818 =item ifUnderThreshold
819
820 =item ifUnderThreshold stepkids
821
822 =item ifUnderThreshold allkids
823
824 Conditional tag, true if the number of children/stepkids/allkids is
825 less or equal to the article's threshold.
826
827 =item body
828
829 The formatted body of the article.
830
831 =item keywords
832
833 Ignore this one.
834
835 =item iterator ... crumbs [option]
836
837 Iterates over the ancestors of the article.  See the L</item crumbs>.
838
839 I<option> can be empty, "listedonly" or "showtop".  If empty the
840 display of an unlisted level1 ancestor is controlled by
841 $UNLISTED_LEVEL1_IN_CRUMBS, if "listedonly" then an unlisted level1
842 article isn't shown in the crumbs, and it is if "showtop" is the
843 I<option>.  This can be used in <: ifCrumbs :> too.
844
845 =item crumbs I<name>
846
847 Access to the fields of the specific ancestor.  I<name> can be any of
848 the L<Article attributes>.
849
850 =item ifCrumbs [options]
851
852 Conditional tag, true if there are any crumbs.
853
854 See L</iterator ... crumbs [option]> for information on I<option>.
855
856 =item ifChildren
857
858 Conditional tag, true if the article has any children.
859
860 =item iterator ... children
861
862 Iterates over the children of the article.  See the L</item child>.
863
864 =item child I<name>
865
866 Access to the fields of the current child.
867
868 =item summary
869
870 Produces a processed summary of the current child's body.
871
872 =item ifPrevChild
873
874 Conditional tag, true if there is a previous child.  Originally used
875 for generating a move up link, but you can use the moveUp tag for
876 that now.
877
878 =item ifNextChild
879
880 Conditional tag, true if there is a next child.  Originally used to
881 generating a move down link, but you can use the moveDown tag for that
882 now.
883
884 =item ifCurrentPage I<which>
885
886 Conditional tag, true if the given I<which> is the page currently
887 being generated.  This can be used to apply special formatting if a
888 C<level1> or C<level2> article is the current page.
889
890 =item iterator ... images
891
892 Iterates over the unnamed images for the given article.
893
894 =item iterator ... images all
895
896 Iterates over all images for the article.
897
898 =item iterator ... images named
899
900 Iterates over the named images for the article.
901
902 =item iterator ... images named /regexp/
903
904 Iterates over images with names matching the given regular expression.
905 Note that if the expression matches an empty string then unnamed
906 images will be included.
907
908 =item image which field
909
910 Extracts the given field from the specified image.
911
912 I<which> in this can be either an image index to access a specific
913 image, or "-" to access the current image in the images iterator.
914
915 The image fields are:
916
917 =over
918
919 =item articleId
920
921 The identifier of the article the image belongs to.
922
923 =item image
924
925 A partial url of the image, relative to /images/.
926
927 =item alt
928
929 Alternative text of the image.
930
931 =item width
932
933 =item height
934
935 dimensions of the image.
936
937 =item url
938
939 the url if any associated with the image
940
941 =back
942
943 =item image which align
944
945 =item image which
946
947 =item image
948
949 Produces HTML to render the given image.
950
951 I<which> can be an image index (1, 2, 3...) to select one of the
952 images from the current article, or '-' or omitted to select the
953 current image from the images iterator.  If align is present then the
954 C<align> attribute of the image is set.
955
956 If the image has a URL that <a href="...">...</a> will also be
957 generated.
958
959 =item ifImage imageindex
960
961 Condition tag, true if an image exists at the given index.
962
963 =item ifImages
964
965 =item ifImages all
966
967 Conditional tag, true if the article has any images.
968
969 =item ifImages named
970
971 Conditional tag, true if the article has any named images.
972
973 =item ifImages named /regexp/
974
975 Conditional tag, true if the article has any named images, where the
976 name matches the regular expression.
977
978 =item ifImages unnamed
979
980 Conditional tag, true if the article has any unnamed images.
981
982 =item embed child
983
984 This has been made more general and been moved, see L<Generate/embed child>.
985
986 =item ifDynamic
987
988 Tests if the article is dynamically generated.
989
990 =item top I<field>
991
992 Toplevel article being generated.  This is the page that any articles
993 are being embedded in.
994
995 =item iterator ... files
996
997 Iterates over the files attached to the article, setting the file tag.
998
999 =item file I<field>
1000
1001 Information from the current file in the files iterator.
1002
1003 The file fields are:
1004
1005 =over
1006
1007 =item *
1008
1009 id - identifier for this file
1010
1011 =item *
1012
1013 articleId - article this file belongs to
1014
1015 =item *
1016
1017 displayName - the filename of the file as displayed
1018
1019 =item *
1020
1021 filename - filename of the file as stored on disk,
1022
1023 =item *
1024
1025 sizeInBytes - size of the file in bytes.
1026
1027 =item *
1028
1029 description - the entered description of the file
1030
1031 =item *
1032
1033 contentType - the MIME content type of the file
1034
1035 =item *
1036
1037 displayOrder - number used to control the listing order.
1038
1039 =item *
1040
1041 forSale - non-zero if the file needs to be paid for to be downloaded.
1042
1043 =item *
1044
1045 download - if this is non-zero BSE will attempt to make the browser
1046 download the file rather than display it.
1047
1048 =item *
1049
1050 whenUploaded - date/time when the file was uploaded.
1051
1052 =item *
1053
1054 requireUser - if non-zero the user must be logged on to download this
1055 file.
1056
1057 =item *
1058
1059 notes - longer descriptive text.
1060
1061 =item *
1062
1063 name - identifier for the file for filelink[]
1064
1065 =item *
1066
1067 hide_from_list - if non-zero the file won't be listed by the files
1068 iterator, but will still be available to filelink[].
1069
1070 =back
1071
1072 =back
1073
1074 =head2 Article attributes
1075
1076 =over 4
1077
1078 =item id
1079
1080 Identifies the article.
1081
1082 =item parentId
1083
1084 The identifier of the parent article.
1085
1086 =item title
1087
1088 The title of the article.  See the title tag
1089
1090 =item titleImage
1091
1092 The name of the title image for the article, if any.  See the title
1093 and ifTitleImage tags.
1094
1095 =item body
1096
1097 The body of the article.  See the body tag.
1098
1099 =item thumbImage
1100
1101 =item thumbWidth
1102
1103 =item thumbHeight
1104
1105 The thumbnail image for the article, if any.  See the thumbnail tag.
1106
1107 =item release
1108
1109 =item expire
1110
1111 The release and expiry dates of the article.
1112
1113 =item keyword
1114
1115 Any keywords for the article.  Indexed by the search engine.
1116
1117 =item link
1118
1119 =item admin
1120
1121 Links to the normal and adminstrative versions of the article.  The
1122 url tag defined by Generate.pm will select the appropriate tag for the
1123 current mode.
1124
1125 =item threshold
1126
1127 The maximum number of articles that should be embeded into the current
1128 article for display.  See the ifUnderThreshold tag.
1129
1130 =item summaryLength
1131
1132 The maximum amount of text displayed in the summary of an article.
1133 See the summary tag.
1134
1135 =item generator
1136
1137 The class used to generate the article.  Should be one of
1138 Generate::Article, Generate::Catalog or Generate::Product.
1139
1140 =item level
1141
1142 The level of the article.  Sections are level1, etc
1143
1144 =item listed
1145
1146 How the article is listed.  If zero then the article can only be found
1147 in a search.  If 1 then the article is listed in menus and article
1148 contents, if 2 then the article is only listed in article contents.
1149
1150 =item lastModified
1151
1152 When the article was last modified.  Currently only used for display
1153 in search results.
1154
1155 =item lastModifiedBy
1156
1157 Set the the current admin user logon if access_control is enabled in the cfg.
1158
1159 =item created
1160
1161 Set to the current date time when a new article is created.
1162
1163 =item createdBy
1164
1165 Set to the current admin user logon if access_control is enabled in
1166 the cfg.
1167
1168 =item author
1169
1170 A user definable field for attributing the article author.
1171
1172 =item pageTitle
1173
1174 An alternate article title which can be used to make search engine
1175 baited oage titles.
1176
1177 =item metaDescription
1178
1179 Article metadata description, used as metadata in the generated HTML
1180 output.
1181
1182 =item metaKeywords
1183
1184 Article metadata keywords, used as metadata in the generated HTML
1185 output.
1186
1187 =back
1188
1189 The following attributes are unlikely to be used in a page:
1190
1191 =over 4
1192
1193 =item displayOrder
1194
1195 Used internally to control the ordering of articles within a section.
1196
1197 =item imagePos
1198
1199 The position of the first image in the body.  The body tag will format
1200 images into the body as specified by this tag.
1201
1202 =item template
1203
1204 The template used to format the article.
1205
1206 =item flags
1207
1208 Flags which can be checked by code or template tags to control behaviours 
1209 specific the the article.
1210
1211 =item force_dynamic
1212
1213 Forces a page to be displayed dynamically with page.pl regardless of
1214 access control.
1215
1216 =item inherit_siteuser_rights
1217
1218 Controls whether the article inherits its parents access controls.
1219
1220 =back
1221
1222 =head2 Admin tags
1223
1224 =over 4
1225
1226 The following tags produce output only in admin mode.
1227
1228 =item admin
1229
1230 Produces buttons and links used for administering the article.
1231
1232 =item moveUp
1233
1234 Generates a move up link if there is a previous child for the current
1235 child.
1236
1237 =item moveDown
1238
1239 Generates a move down link if there is a next child for the current child.
1240
1241 =item admin
1242
1243 Produces buttons and links used for administering the article.
1244
1245 This tag can use a specialized template if it's available.  If you
1246 call it with a parameter, <:admin I<template>:> then it will use
1247 template C<< admin/adminmenu/I<template>.tmpl >>.  When used in an
1248 article template C<< <:admin:> >> behaves like C<< <:admin article:>
1249 >>, when used in a catalog template C<< <:admin:> >> behaves like C<<
1250 <:admin catalog:> >>, when used in a product template C<< <:admin:> >>
1251 behaves like C<< <:admin product:> >>.  See L<Admin Menu Templates>
1252 for the tags available in admin menu templates.
1253
1254 If the template doesn't exist then the old behaviour applies.
1255
1256 =back
1257
1258 =head2 Admin Menu Templates
1259
1260 These tags can be used in the templates included by the C<< <:admin:>
1261 >> tag.
1262
1263 The basic template tags and ifUserCan tag can be used in admin menu
1264 templates.
1265
1266 =over
1267
1268 =item article field
1269
1270 Retrieves a field from the current article.
1271
1272 =item parent field
1273
1274 Retrieves a field from the parent of the current article.
1275
1276 =item ifParent
1277
1278 Conditional tag, true if the current article has a parent.
1279
1280 =item ifEmbedded
1281
1282 Conditional tag, true if the current article is embedded in another
1283 article, in this context.
1284
1285 =back
1286
1287 =cut