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