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;
12 use base 'BSE::ThumbLow';
13 use base 'BSE::TagFormats';
15 our $VERSION = "1.007";
17 my $excerptSize = 300;
20 my ($class, %opts) = @_;
23 Carp->import('confess');
24 confess("cfg missing on generator->new call");
26 $opts{maxdepth} = $EMBED_MAX_DEPTH unless exists $opts{maxdepth};
27 $opts{depth} = 0 unless $opts{depth};
33 site => BSE::TB::Site->new,
35 ($opts{admin} || $opts{admin_links}
36 ? sub { $_[0]->admin }
39 admin => $opts{admin},
40 admin_links => $opts{admin_links},
43 return escape_html(Data::Dumper::Dumper(shift));
47 my $self = bless \%opts, $class;
48 $self->set_variable_class(articles => "Articles");
58 my ($self, $name, $value) = @_;
60 $self->{vars}{$name} = $value;
65 sub set_variable_class {
66 my ($self, $name, $class) = @_;
68 require Squirrel::Template;
69 $self->set_variable($name => Squirrel::Template::Expr::WrapClass->new($class));
78 # replace commonly used characters
80 # unfortunately some browsers^W^Wnetscape don't support the entities yet <sigh>
84 $text =~ s/\226/-/g; # "--" looks ugly
87 $text =~ s/\’/'/g;
93 my ($self, $articles, $text, $acts, $length) = @_;
95 # remove any block level formatting
96 $self->remove_block($articles, $acts, \$text);
98 $text =~ tr/\n\r / /s;
100 if (length $text > $length) {
101 $text = substr($text, 0, $length);
102 $text =~ s/\s+\S+$//;
104 # roughly balance [ and ]
106 1 while $temp =~ s/\s\[[^\]]*\]//; # eliminate matched
108 ++$count while $temp =~ s/\w\[[^\]]*$//; # count unmatched
110 $text .= ']' x $count;
114 # the formatter now adds <p></p> around the text, but we don't
116 my $result = $self->format_body(articles => $articles,
118 $result =~ s!<p>|</p>!!g;
123 # attempts to move the given position forward if it's within a HTML tag,
124 # entity or just a word
125 sub adjust_for_html {
126 my ($self, $text, $pos) = @_;
128 # advance if in a tag
129 return $pos + length $1
130 if substr($text, 0, $pos) =~ /<[^<>]*$/
131 && substr($text, $pos) =~ /^([^<>]*>)/;
132 return $pos + length $1
133 if substr($text, 0, $pos) =~ /&[^;&]*$/
134 && substr($text, $pos) =~ /^([^;&]*;)/;
135 return $pos + length $1
136 if $pos <= length $text
137 && substr($text, $pos-1, 1) =~ /\w$/
138 && substr($text, $pos) =~ /^(\w+)/;
143 # raw html - this has some limitations
144 # the input text has already been escaped, so we need to unescape it
145 # too bad if you want [] in your html (but you can use entities)
147 return unescape_html($_[0]);
151 my ($self, $acts, $articles, $what, $template, $maxdepth, $templater) = @_;
153 $maxdepth = $self->{maxdepth}
154 if !$maxdepth || $maxdepth > $self->{maxdepth};
155 #if ($self->{depth}) {
156 # print STDERR "Embed depth $self->{depth}\n";
158 if ($self->{depth} > $self->{maxdepth}) {
159 if ($self->{maxdepth} == $EMBED_MAX_DEPTH) {
160 return "** too many embedding levels **";
168 if ($what =~ /^alias:([a-z]\w*)$/) {
170 ($embed) = $articles->getBy(linkAlias => $alias)
171 or return "** Cannot find article aliased $alias to be embedded **";;
175 if ($what !~ /^\d+$/) {
176 # not an article id, assume there's an article here we can use
177 $id = $acts->{$what} && $templater->perform($acts, $what, 'id');
178 unless ($id && $id =~ /^\d+$/) {
180 defined $template or $template = "-";
181 return "<:embed $what $template $maxdepth:>";
188 $embed = $articles->getByPkey($id)
189 or return "** Cannot find article $id to be embedded **";;
193 if (ref($self) ne $embed->{generator}) {
194 my $genname = $embed->{generator};
195 $genname =~ s#::#/#g; # broken on MacOS I suppose
201 print STDERR "Cannot load generator $embed->{generator}: $@\n";
202 return "** Cannot load generator $embed->{generator} for article $embed->{id} **";
204 my $top = $self->{top} || $embed;
205 $gen = $embed->{generator}->new
207 admin=>$self->{admin},
208 admin_links => $self->{admin_links},
210 request=>$self->{request},
215 my $olddepth = $gen->{depth};
216 $gen->{depth} = $self->{depth}+1;
217 my $oldmaxdepth = $gen->{maxdepth};
218 $gen->{maxdepth} = $maxdepth;
219 $template = "" if defined($template) && $template eq "-";
220 my $result = $gen->embed($embed, $articles, $template);
221 $gen->{depth} = $olddepth;
222 $gen->{maxdepth} = $oldmaxdepth;
228 my ($self, $acts, $articles, $which, $template, $maxdepth) = @_;
230 my $text = $self->_embed_low($acts, $articles, $which, $template, $maxdepth);
235 sub formatter_class {
236 require BSE::Formatter::Article;
237 return 'BSE::Formatter::Article'
240 # replace markup, insert img tags
254 my $acts = $opts{acts};
255 my $articles = $opts{articles};
256 my $body = $opts{text};
257 my $imagePos = $opts{imagepos};
258 my $abs_urls = $opts{abs_urls};
259 my $auto_images = $opts{auto_images};
260 my $templater = $opts{templater};
261 my $images = $opts{images};
262 my $files = $opts{files};
264 return substr($body, 6) if $body =~ /^<html>/i;
266 my $formatter_class = $self->formatter_class;
268 my $formatter = $formatter_class->new(gen => $self,
270 articles => $articles,
271 abs_urls => $abs_urls,
272 auto_images => \$auto_images,
275 templater => $templater);
277 $body = $formatter->format($body);
279 my $xhtml = $self->{cfg}->entry('basic', 'xhtml', 1);
281 # we don't format named images
282 my @images = grep $_->{name} eq '', @$images;
285 && $self->{cfg}->entry('basic', 'auto_images', 1)
286 && $imagePos ne 'xx') {
287 # the first image simply goes where we're told to put it
288 # the imagePos is [tb][rl] (top|bottom)(right|left)
289 my $align = $imagePos =~ /r/ ? 'right' : 'left';
291 # Offset the end a bit so we don't get an image hanging as obviously
293 # Numbers determined by trial - it can still look pretty rough.
294 my $len = length $body;
302 #my $incr = @images > 1 ? 2*$len / (2*@images+1) : 0;
303 my $incr = $len / @images;
304 # inserting the image tags moves character positions around
305 # so we need the temp buffer
306 if ($imagePos =~ /b/) {
307 @images = reverse @images;
308 if (@images % 2 == 0) {
309 # starting at the bottom, swap it around
310 $align = $align eq 'right' ? 'left' : 'right';
314 for my $image (@images) {
315 # adjust to make sure this isn't in the middle of a tag or entity
316 my $pos = $self->adjust_for_html($body, $incr);
318 my $img = $image->inline(cfg => $self->{cfg}, align => $align);
320 $output .= substr($body, 0, $pos);
321 substr($body, 0, $pos) = '';
322 $align = $align eq 'right' ? 'left' : 'right';
324 $body = $output . $body; # don't forget the rest of it
327 return make_entities($body);
331 my ($self, $article, $articles, $template) = @_;
333 if (defined $template && $template =~ /\$/) {
334 $template =~ s/\$/$article->{template}/;
337 $template = $article->{template}
338 unless defined($template) && $template =~ /\S/;
341 my $html = BSE::Template->get_source($template, $self->{cfg});
343 # the template will hopefully contain <:embed start:> and <:embed end:>
345 # otherwise pull out the body content
346 if ($html =~ /<:\s*embed\s*start\s*:>(.*)<:\s*embed\s*end\s*:>/s
347 || $html =~ m"<\s*body[^>]*>(.*)<\s*/\s*body>"s) {
350 return $self->generate_low($html, $article, $articles, 1);
354 my ($self, $state, $args, $acts, $name, $templater) = @_;
356 my $filter = $self->_get_filter(\$args);
358 $state->{parentid} = undef;
359 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
361 unless ($id =~ /^\d+$|^-1$/) {
362 $id = $templater->perform($acts, $id, "id");
365 @ids = grep /^\d+$|^-1$/, @ids;
367 $state->{parentid} = $ids[0];
369 $self->_do_filter($filter, map Articles->listedChildren($_), @ids);
372 my $cols_re; # cache for below
378 my ($self, $rargs) = @_;
380 if ($$rargs =~ s/filter:\s+(.*)\z//s) {
382 my $orig_expr = $expr;
384 my $cols_expr = '(' . join('|', Article->columns) . ')';
385 $cols_re = qr/\[$cols_expr\]/;
387 $expr =~ s/$cols_re/\$article->{$1}/g;
388 $expr =~ s/ARTICLE/\$article/g;
389 #print STDERR "Expr $expr\n";
390 my $filter = $expr_cache{$expr};
392 $filter = eval 'sub { my $article = shift; '.$expr.'; }';
394 print STDERR "** Failed to compile filter expression >>$expr<< built from >>$orig_expr<<\n";
397 $expr_cache{$expr} = $filter;
409 my ($self, $filter, @articles) = @_;
414 return grep $filter->($_), @articles;
417 sub iter_all_kids_of {
418 my ($self, $state, $args, $acts, $name, $templater) = @_;
420 my $filter = $self->_get_filter(\$args);
422 $state->{parentid} = undef;
423 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
425 unless ($id =~ /^\d+$|^-1$/) {
426 $id = $templater->perform($acts, $id, "id");
429 @ids = grep /^\d+$|^-1$/, @ids;
430 @ids == 1 and $state->{parentid} = $ids[0];
432 $self->_do_filter($filter, map Articles->all_visible_kids($_), @ids);
436 my ($self, $state, $args, $acts, $name, $templater) = @_;
438 my $filter = $self->_get_filter(\$args);
440 $state->{parentid} = undef;
441 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
443 unless ($id =~ /^\d+$/) {
444 $id = $templater->perform($acts, $id, "id");
447 @ids = grep /^\d+$/, @ids;
448 @ids == 1 and $state->{parentid} = $ids[0];
450 $self->_do_filter($filter, map Articles->getByPkey($_), @ids);
454 my ($self, $args) = @_;
456 unless ($self->{gimages}) {
457 require BSE::TB::Images;
458 my @gimages = BSE::TB::Images->getBy(articleId => -1);
459 my %gimages = map { $_->{name} => $_ } @gimages;
460 $self->{gimages} = \%gimages;
464 sort { $a->{name} cmp $b->{name} } values %{$self->{gimages}};
465 if ($args =~ m!^named\s+/([^/]+)/$!) {
467 return grep $_->{name} =~ /$re/i, @gimages;
475 my ($self, $args) = @_;
477 unless ($self->{gfiles}) {
478 my @gfiles = Articles->global_files;
479 my %gfiles = map { $_->{name} => $_ } @gfiles;
480 $self->{gfiles} = \%gfiles;
484 sort { $a->{name} cmp $b->{name} } values %{$self->{gfiles}};
485 if ($args =~ m!^named\s+/([^/]+)/$!) {
487 return grep $_->{name} =~ /$re/i, @gfiles;
489 elsif ($args =~ m(^filter: (.*)$)s) {
491 $expr =~ s/FILE\[(\w+)\]/\$file->$1/g;
492 my $sub = eval 'sub { my $file = shift; ' . $expr . '; }';
494 or die "* Cannot compile sub from filter $expr: $@ *";
495 return grep $sub->($_), @gfiles;
505 $self->{admin} or return;
507 return BSE::Util::Tags->secure($self->{request});
513 my $static = $self->{cfg}->entry('basic', 'static_thumbnails', 1);
514 $self->{admin} and $static = 0;
515 $self->{dynamic} and $static = 0;
520 # implements popimage and gpopimage
521 sub do_popimage_low {
522 my ($self, $im, $class) = @_;
528 static => $self->_static_images,
534 my ($self, $image_id, $class) = @_;
536 my $im = $self->get_gimage($image_id)
537 or return "* Unknown global image '$image_id' *";
539 return $self->do_popimage_low($im, $class);
542 sub _sthumbimage_low {
543 my ($self, $geometry, $im, $field) = @_;
545 return $self->_thumbimage_low($geometry, $im, $field, $self->{cfg}, $self->_static_images);
548 sub tag_gthumbimage {
549 my ($self, $rcurrent, $args, $acts, $name, $templater) = @_;
551 my ($geometry_id, $id, $field) = DevHelp::Tags->get_parms($args, $acts, $templater);
553 return $self->do_gthumbimage($geometry_id, $id, $field, $$rcurrent);
557 my ($self, $acts, $templater, $article_id, $image_tags, $msg) = @_;
560 if ($article_id =~ /^\d+$/) {
562 $article = Articles->getByPkey($article_id);
564 $$msg = "* no article $article_id found *";
568 elsif ($acts->{$article_id}) {
569 my $id = $templater->perform($acts, $article_id, "id");
570 $article = Articles->getByPkey($id);
572 $$msg = "* article $article_id/$id not found *";
577 ($article) = Articles->getBy(linkAlias => $article_id);
579 $$msg = "* no article $article_id found *";
586 my @images = $article->images;
588 for my $tag (split /,/, $image_tags) {
589 if ($tag =~ m!^/(.*)/$!) {
591 ($im) = grep $_->{name} =~ /$re/i, @images
594 elsif ($tag =~ /^\d+$/) {
595 if ($tag >= 1 && $tag <= @images) {
596 $im = $images[$tag-1];
600 elsif ($tag =~ /^[^\W\d]\w*$/) {
601 ($im) = grep $_->{name} eq $tag, @images
606 $$msg = "* no image matching $image_tags found *";
613 sub tag_sthumbimage {
614 my ($self, $args, $acts, $name, $templater) = @_;
616 my ($article_id, $geometry, $image_tags, $field) = split ' ', $args;
619 my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg)
622 return $self->_sthumbimage_low($geometry, $im, $field);
626 my ($self, $args, $acts, $name, $templater) = @_;
628 my ($article_id, $image_tags, $field, $rest) = split ' ', $args, 4;
631 my $im = $self->_find_image($acts, $templater, $article_id, $image_tags, \$msg)
634 return $self->_format_image($im, $field, $rest);
637 =item iterator vimages I<articles> I<filter>
639 =item iterator vimages I<articles>
641 Iterates over the images belonging to the articles specified.
643 I<articles> can be any of:
649 article - the current article
653 children - all visible children (including stepkids) of the current
658 parent - the parent of the current article
662 I<number> - a numeric article id, such as C<10>.
666 alias(I<alias>) - a link alias of an article
670 childrenof(I<articles>) - an articles that are children of
671 I<articles>. I<articles> can be any normal article spec, so
672 C<childrenof(childrenof(-1))> is valid.
676 I<tagname> - a tag name referring to an article.
680 I<articles> has [] replacement done before parsing.
682 I<filter> can be missing, or either of:
688 named /I<regexp>/ - images with names matching the given regular
693 numbered I<number> - images with the given index.
697 Items for this iterator are vimage and vthumbimage.
702 my ($self, $article, $args, $acts, $name, $templater) = @_;
706 if ($args =~ s!\s+named\s+/([^/]+)/$!!) {
709 elsif ($args =~ s!\s+numbered\s+(\d+)$!!) {
712 my @args = DevHelp::Tags->get_parms($args, $acts, $templater);
714 for my $article_id (map { split /[, ]/ } @args) {
715 my @articles = $self->_find_articles($article_id, $article, $acts, $name, $templater);
716 for my $article (@articles) {
717 my @aimages = $article->images;
719 push @images, grep $_->{name} =~ /$re/, @aimages;
721 elsif (defined $num) {
722 if ($num >= 0 && $num <= @aimages) {
723 push @images, $aimages[$num-1];
727 push @images, @aimages;
739 Retrieve the given field from the current vimage, or display the image.
744 my ($self, $rvimage, $args) = @_;
746 $$rvimage or return '** no current vimage **';
748 my ($field, $rest) = split ' ', $args, 2;
750 return $self->_format_image($$rvimage, $field, $rest);
753 =item vthumbimage geometry field
755 =item vthumbimage geometry
757 Retrieve the given field from the thumbnail of the current vimage or
758 display the thumbnail.
762 sub tag_vthumbimage {
763 my ($self, $rvimage, $args) = @_;
765 $$rvimage or return '** no current vimage **';
766 my ($geo, $field) = split ' ', $args;
768 return $self->_sthumbimage_low($geo, $$rvimage, $field);
772 my ($self, $article_id, $article, $acts, $name, $templater) = @_;
774 if ($article_id =~ /^\d+$/) {
775 my $result = Articles->getByPkey($article_id);
776 $result or print STDERR "** Unknown article id $article_id **\n";
777 return $result ? $result : ();
779 elsif ($article_id =~ /^alias\((\w+)\)$/) {
780 my $result = Articles->getBy(linkAlias => $1);
781 $result or print STDERR "** Unknown article alias $article_id **\n";
782 return $result ? $result : ();
784 elsif ($article_id =~ /^childrenof\((.*)\)$/) {
787 return Articles->all_visible_kids(-1);
790 my @parents = $self->_find_articles($id)
792 return map $_->all_visible_kids, @parents;
795 elsif ($acts->{$article_id}) {
796 my $id = $templater->perform($acts, $article_id, 'id');
797 if ($id && $id =~ /^\d+$/) {
798 return Articles->getByPkey($id);
801 print STDERR "** Unknown article identifier $article_id **\n";
807 my ($self, $articles, $acts, $article, $embedded) = @_;
809 # used to generate the side menu
810 my $section_index = -1;
811 my @sections = $articles->listedChildren(-1);
812 #sort { $a->{displayOrder} <=> $b->{displayOrder} }
813 #grep $_->{listed}, $articles->sections;
814 my $subsect_index = -1;
815 my @subsections; # filled as we move through the sections
816 my @level3; # filled as we move through the subsections
817 my $level3_index = -1;
819 my $cfg = $self->{cfg} || BSE::Cfg->single;
820 my %extras = $cfg->entriesCS('extra tags');
821 for my $key (keys %extras) {
823 my $data = $cfg->entryVar('extra tags', $key);
824 $extras{$key} = sub { $data };
829 my $it = BSE::Util::Iterate->new;
830 my $art_it = BSE::Util::Iterate::Article->new(cfg => $cfg,
831 admin => $self->{admin},
832 top => $self->{top});
837 custom_class($cfg)->base_tags($articles, $acts, $article, $embedded, $cfg),
839 BSE::Util::Tags->static($acts, $self->{cfg}),
840 # for embedding the content from children and other sources
841 ifEmbedded=> sub { $embedded },
843 my ($args, $acts, $name, $templater) = @_;
844 my ($what, $template, $maxdepth) = split ' ', $args;
845 undef $maxdepth if defined $maxdepth && $maxdepth !~ /^\d+/;
846 return $self->_embed_low($acts, $articles, $what, $template, $maxdepth, $templater);
848 ifCanEmbed=> sub { $self->{depth} <= $self->{maxdepth} },
852 my ($args, $acts, $name, $templater) = @_;
853 my ($which, $limit) = DevHelp::Tags->get_parms($args, $acts, $templater);
854 $which or $which = "child";
855 $limit or $limit = $article->{summaryLength};
857 or return "<:summary $which Cannot find $which:>";
858 my $id = $templater->perform($acts, $which, "id")
859 or return "<:summary $which No id returned :>";
860 my $article = $articles->getByPkey($id)
861 or return "<:summary $which Cannot find article $id:>";
862 return $self->summarize($articles, $article->{body}, $acts, $limit);
864 ifAdmin => sub { $self->{admin} },
865 ifAdminLinks => sub { $self->{admin_links} },
867 # for generating the side menu
868 iterate_level1_reset => sub { $section_index = -1 },
869 iterate_level1 => sub {
871 if ($section_index < @sections) {
872 #@subsections = grep $_->{listed},
873 # $articles->children($sections[$section_index]->{id});
874 @subsections = grep { $_->{listed} != 2 }
875 $articles->listedChildren($sections[$section_index]->{id});
884 return tag_article($sections[$section_index], $cfg, $_[0]);
887 # used to generate a list of subsections for the side-menu
888 iterate_level2 => sub {
890 if ($subsect_index < @subsections) {
891 @level3 = grep { $_->{listed} != 2 }
892 $articles->listedChildren($subsections[$subsect_index]{id});
899 return tag_article($subsections[$subsect_index], $cfg, $_[0]);
903 return scalar @subsections;
906 # possibly level3 items
907 iterate_level3 => sub {
908 return ++$level3_index < @level3;
911 tag_article($level3[$level3_index], $cfg, $_[0])
913 ifLevel3 => sub { scalar @level3 },
915 # generate an admin or link url, depending on admin state
918 my ($name, $acts, $func, $templater) = @_;
919 my $item = $self->{admin_links} ? 'admin' : 'link';
922 my $url = $templater->perform($acts, $name, $item);
923 if (!$self->{admin} && $self->{admin_links}) {
924 $url .= $url =~ /\?/ ? "&" : "?";
925 $url .= "admin=0&admin_links=1";
931 $acts->{$_[0]} or return 0;
932 return $acts->{$_[0]}->('listed') == 1;
936 my ($image, $text) = split ' ', $_[0];
937 if (-e $IMAGEDIR."/titles/".$image) {
938 return qq!<img src="/images/titles/!.$image .qq!" border=0>!
941 return escape_html($text);
944 $art_it->make( code => [ iter_kids_of => $self ],
946 plural => 'children_of',
949 $art_it->make( code => [ iter_kids_of => $self ],
950 single => 'ofchild2',
951 plural => 'children_of2',
954 $art_it->make( code => [ iter_kids_of => $self ],
955 single => 'ofchild3',
956 plural => 'children_of3',
959 $art_it->make( code => [ iter_all_kids_of => $self ],
960 single => 'ofallkid',
961 plural => 'allkids_of',
963 $art_it->make( code => [ iter_all_kids_of => $self ],
964 single => 'ofallkid2',
965 plural => 'allkids_of2',
968 $art_it->make( code => [ iter_all_kids_of => $self ],
969 single => 'ofallkid3',
970 plural => 'allkids_of3',
973 $art_it->make( code => [ iter_all_kids_of => $self ],
974 single => 'ofallkid4',
975 plural => 'allkids_of4',
978 $art_it->make( code => [ iter_all_kids_of => $self ],
979 single => 'ofallkid5',
980 plural => 'allkids_of5',
983 $art_it->make( code => [ iter_inlines => $self ],
990 my ($args, $acts, $func, $templater) = @_;
991 my ($name, $align, @rest) =
992 DevHelp::Tags->get_parms($args, $acts, $templater);
997 $im = $current_gimage
1001 $im = $self->get_gimage($name)
1005 $self->_format_image($im, $align, $rest);
1007 $it->make_iterator( [ \&iter_gimages, $self ], 'gimagei', 'gimages',
1008 undef, undef, undef, \$current_gimage),
1011 my ($name, $field) = split ' ', $_[0], 3;
1013 my $file = $self->get_gfile($name)
1016 $self->_format_file($file, $field);
1018 $it->make_iterator( [ \&iter_gfiles, $self ], 'gfilei', 'gfiles'),
1019 gthumbimage => [ tag_gthumbimage => $self, \$current_gimage ],
1020 sthumbimage => [ tag_sthumbimage => $self ],
1021 simage => [ tag_simage => $self ],
1022 $it->make_iterator( [ iter_vimages => $self, $article ], 'vimage', 'vimages', undef, undef, undef, \$current_vimage),
1023 vimage => [ tag_vimage => $self, \$current_vimage ],
1024 vthumbimage => [ tag_vthumbimage => $self, \$current_vimage ],
1029 my ($body, $case_sensitive, $terms) = @_;
1033 if ($case_sensitive) {
1034 for my $term (@$terms) {
1035 if ($$body =~ /^(.*?)\Q$term/s) {
1036 push(@found, [ length($1), $term ]);
1041 for my $term (@$terms) {
1042 if ($$body =~ /^(.*?)\Q$term/is) {
1043 push(@found, [ length($1), $term ]);
1051 # this takes the same inputs as _make_table(), but eliminates any
1053 sub _cleanup_table {
1054 my ($opts, $data) = @_;
1055 my @lines = split /\n/, $data;
1060 return join(' ', @lines);
1063 # produce a nice excerpt for a found article
1065 my ($self, $article, $found, $case_sensitive, $terms, $type, $body) = @_;
1068 $body = $article->{body};
1070 # we remove any formatting tags here, otherwise we get wierd table
1071 # rubbish or other formatting in the excerpt.
1072 my @files = $article->files;
1073 $self->remove_block('Articles', [], \$body, \@files);
1074 1 while $body =~ s/[bi]\[([^\]\[]+)\]/$1/g;
1077 $body = escape_html($body);
1081 my @found = find_terms(\$body, $case_sensitive, $terms);
1083 my @reterms = @$terms;
1089 # do a reverse sort so that the longer terms (and composite
1090 # terms) are replaced first
1091 my $re_str = join("|", reverse sort @reterms);
1093 my $cfg = $self->{cfg};
1094 if ($cfg->entryBool('search', 'highlight_partial', 1)) {
1095 $re = $case_sensitive ? qr/\b($re_str)/ : qr/\b($re_str)/i;
1098 $re = $case_sensitive ? qr/\b($re_str)\b/ : qr/\b($re_str)\b/i;
1101 # this used to try searching children as well, but it broke more
1104 # we tried hard and failed
1105 # return a generic article
1106 if (length $body > $excerptSize) {
1107 $body = substr($body, 0, $excerptSize);
1108 $body =~ s/\S+\s*$/.../;
1115 splice(@found, 5,-1) if @found > 5;
1116 my $itemSize = $excerptSize / @found;
1118 # try to combine any that are close
1119 @found = sort { $a->[0] <=> $b->[0] } @found;
1120 for my $i (reverse 0 .. $#found-1) {
1121 if ($found[$i+1][0] - $found[$i][0] < $itemSize) {
1122 my @losing = @{$found[$i+1]};
1124 push(@{$found[$i]}, @losing);
1125 splice(@found, $i+1, 1); # remove it
1129 my $highlight_prefix =
1130 $cfg->entry('search highlight', "${type}_prefix", "<b>");
1131 my $highlight_suffix =
1132 $cfg->entry('search highlight', "${type}_suffix", "</b>");
1133 my $termSize = $excerptSize / @found;
1135 for my $term (@found) {
1136 my ($pos, @terms) = @$term;
1137 my $start = $pos - $termSize/2;
1141 $part = substr($body, 0, $termSize);
1145 $part = substr($body, $start, $termSize);
1148 if ($start + $termSize < length $body) {
1149 $part =~ s/\s*\S*$/... /;
1153 $result =~ s{$re}{$highlight_prefix$1$highlight_suffix}ig;
1164 # make whatever text $body points at safe for summarizing by removing most
1165 # block level formatting
1167 my ($self, $articles, $acts, $body, $files) = @_;
1169 my $formatter_class = $self->formatter_class;
1173 my $formatter = $formatter_class->new(gen => $self,
1175 article => $articles,
1176 articles => $articles,
1179 $$body = $formatter->remove_format($$body);
1183 my ($self, $name) = @_;
1185 unless ($self->{gimages}) {
1186 require BSE::TB::Images;
1187 my @gimages = BSE::TB::Images->getBy(articleId => -1);
1188 my %gimages = map { $_->{name} => $_ } @gimages;
1189 $self->{gimages} = \%gimages;
1192 return $self->{gimages}{$name};
1196 my ($self, $name) = @_;
1198 unless ($self->{gfiles}) {
1199 my @gfiles = Articles->global_files;
1200 my %gfiles = map { $_->{name} => $_ } @gfiles;
1201 $self->{gfiles} = \%gfiles;
1204 return $self->{gfiles}{$name};
1207 # note: this is called by BSE::Formatter::thumbimage(), update that if
1209 sub do_gthumbimage {
1210 my ($self, $geo_id, $image_id, $field, $current) = @_;
1213 if ($image_id eq '-' && $current) {
1217 $im = $self->get_gimage($image_id);
1220 or return '** unknown global image id **';
1222 return $self->_sthumbimage_low($geo_id, $im, $field);
1225 sub get_real_article {
1226 my ($self, $article) = @_;
1237 Generate - provides base Squirel::Template actions for use in generating
1244 This is probably better documented in L<templates.pod>.
1248 These tags can be used anywhere, including in admin templates. It's
1249 possible some admin code has been missed, if you find a place where
1250 these cannot be used let us know.
1255 =item kb I<data tag>
1257 Formats the give value in kI<whatevers>. If you have a number that
1258 could go over 1000 and you want it to use the 'k' metric prefix when
1259 it does, use this tag. eg. <:kb file sizeInBytes:>
1261 =item date I<data tag>
1263 =item date "I<format>" I<data tag>
1265 Formats a date or date/time value from the database into something
1266 more human readable. If you don't supply a format then the default
1267 format of "%d-%b-%Y" is used ("20-Mar-2002").
1269 The I<format> is a strftime() format specification, if that means
1270 anything to you. If it doesn't, each code starts with % and are
1271 replaced as follows:
1277 abbreviated weekday name
1285 abbreviated month name
1293 "preferred" date and time representation
1297 day of the month as a 2 digit number
1301 hour (24-hour clock)
1305 hour (12-hour clock)
1309 day of year as a 3-digit number
1313 month as a 2 digit number
1317 minute as a 2 digit number
1321 AM or PM or their equivalents
1325 seconds as a 2 digit number
1329 week number as a 2 digit number (first Sunday as the first day of week 1)
1333 weekday as a decimal number (0-6)
1337 week number as a 2 digit number (first Monday as the first day of week 1)
1341 the locale's appropriate date representation
1345 the locale's appropriate time representation
1349 2-digit year without century
1357 time zone name or abbreviation
1365 Your local strftime() implementation may implement some extensions to
1366 the above, if your server is on a Unix system try running "man
1367 strftime" for more information.
1369 =item bodytext I<data tag>
1371 Formats the text from the given tag in the same way that body text is.
1373 =item ifEq I<data1> I<data2>
1375 Checks if the 2 values are exactly equal. This is a string
1378 The 2 data parameters can either be a tag reference in [], a literal
1379 string inside "" or a single word.
1381 =item ifMatch I<data1> I<data2>
1383 Treats I<data2> as a perl regular expression and attempts to match
1384 I<data1> against it.
1386 The 2 data parameters can either be a tag reference in [], a literal
1387 string inside "" or a single word.
1389 =item cfg I<section> I<key>
1391 =item cfg I<section> I<key> I<default>
1393 Retrieves a value from the BSE configuration file.
1395 If you don't supply a default then a default will be the empty string.
1399 The release number of BSE.
1409 Conditional tag, true if generating in admin mode.
1411 =item iterator ... level1
1413 Iterates over the listed level 1 articles.
1415 =item level1 I<name>
1417 The value of the I<name> field of the current level 1 article.
1419 =item iterator ... level2
1421 Iterates over the listed level 2 children of the current level 1 article.
1423 =item level2 I<name>
1425 The value of the I<name> field of the current level 2 article.
1427 =item ifLevel2 I<name>
1429 Conditional tag, true if the current level 1 article has any listed
1432 =item iterator ... level3
1434 Iterates over the listed level 3 children of the current level 2 article.
1436 =item level3 I<name>
1438 The value of the I<name> field of the current level 3 article.
1440 =item ifLevel3 I<name>
1442 Conditional tag, true if the current level 2 article has any listed
1447 Returns a link to the specified article . Due to the way the action
1448 list is built, this can be article types defined in derived classes of
1449 Generate, like the C<parent> article in Generate::Article.
1451 =item money I<data tag>
1453 Formats the given value as a monetary value. This does not include a
1454 currency symbol. Internally BSE stores monetary values as integers to
1455 prevent the loss of accuracy inherent in floating point numbers. You
1456 need to use this tag to display any monetary value.
1458 =item ifInMenu I<which>
1460 Conditional tag, true if the given item can appear in a menu.
1462 =item titleImage I<imagename> I<text>
1464 Generates an IMG tag if the given I<imagename> is in the title image
1465 directory ($IMAGEDIR/titles). If it doesn't exists, produces the
1468 =item embed I<which>
1470 =item embed I<which> I<template>
1472 =item embed I<which> I<template> I<maxdepth>
1476 Embeds the article specified by which using either the specified
1477 template or the articles template.
1479 In this case I<which> can also be an article ID.
1481 I<template> is a filename relative to the templates directory. If
1482 this is "-" then the articles template is used (so you can set
1483 I<maxdepth> without setting the template.) If I<template> contains a
1484 C<$> sign it will be replaced with the name of the original template.
1486 If I<maxdepth> is supplied and is less than the current maximum depth
1487 then it becomes the new maximum depth. This can be used with ifCanEmbed.
1489 =item embed start ... embed end
1491 Marks the range of text that would be embedded in a parent that used
1496 Conditional tag, true if the current article is being embedded.
1502 Needs more documentation.