4 use Constants qw($IMAGEDIR $LOCAL_FORMAT $BODY_EMBED
5 $EMBED_MAX_DEPTH $HAVE_HTML_PARSER);
8 use BSE::Util::Tags qw(tag_article);
9 use BSE::CfgInfo qw(custom_class);
10 use BSE::Util::Iterate;
11 use base 'BSE::ThumbLow';
12 use base 'BSE::TagFormats';
14 our $VERSION = "1.003";
16 my $excerptSize = 300;
19 my ($class, %opts) = @_;
22 Carp->import('confess');
23 confess("cfg missing on generator->new call");
25 $opts{maxdepth} = $EMBED_MAX_DEPTH unless exists $opts{maxdepth};
26 $opts{depth} = 0 unless $opts{depth};
27 return bless \%opts, $class;
34 # replace commonly used characters
36 # unfortunately some browsers^W^Wnetscape don't support the entities yet <sigh>
40 $text =~ s/\226/-/g; # "--" looks ugly
43 $text =~ s/\’/'/g;
49 my ($self, $articles, $text, $acts, $length) = @_;
51 # remove any block level formatting
52 $self->remove_block($articles, $acts, \$text);
54 $text =~ tr/\n\r / /s;
56 if (length $text > $length) {
57 $text = substr($text, 0, $length);
60 # roughly balance [ and ]
62 1 while $temp =~ s/\s\[[^\]]*\]//; # eliminate matched
64 ++$count while $temp =~ s/\w\[[^\]]*$//; # count unmatched
66 $text .= ']' x $count;
70 # the formatter now adds <p></p> around the text, but we don't
72 my $result = $self->format_body(articles => $articles,
74 $result =~ s!<p>|</p>!!g;
79 # attempts to move the given position forward if it's within a HTML tag,
80 # entity or just a word
82 my ($self, $text, $pos) = @_;
85 return $pos + length $1
86 if substr($text, 0, $pos) =~ /<[^<>]*$/
87 && substr($text, $pos) =~ /^([^<>]*>)/;
88 return $pos + length $1
89 if substr($text, 0, $pos) =~ /&[^;&]*$/
90 && substr($text, $pos) =~ /^([^;&]*;)/;
91 return $pos + length $1
92 if $pos <= length $text
93 && substr($text, $pos-1, 1) =~ /\w$/
94 && substr($text, $pos) =~ /^(\w+)/;
99 # raw html - this has some limitations
100 # the input text has already been escaped, so we need to unescape it
101 # too bad if you want [] in your html (but you can use entities)
103 return unescape_html($_[0]);
107 my ($self, $acts, $articles, $what, $template, $maxdepth, $templater) = @_;
109 $maxdepth = $self->{maxdepth}
110 if !$maxdepth || $maxdepth > $self->{maxdepth};
111 #if ($self->{depth}) {
112 # print STDERR "Embed depth $self->{depth}\n";
114 if ($self->{depth} > $self->{maxdepth}) {
115 if ($self->{maxdepth} == $EMBED_MAX_DEPTH) {
116 return "** too many embedding levels **";
124 if ($what =~ /^alias:([a-z]\w*)$/) {
126 ($embed) = $articles->getBy(linkAlias => $alias)
127 or return "** Cannot find article aliased $alias to be embedded **";;
131 if ($what !~ /^\d+$/) {
132 # not an article id, assume there's an article here we can use
133 $id = $acts->{$what} && $templater->perform($acts, $what, 'id');
134 unless ($id && $id =~ /^\d+$/) {
136 defined $template or $template = "-";
137 return "<:embed $what $template $maxdepth:>";
144 $embed = $articles->getByPkey($id)
145 or return "** Cannot find article $id to be embedded **";;
149 if (ref($self) ne $embed->{generator}) {
150 my $genname = $embed->{generator};
151 $genname =~ s#::#/#g; # broken on MacOS I suppose
157 print STDERR "Cannot load generator $embed->{generator}: $@\n";
158 return "** Cannot load generator $embed->{generator} for article $embed->{id} **";
160 my $top = $self->{top} || $embed;
161 $gen = $embed->{generator}->new
163 admin=>$self->{admin},
164 admin_links => $self->{admin_links},
166 request=>$self->{request},
171 my $olddepth = $gen->{depth};
172 $gen->{depth} = $self->{depth}+1;
173 my $oldmaxdepth = $gen->{maxdepth};
174 $gen->{maxdepth} = $maxdepth;
175 $template = "" if defined($template) && $template eq "-";
176 my $result = $gen->embed($embed, $articles, $template);
177 $gen->{depth} = $olddepth;
178 $gen->{maxdepth} = $oldmaxdepth;
184 my ($self, $acts, $articles, $which, $template, $maxdepth) = @_;
186 my $text = $self->_embed_low($acts, $articles, $which, $template, $maxdepth);
191 sub formatter_class {
192 require BSE::Formatter::Article;
193 return 'BSE::Formatter::Article'
196 # replace markup, insert img tags
210 my $acts = $opts{acts};
211 my $articles = $opts{articles};
212 my $body = $opts{text};
213 my $imagePos = $opts{imagepos};
214 my $abs_urls = $opts{abs_urls};
215 my $auto_images = $opts{auto_images};
216 my $templater = $opts{templater};
217 my $images = $opts{images};
218 my $files = $opts{files};
220 return substr($body, 6) if $body =~ /^<html>/i;
222 my $formatter_class = $self->formatter_class;
224 my $formatter = $formatter_class->new(gen => $self,
226 articles => $articles,
227 abs_urls => $abs_urls,
228 auto_images => \$auto_images,
231 templater => $templater);
233 $body = $formatter->format($body);
235 my $xhtml = $self->{cfg}->entry('basic', 'xhtml', 1);
237 # we don't format named images
238 my @images = grep $_->{name} eq '', @$images;
239 if ($auto_images && @images) {
240 # the first image simply goes where we're told to put it
241 # the imagePos is [tb][rl] (top|bottom)(right|left)
242 my $align = $imagePos =~ /r/ ? 'right' : 'left';
244 # Offset the end a bit so we don't get an image hanging as obviously
246 # Numbers determined by trial - it can still look pretty rough.
247 my $len = length $body;
255 #my $incr = @images > 1 ? 2*$len / (2*@images+1) : 0;
256 my $incr = $len / @images;
257 # inserting the image tags moves character positions around
258 # so we need the temp buffer
259 if ($imagePos =~ /b/) {
260 @images = reverse @images;
261 if (@images % 2 == 0) {
262 # starting at the bottom, swap it around
263 $align = $align eq 'right' ? 'left' : 'right';
267 for my $image (@images) {
268 # adjust to make sure this isn't in the middle of a tag or entity
269 my $pos = $self->adjust_for_html($body, $incr);
271 my $img = $image->inline(cfg => $self->{cfg}, align => $align);
273 $output .= substr($body, 0, $pos);
274 substr($body, 0, $pos) = '';
275 $align = $align eq 'right' ? 'left' : 'right';
277 $body = $output . $body; # don't forget the rest of it
280 return make_entities($body);
284 my ($self, $article, $articles, $template) = @_;
286 if (defined $template && $template =~ /\$/) {
287 $template =~ s/\$/$article->{template}/;
290 $template = $article->{template}
291 unless defined($template) && $template =~ /\S/;
294 my $html = BSE::Template->get_source($template, $self->{cfg});
296 # the template will hopefully contain <:embed start:> and <:embed end:>
298 # otherwise pull out the body content
299 if ($html =~ /<:\s*embed\s*start\s*:>(.*)<:\s*embed\s*end\s*:>/s
300 || $html =~ m"<\s*body[^>]*>(.*)<\s*/\s*body>"s) {
303 return $self->generate_low($html, $article, $articles, 1);
307 my ($self, $state, $args, $acts, $name, $templater) = @_;
309 my $filter = $self->_get_filter(\$args);
311 $state->{parentid} = undef;
312 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
314 unless ($id =~ /^\d+$|^-1$/) {
315 $id = $templater->perform($acts, $id, "id");
318 @ids = grep /^\d+$|^-1$/, @ids;
320 $state->{parentid} = $ids[0];
322 $self->_do_filter($filter, map Articles->listedChildren($_), @ids);
325 my $cols_re; # cache for below
331 my ($self, $rargs) = @_;
333 if ($$rargs =~ s/filter:\s+(.*)\z//s) {
335 my $orig_expr = $expr;
337 my $cols_expr = '(' . join('|', Article->columns) . ')';
338 $cols_re = qr/\[$cols_expr\]/;
340 $expr =~ s/$cols_re/\$article->{$1}/g;
341 $expr =~ s/ARTICLE/\$article/g;
342 #print STDERR "Expr $expr\n";
343 my $filter = $expr_cache{$expr};
345 $filter = eval 'sub { my $article = shift; '.$expr.'; }';
347 print STDERR "** Failed to compile filter expression >>$expr<< built from >>$orig_expr<<\n";
350 $expr_cache{$expr} = $filter;
362 my ($self, $filter, @articles) = @_;
367 return grep $filter->($_), @articles;
370 sub iter_all_kids_of {
371 my ($self, $state, $args, $acts, $name, $templater) = @_;
373 my $filter = $self->_get_filter(\$args);
375 $state->{parentid} = undef;
376 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
378 unless ($id =~ /^\d+$|^-1$/) {
379 $id = $templater->perform($acts, $id, "id");
382 @ids = grep /^\d+$|^-1$/, @ids;
383 @ids == 1 and $state->{parentid} = $ids[0];
385 $self->_do_filter($filter, map Articles->all_visible_kids($_), @ids);
389 my ($args, $acts, $name, $templater) = @_;
391 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
393 unless ($id =~ /^\d+$/) {
394 $id = $templater->perform($acts, $id, "id");
397 @ids = grep /^\d+$/, @ids;
398 map Articles->getByPkey($_), @ids;
402 my ($self, $args) = @_;
404 unless ($self->{gimages}) {
405 require BSE::TB::Images;
406 my @gimages = BSE::TB::Images->getBy(articleId => -1);
407 my %gimages = map { $_->{name} => $_ } @gimages;
408 $self->{gimages} = \%gimages;
412 sort { $a->{name} cmp $b->{name} } values %{$self->{gimages}};
413 if ($args =~ m!^named\s+/([^/]+)/$!) {
415 return grep $_->{name} =~ /$re/i, @gimages;
423 my ($self, $args) = @_;
425 unless ($self->{gfiles}) {
426 my @gfiles = Articles->global_files;
427 my %gfiles = map { $_->{name} => $_ } @gfiles;
428 $self->{gfiles} = \%gfiles;
432 sort { $a->{name} cmp $b->{name} } values %{$self->{gfiles}};
433 if ($args =~ m!^named\s+/([^/]+)/$!) {
435 return grep $_->{name} =~ /$re/i, @gfiles;
437 elsif ($args =~ m(^filter: (.*)$)s) {
439 $expr =~ s/FILE\[(\w+)\]/\$file->$1/g;
440 my $sub = eval 'sub { my $file = shift; ' . $expr . '; }';
442 or die "* Cannot compile sub from filter $expr: $@ *";
443 return grep $sub->($_), @gfiles;
453 $self->{admin} or return;
455 return BSE::Util::Tags->secure($self->{request});
461 my $static = $self->{cfg}->entry('basic', 'static_thumbnails', 1);
462 $self->{admin} and $static = 0;
463 $self->{dynamic} and $static = 0;
468 # implements popimage and gpopimage
469 sub do_popimage_low {
470 my ($self, $im, $class) = @_;
476 static => $self->_static_images,
482 my ($self, $image_id, $class) = @_;
484 my $im = $self->get_gimage($image_id)
485 or return "* Unknown global image '$image_id' *";
487 return $self->do_popimage_low($im, $class);
490 sub _sthumbimage_low {
491 my ($self, $geometry, $im, $field) = @_;
493 return $self->_thumbimage_low($geometry, $im, $field, $self->{cfg}, $self->_static_images);
496 sub tag_gthumbimage {
497 my ($self, $rcurrent, $args, $acts, $name, $templater) = @_;
499 my ($geometry_id, $id, $field) = DevHelp::Tags->get_parms($args, $acts, $templater);
501 return $self->do_gthumbimage($geometry_id, $id, $field, $$rcurrent);
505 my ($self, $acts, $templater, $article_id, $image_tags, $msg) = @_;
508 if ($article_id =~ /^\d+$/) {
510 $article = Articles->getByPkey($article_id);
512 $$msg = "* no article $article_id found *";
516 elsif ($acts->{$article_id}) {
517 my $id = $templater->perform($acts, $article_id, "id");
518 $article = Articles->getByPkey($id);
520 $$msg = "* article $article_id/$id not found *";
525 ($article) = Articles->getBy(linkAlias => $article_id);
527 $$msg = "* no article $article_id found *";
534 my @images = $article->images;
536 for my $tag (split /,/, $image_tags) {
537 if ($tag =~ m!^/(.*)/$!) {
539 ($im) = grep $_->{name} =~ /$re/i, @images
542 elsif ($tag =~ /^\d+$/) {
543 if ($tag >= 1 && $tag <= @images) {
544 $im = $images[$tag-1];
548 elsif ($tag =~ /^[^\W\d]\w*$/) {
549 ($im) = grep $_->{name} eq $tag, @images
554 $$msg = "* no image matching $image_tags found *";
561 sub tag_sthumbimage {
562 my ($self, $args, $acts, $name, $templater) = @_;
564 my ($article_id, $geometry, $image_tags, $field) = split ' ', $args;
567 my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg)
570 return $self->_sthumbimage_low($geometry, $im, $field);
574 my ($self, $args, $acts, $name, $templater) = @_;
576 my ($article_id, $image_tags, $field, $rest) = split ' ', $args, 4;
579 my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg)
582 return $self->_format_image($im, $field, $rest);
585 =item iterator vimages I<articles> I<filter>
587 =item iterator vimages I<articles>
589 Iterates over the images belonging to the articles specified.
591 I<articles> can be any of:
597 article - the current article
601 children - all visible children (including stepkids) of the current
606 parent - the parent of the current article
610 I<number> - a numeric article id, such as C<10>.
614 alias(I<alias>) - a link alias of an article
618 childrenof(I<articles>) - an articles that are children of
619 I<articles>. I<articles> can be any normal article spec, so
620 C<childrenof(childrenof(-1))> is valid.
624 I<tagname> - a tag name referring to an article.
628 I<articles> has [] replacement done before parsing.
630 I<filter> can be missing, or either of:
636 named /I<regexp>/ - images with names matching the given regular
641 numbered I<number> - images with the given index.
645 Items for this iterator are vimage and vthumbimage.
650 my ($self, $article, $args, $acts, $name, $templater) = @_;
654 if ($args =~ s!\s+named\s+/([^/]+)/$!!) {
657 elsif ($args =~ s!\s+numbered\s+(\d+)$!!) {
660 my @args = DevHelp::Tags->get_parms($args, $acts, $templater);
662 for my $article_id (map { split /[, ]/ } @args) {
663 my @articles = $self->_find_articles($article_id, $article, $acts, $name, $templater);
664 for my $article (@articles) {
665 my @aimages = $article->images;
667 push @images, grep $_->{name} =~ /$re/, @aimages;
669 elsif (defined $num) {
670 if ($num >= 0 && $num <= @aimages) {
671 push @images, $aimages[$num-1];
675 push @images, @aimages;
687 Retrieve the given field from the current vimage, or display the image.
692 my ($self, $rvimage, $args) = @_;
694 $$rvimage or return '** no current vimage **';
696 my ($field, $rest) = split ' ', $args, 2;
698 return $self->_format_image($$rvimage, $field, $rest);
701 =item vthumbimage geometry field
703 =item vthumbimage geometry
705 Retrieve the given field from the thumbnail of the current vimage or
706 display the thumbnail.
710 sub tag_vthumbimage {
711 my ($self, $rvimage, $args) = @_;
713 $$rvimage or return '** no current vimage **';
714 my ($geo, $field) = split ' ', $args;
716 return $self->_sthumbimage_low($geo, $$rvimage, $field);
720 my ($self, $article_id, $article, $acts, $name, $templater) = @_;
722 if ($article_id =~ /^\d+$/) {
723 my $result = Articles->getByPkey($article_id);
724 $result or print STDERR "** Unknown article id $article_id **\n";
725 return $result ? $result : ();
727 elsif ($article_id =~ /^alias\((\w+)\)$/) {
728 my $result = Articles->getBy(linkAlias => $1);
729 $result or print STDERR "** Unknown article alias $article_id **\n";
730 return $result ? $result : ();
732 elsif ($article_id =~ /^childrenof\((.*)\)$/) {
735 return Articles->all_visible_kids(-1);
738 my @parents = $self->_find_articles($id)
740 return map $_->all_visible_kids, @parents;
743 elsif ($acts->{$article_id}) {
744 my $id = $templater->perform($acts, $article_id, 'id');
745 if ($id && $id =~ /^\d+$/) {
746 return Articles->getByPkey($id);
749 print STDERR "** Unknown article identifier $article_id **\n";
755 my ($self, $articles, $acts, $article, $embedded) = @_;
757 # used to generate the side menu
758 my $section_index = -1;
759 my @sections = $articles->listedChildren(-1);
760 #sort { $a->{displayOrder} <=> $b->{displayOrder} }
761 #grep $_->{listed}, $articles->sections;
762 my $subsect_index = -1;
763 my @subsections; # filled as we move through the sections
764 my @level3; # filled as we move through the subsections
765 my $level3_index = -1;
767 my $cfg = $self->{cfg} || BSE::Cfg->new;
768 my %extras = $cfg->entriesCS('extra tags');
769 for my $key (keys %extras) {
771 my $data = $cfg->entryVar('extra tags', $key);
772 $extras{$key} = sub { $data };
777 my $it = BSE::Util::Iterate->new;
778 my $art_it = BSE::Util::Iterate::Article->new(cfg => $cfg,
779 admin => $self->{admin},
780 top => $self->{top});
785 custom_class($cfg)->base_tags($articles, $acts, $article, $embedded, $cfg),
787 BSE::Util::Tags->static($acts, $self->{cfg}),
788 # for embedding the content from children and other sources
789 ifEmbedded=> sub { $embedded },
791 my ($args, $acts, $name, $templater) = @_;
792 my ($what, $template, $maxdepth) = split ' ', $args;
793 undef $maxdepth if defined $maxdepth && $maxdepth !~ /^\d+/;
794 return $self->_embed_low($acts, $articles, $what, $template, $maxdepth, $templater);
796 ifCanEmbed=> sub { $self->{depth} <= $self->{maxdepth} },
800 my ($args, $acts, $name, $templater) = @_;
801 my ($which, $limit) = DevHelp::Tags->get_parms($args, $acts, $templater);
802 $which or $which = "child";
803 $limit or $limit = $article->{summaryLength};
805 or return "<:summary $which Cannot find $which:>";
806 my $id = $templater->perform($acts, $which, "id")
807 or return "<:summary $which No id returned :>";
808 my $article = $articles->getByPkey($id)
809 or return "<:summary $which Cannot find article $id:>";
810 return $self->summarize($articles, $article->{body}, $acts, $limit);
812 ifAdmin => sub { $self->{admin} },
813 ifAdminLinks => sub { $self->{admin_links} },
815 # for generating the side menu
816 iterate_level1_reset => sub { $section_index = -1 },
817 iterate_level1 => sub {
819 if ($section_index < @sections) {
820 #@subsections = grep $_->{listed},
821 # $articles->children($sections[$section_index]->{id});
822 @subsections = grep { $_->{listed} != 2 }
823 $articles->listedChildren($sections[$section_index]->{id});
832 return tag_article($sections[$section_index], $cfg, $_[0]);
835 # used to generate a list of subsections for the side-menu
836 iterate_level2 => sub {
838 if ($subsect_index < @subsections) {
839 @level3 = grep { $_->{listed} != 2 }
840 $articles->listedChildren($subsections[$subsect_index]{id});
847 return tag_article($subsections[$subsect_index], $cfg, $_[0]);
851 return scalar @subsections;
854 # possibly level3 items
855 iterate_level3 => sub {
856 return ++$level3_index < @level3;
859 tag_article($level3[$level3_index], $cfg, $_[0])
861 ifLevel3 => sub { scalar @level3 },
863 # generate an admin or link url, depending on admin state
866 my ($name, $acts, $func, $templater) = @_;
867 my $item = $self->{admin_links} ? 'admin' : 'link';
870 my $url = $templater->perform($acts, $name, $item);
871 if (!$self->{admin} && $self->{admin_links}) {
872 $url .= $url =~ /\?/ ? "&" : "?";
873 $url .= "admin=0&admin_links=1";
879 $acts->{$_[0]} or return 0;
880 return $acts->{$_[0]}->('listed') == 1;
884 my ($image, $text) = split ' ', $_[0];
885 if (-e $IMAGEDIR."/titles/".$image) {
886 return qq!<img src="/images/titles/!.$image .qq!" border=0>!
889 return escape_html($text);
892 $art_it->make( code => [ iter_kids_of => $self ],
894 plural => 'children_of',
897 $art_it->make( code => [ iter_kids_of => $self ],
898 single => 'ofchild2',
899 plural => 'children_of2',
902 $art_it->make( code => [ iter_kids_of => $self ],
903 single => 'ofchild3',
904 plural => 'children_of3',
907 $art_it->make( code => [ iter_all_kids_of => $self ],
908 single => 'ofallkid',
909 plural => 'allkids_of',
911 $art_it->make( code => [ iter_all_kids_of => $self ],
912 single => 'ofallkid2',
913 plural => 'allkids_of2',
916 $art_it->make( code => [ iter_all_kids_of => $self ],
917 single => 'ofallkid3',
918 plural => 'allkids_of3',
921 $art_it->make( code => [ iter_all_kids_of => $self ],
922 single => 'ofallkid4',
923 plural => 'allkids_of4',
926 $art_it->make( code => [ iter_all_kids_of => $self ],
927 single => 'ofallkid5',
928 plural => 'allkids_of5',
931 $art_it->make_iterator( \&iter_inlines, 'inline', 'inlines' ),
934 my ($args, $acts, $func, $templater) = @_;
935 my ($name, $align, @rest) =
936 DevHelp::Tags->get_parms($args, $acts, $templater);
941 $im = $current_gimage
945 $im = $self->get_gimage($name)
949 $self->_format_image($im, $align, $rest);
951 $it->make_iterator( [ \&iter_gimages, $self ], 'gimagei', 'gimages',
952 undef, undef, undef, \$current_gimage),
955 my ($name, $field) = split ' ', $_[0], 3;
957 my $file = $self->get_gfile($name)
960 $self->_format_file($file, $field);
962 $it->make_iterator( [ \&iter_gfiles, $self ], 'gfilei', 'gfiles'),
963 gthumbimage => [ tag_gthumbimage => $self, \$current_gimage ],
964 sthumbimage => [ tag_sthumbimage => $self ],
965 simage => [ tag_simage => $self ],
966 $it->make_iterator( [ iter_vimages => $self, $article ], 'vimage', 'vimages', undef, undef, undef, \$current_vimage),
967 vimage => [ tag_vimage => $self, \$current_vimage ],
968 vthumbimage => [ tag_vthumbimage => $self, \$current_vimage ],
973 my ($body, $case_sensitive, $terms) = @_;
977 if ($case_sensitive) {
978 for my $term (@$terms) {
979 if ($$body =~ /^(.*?)\Q$term/s) {
980 push(@found, [ length($1), $term ]);
985 for my $term (@$terms) {
986 if ($$body =~ /^(.*?)\Q$term/is) {
987 push(@found, [ length($1), $term ]);
995 # this takes the same inputs as _make_table(), but eliminates any
998 my ($opts, $data) = @_;
999 my @lines = split /\n/, $data;
1004 return join(' ', @lines);
1007 # produce a nice excerpt for a found article
1009 my ($self, $article, $found, $case_sensitive, $terms, $type, $body) = @_;
1012 $body = $article->{body};
1014 # we remove any formatting tags here, otherwise we get wierd table
1015 # rubbish or other formatting in the excerpt.
1016 my @files = $article->files;
1017 $self->remove_block('Articles', [], \$body, \@files);
1018 1 while $body =~ s/[bi]\[([^\]\[]+)\]/$1/g;
1021 $body = escape_html($body);
1025 my @found = find_terms(\$body, $case_sensitive, $terms);
1027 my @reterms = @$terms;
1033 # do a reverse sort so that the longer terms (and composite
1034 # terms) are replaced first
1035 my $re_str = join("|", reverse sort @reterms);
1037 my $cfg = $self->{cfg};
1038 if ($cfg->entryBool('search', 'highlight_partial', 1)) {
1039 $re = $case_sensitive ? qr/\b($re_str)/ : qr/\b($re_str)/i;
1042 $re = $case_sensitive ? qr/\b($re_str)\b/ : qr/\b($re_str)\b/i;
1045 # this used to try searching children as well, but it broke more
1048 # we tried hard and failed
1049 # return a generic article
1050 if (length $body > $excerptSize) {
1051 $body = substr($body, 0, $excerptSize);
1052 $body =~ s/\S+\s*$/.../;
1059 splice(@found, 5,-1) if @found > 5;
1060 my $itemSize = $excerptSize / @found;
1062 # try to combine any that are close
1063 @found = sort { $a->[0] <=> $b->[0] } @found;
1064 for my $i (reverse 0 .. $#found-1) {
1065 if ($found[$i+1][0] - $found[$i][0] < $itemSize) {
1066 my @losing = @{$found[$i+1]};
1068 push(@{$found[$i]}, @losing);
1069 splice(@found, $i+1, 1); # remove it
1073 my $highlight_prefix =
1074 $cfg->entry('search highlight', "${type}_prefix", "<b>");
1075 my $highlight_suffix =
1076 $cfg->entry('search highlight', "${type}_suffix", "</b>");
1077 my $termSize = $excerptSize / @found;
1079 for my $term (@found) {
1080 my ($pos, @terms) = @$term;
1081 my $start = $pos - $termSize/2;
1085 $part = substr($body, 0, $termSize);
1089 $part = substr($body, $start, $termSize);
1092 if ($start + $termSize < length $body) {
1093 $part =~ s/\s*\S*$/... /;
1097 $result =~ s{$re}{$highlight_prefix$1$highlight_suffix}ig;
1108 # make whatever text $body points at safe for summarizing by removing most
1109 # block level formatting
1111 my ($self, $articles, $acts, $body, $files) = @_;
1113 my $formatter_class = $self->formatter_class;
1117 my $formatter = $formatter_class->new(gen => $self,
1119 article => $articles,
1120 articles => $articles,
1123 $$body = $formatter->remove_format($$body);
1127 my ($self, $name) = @_;
1129 unless ($self->{gimages}) {
1130 require BSE::TB::Images;
1131 my @gimages = BSE::TB::Images->getBy(articleId => -1);
1132 my %gimages = map { $_->{name} => $_ } @gimages;
1133 $self->{gimages} = \%gimages;
1136 return $self->{gimages}{$name};
1140 my ($self, $name) = @_;
1142 unless ($self->{gfiles}) {
1143 my @gfiles = Articles->global_files;
1144 my %gfiles = map { $_->{name} => $_ } @gfiles;
1145 $self->{gfiles} = \%gfiles;
1148 return $self->{gfiles}{$name};
1151 # note: this is called by BSE::Formatter::thumbimage(), update that if
1153 sub do_gthumbimage {
1154 my ($self, $geo_id, $image_id, $field, $current) = @_;
1157 if ($image_id eq '-' && $current) {
1161 $im = $self->get_gimage($image_id);
1164 or return '** unknown global image id **';
1166 return $self->_sthumbimage_low($geo_id, $im, $field);
1169 sub get_real_article {
1170 my ($self, $article) = @_;
1181 Generate - provides base Squirel::Template actions for use in generating
1188 This is probably better documented in L<templates.pod>.
1192 These tags can be used anywhere, including in admin templates. It's
1193 possible some admin code has been missed, if you find a place where
1194 these cannot be used let us know.
1199 =item kb I<data tag>
1201 Formats the give value in kI<whatevers>. If you have a number that
1202 could go over 1000 and you want it to use the 'k' metric prefix when
1203 it does, use this tag. eg. <:kb file sizeInBytes:>
1205 =item date I<data tag>
1207 =item date "I<format>" I<data tag>
1209 Formats a date or date/time value from the database into something
1210 more human readable. If you don't supply a format then the default
1211 format of "%d-%b-%Y" is used ("20-Mar-2002").
1213 The I<format> is a strftime() format specification, if that means
1214 anything to you. If it doesn't, each code starts with % and are
1215 replaced as follows:
1221 abbreviated weekday name
1229 abbreviated month name
1237 "preferred" date and time representation
1241 day of the month as a 2 digit number
1245 hour (24-hour clock)
1249 hour (12-hour clock)
1253 day of year as a 3-digit number
1257 month as a 2 digit number
1261 minute as a 2 digit number
1265 AM or PM or their equivalents
1269 seconds as a 2 digit number
1273 week number as a 2 digit number (first Sunday as the first day of week 1)
1277 weekday as a decimal number (0-6)
1281 week number as a 2 digit number (first Monday as the first day of week 1)
1285 the locale's appropriate date representation
1289 the locale's appropriate time representation
1293 2-digit year without century
1301 time zone name or abbreviation
1309 Your local strftime() implementation may implement some extensions to
1310 the above, if your server is on a Unix system try running "man
1311 strftime" for more information.
1313 =item bodytext I<data tag>
1315 Formats the text from the given tag in the same way that body text is.
1317 =item ifEq I<data1> I<data2>
1319 Checks if the 2 values are exactly equal. This is a string
1322 The 2 data parameters can either be a tag reference in [], a literal
1323 string inside "" or a single word.
1325 =item ifMatch I<data1> I<data2>
1327 Treats I<data2> as a perl regular expression and attempts to match
1328 I<data1> against it.
1330 The 2 data parameters can either be a tag reference in [], a literal
1331 string inside "" or a single word.
1333 =item cfg I<section> I<key>
1335 =item cfg I<section> I<key> I<default>
1337 Retrieves a value from the BSE configuration file.
1339 If you don't supply a default then a default will be the empty string.
1343 The release number of BSE.
1353 Conditional tag, true if generating in admin mode.
1355 =item iterator ... level1
1357 Iterates over the listed level 1 articles.
1359 =item level1 I<name>
1361 The value of the I<name> field of the current level 1 article.
1363 =item iterator ... level2
1365 Iterates over the listed level 2 children of the current level 1 article.
1367 =item level2 I<name>
1369 The value of the I<name> field of the current level 2 article.
1371 =item ifLevel2 I<name>
1373 Conditional tag, true if the current level 1 article has any listed
1376 =item iterator ... level3
1378 Iterates over the listed level 3 children of the current level 2 article.
1380 =item level3 I<name>
1382 The value of the I<name> field of the current level 3 article.
1384 =item ifLevel3 I<name>
1386 Conditional tag, true if the current level 2 article has any listed
1391 Returns a link to the specified article . Due to the way the action
1392 list is built, this can be article types defined in derived classes of
1393 Generate, like the C<parent> article in Generate::Article.
1395 =item money I<data tag>
1397 Formats the given value as a monetary value. This does not include a
1398 currency symbol. Internally BSE stores monetary values as integers to
1399 prevent the loss of accuracy inherent in floating point numbers. You
1400 need to use this tag to display any monetary value.
1402 =item ifInMenu I<which>
1404 Conditional tag, true if the given item can appear in a menu.
1406 =item titleImage I<imagename> I<text>
1408 Generates an IMG tag if the given I<imagename> is in the title image
1409 directory ($IMAGEDIR/titles). If it doesn't exists, produces the
1412 =item embed I<which>
1414 =item embed I<which> I<template>
1416 =item embed I<which> I<template> I<maxdepth>
1420 Embeds the article specified by which using either the specified
1421 template or the articles template.
1423 In this case I<which> can also be an article ID.
1425 I<template> is a filename relative to the templates directory. If
1426 this is "-" then the articles template is used (so you can set
1427 I<maxdepth> without setting the template.) If I<template> contains a
1428 C<$> sign it will be replaced with the name of the original template.
1430 If I<maxdepth> is supplied and is less than the current maximum depth
1431 then it becomes the new maximum depth. This can be used with ifCanEmbed.
1433 =item embed start ... embed end
1435 Marks the range of text that would be embedded in a parent that used
1440 Conditional tag, true if the current article is being embedded.
1446 Needs more documentation.