]> git.imager.perl.org - bse.git/blame - site/cgi-bin/modules/BSE/Util/DynamicTags.pm
implement access to the cart for the new template tagging
[bse.git] / site / cgi-bin / modules / BSE / Util / DynamicTags.pm
CommitLineData
57d988af
TC
1package BSE::Util::DynamicTags;
2use strict;
c76e86ea 3use BSE::Util::Tags qw(tag_article);
3f9c8a96 4use BSE::Util::HTML;
b864cc90 5use base 'BSE::ThumbLow';
47c75494 6use base 'BSE::TagFormats';
55e7c0f4 7use BSE::CfgInfo qw(custom_class);
11af7272 8use BSE::Cart;
57d988af 9
11af7272 10our $VERSION = "1.026";
76c6b28e
TC
11
12=head1 NAME
13
14BSE::Util::DynamicTags - common dynamic page tags for BSE.
15
16=head1 SYNOPSIS
17
18 # in the code
19 my %acts =
20 (
21 $req->dyn_user_tags(),
22 ...
23 );
24
25 # in the page
26
81aa5f57 27 <:user userId:>
76c6b28e
TC
28 ...
29
30=head1 DESCRIPTION
31
32This module defines the common set of tags available on public dynamic
33pages.
34
35=head1 METHODS
36
37=over
38
39=item new
40
41Create a new tags object, accepts a single parameter which is a
42L<BSE::Request> object.
43
44=cut
cb7fd78d 45
54c97cf6 46sub new {
57d988af 47 my ($class, $req) = @_;
54c97cf6
TC
48 return bless { req => $req }, $class;
49}
50
76c6b28e 51=item tags
736c2142 52
76c6b28e 53Returns the common tags.
736c2142 54
736c2142
TC
55=cut
56
54c97cf6
TC
57sub tags {
58 my ($self) = @_;
57d988af 59
54c97cf6 60 my $req = $self->{req};
11af7272 61
57d988af
TC
62 return
63 (
ebec7b06 64 BSE::Util::Tags->common($req),
57d988af
TC
65 user => [ \&tag_user, $req ],
66 ifUser => [ \&tag_ifUser, $req ],
67 ifUserCanSee => [ \&tag_ifUserCanSee, $req ],
c76e86ea
TC
68 $self->dyn_article_iterator('dynlevel1s', 'dynlevel1'),
69 $self->dyn_article_iterator('dynlevel2s', 'dynlevel2'),
70 $self->dyn_article_iterator('dynlevel3s', 'dynlevel3'),
71 $self->dyn_article_iterator('dynallkids_of', 'dynofallkid'),
1d9a2f7f
TC
72 $self->dyn_article_iterator('dynallkids_of2', 'dynofallkid2'),
73 $self->dyn_article_iterator('dynallkids_of3', 'dynofallkid3'),
c76e86ea 74 $self->dyn_article_iterator('dynchildren_of', 'dynofchild'),
cae48ae5 75 $self->dyn_iterator('dyncart', 'dyncartitem'),
76c6b28e
TC
76 $self->dyn_article_iterator('wishlist', 'wishlistentry'),
77 $self->dyn_iterator('dynunused_tagcats', 'dynunused_tagcat'),
78 $self->dyn_iterator('dynunused_tags', 'dynunused_tag'),
79 $self->dyn_iterator('dyntags', 'dyntag'),
54c97cf6 80 url => [ tag_url => $self ],
cae48ae5
TC
81 dyncarttotalcost => [ tag_dyncarttotal => $self, 'total_cost' ],
82 dyncarttotalunits => [ tag_dyncarttotal => $self, 'total_units' ],
54c97cf6 83 ifAncestor => 0,
c5286ebe 84 ifUserMemberOf => [ tag_ifUserMemberOf => $self ],
b864cc90 85 dthumbimage => [ tag_dthumbimage => $self ],
ef4a712d 86 dgthumbimage => [ tag_dgthumbimage => $self ],
f5505b76 87 dyntarget => [ tag_dyntarget => $self ],
47c75494
TC
88 $self->dyn_iterator('dynvimages', 'dynvimage'),
89 dynvimage => [ tag_dynvimage => $self ],
90 dynvthumbimage => [ tag_dynvthumbimage => $self ],
c6fc339f 91 recaptcha => [ tag_recaptcha => $self, $req ],
ebec7b06 92 dyncatmsg => [ tag_dyncatmsg => $self, $req ],
c777cbaa 93 $self->dyn_iterator("userfiles", "userfile"),
590bd52e 94 $self->dyn_iterator_obj("paidfiles", "paidfile"),
dfd483db
TC
95 price => [ tag_price => $self ],
96 ifTieredPricing => [ tag_ifTieredPricing => $self ],
55e7c0f4 97 $self->_custom_tags,
57d988af
TC
98 );
99}
100
55e7c0f4
TC
101sub _custom_tags {
102 my ($self) = @_;
103
104 $self->cfg->entry('custom', 'dynamic_tags')
105 or return;
106
107 return custom_class($self->cfg)->dynamic_tags($self->req);
108}
109
76c6b28e
TC
110=item cfg
111
112Return a cfg object.
113
114=cut
115
55e7c0f4
TC
116sub cfg {
117 return $_[0]{req}->cfg;
118}
119
76c6b28e
TC
120=item cgi
121
122Return the cgi object.
123
124=cut
125
55e7c0f4
TC
126sub cgi {
127 return $_[0]{req}->cgi;
128}
129
76c6b28e
TC
130=item req
131
132Return the request object.
133
134=cut
135
55e7c0f4
TC
136sub req {
137 return $_[0]{req};
138}
139
76c6b28e
TC
140=item admin_mode
141
142Return true if in admin mode.
143
144=cut
145
146sub admin_mode {
147 return 0;
148}
149
81aa5f57
TC
150=back
151
76c6b28e
TC
152=head1 COMMON DYNAMIC TAGS
153
154=over
155
156=item ifUser
157=synopsis <:if User:><:user userId:><:or:>Not logged in<:eif:>
158
159With parameters, check if there is a user currenly logged in.
160
161Without, check if the given attribute of the currently logged in user
162is a true perl value.
163
164=cut
165
57d988af
TC
166sub tag_ifUser {
167 my ($req, $args) = @_;
168
169 my $user = $req->siteuser
170 or return '';
171 if ($args) {
172 return $user->{$args};
173 }
174 else {
175 return 1;
176 }
177}
178
76c6b28e
TC
179=item user
180
181Retrieve an attribute from the currently logged in user.
182
183Returns an empty string if the user isn't logged in or if the
184attribute is unknown.
185
186=cut
187
57d988af
TC
188sub tag_user {
189 my ($req, $args) = @_;
190
191 my $siteuser = $req->siteuser
192 or return '';
193
194 exists $siteuser->{$args}
195 or return '';
196
197 escape_html($siteuser->{$args});
198}
199
76c6b28e
TC
200=item ifUserCanSee
201=synopsis <:ifUserCanSee 3:><a href="/shop/">See the shop</a><:or:><:eif:>
202
203Tests if the currently logged in siteuser has access to the named or
204numbered article.
205
206=cut
207
57d988af
TC
208sub tag_ifUserCanSee {
209 my ($req, $args) = @_;
210
211 $args
212 or return 0;
213
214 my $article;
215 if ($args =~ /^\d+$/) {
3c6b4eaf 216 require Articles;
57d988af
TC
217 $article = Articles->getByPkey($args);
218 }
219 else {
220 $article = $req->get_article($args);
221 }
222 $article
223 or return 0;
224
3c6b4eaf
TC
225 $req->cfg->entry('basic', 'admin_sees_all', 1)
226 and return 1;
227
57d988af
TC
228 $req->siteuser_has_access($article);
229}
230
76c6b28e
TC
231=item ifUserMemberOf
232
233Test if the currently logged in user is a member of the named group.
234
235Accepts [] style parameters.
236
237=cut
238
c5286ebe
TC
239sub tag_ifUserMemberOf {
240 my ($self, $args, $acts, $func, $templater) = @_;
241
242 my $req = $self->{req};
243
244 my $user = $req->siteuser
245 or return 0; # no user, no group
246
247 my ($name) = DevHelp::Tags->get_parms($args, $acts, $templater);
248
249 $name
250 or return 0; # no group name
251
252 require BSE::TB::SiteUserGroups;
253 my $group = BSE::TB::SiteUserGroups->getByName($req->cfg, $name);
254 unless ($group) {
255 print STDERR "Unknown group name '$name' in ifUserMemberOf\n";
256 return 0;
257 }
258
259 return $group->contains_user($user);
260}
261
76c6b28e
TC
262=item dyntarget
263=synopsis <:dyntarget user a_logon 1:>
264
265Generate a url to the specified script with the given parameters.
266
267Accepts [] style parameters.
268
269=cut
270
f5505b76
TC
271sub tag_dyntarget {
272 my ($self, $args, $acts, $func, $templater) = @_;
273
274 my $req = $self->{req};
275
276 my ($script, $target, @options) = DevHelp::Tags->get_parms($args, $acts, $templater);
277
3624a308
TC
278 for my $option (@options) {
279 $option = unescape_html($option);
280 }
281
f5505b76
TC
282 return escape_html($req->user_url($script, $target, @options));
283}
284
76c6b28e
TC
285=item url
286=synopsis <:url dynofallkid:>
287
288Generate a link to the specified article, taking admin mode into
289account.
290
291=cut
292
54c97cf6
TC
293sub tag_url {
294 my ($self, $name, $acts, $func, $templater) = @_;
295
296 my $item = $self->{admin} ? 'admin' : 'link';
297 my $article = $self->{req}->get_article($name)
298 or return "** unknown article $name **";
6866b8dd 299
c76e86ea
TC
300 my $value;
301 if ($item eq 'link' and ref $article ne 'HASH') {
089bef32 302 $value = $article->link($self->{req}->cfg);
c76e86ea
TC
303 }
304 else {
305 $value = $article->{$item};
306 }
6866b8dd
TC
307
308 # we don't know our context, so always produce absolute URLs
309 if ($value !~ /^\w+:/) {
310 $value = $self->{req}->cfg->entryErr('site', 'url') . $value;
311 }
312
313 return escape_html($value);
54c97cf6
TC
314}
315
76c6b28e
TC
316=item iterator dynlevel1s
317
318Iterate over level 1 articles.
319
320=cut
321
54c97cf6
TC
322sub iter_dynlevel1s {
323 my ($self, $unused, $args) = @_;
324
325 my $result = $self->get_cached('dynlevel1');
326 $result
327 and return $result;
328
329 require Articles;
3c6b4eaf 330 $result = $self->access_filter(Articles->listedChildren(-1));
54c97cf6
TC
331 $self->set_cached(dynlevel1 => $result);
332
333 return $result;
334}
335
76c6b28e
TC
336=item iterator dynlevel2s
337
338Iterate over the children of the dynlevel1 article.
339
340=cut
341
54c97cf6
TC
342sub iter_dynlevel2s {
343 my ($self, $unused, $args) = @_;
344
345 my $req = $self->{req};
346 my $parent = $req->get_article('dynlevel1')
347 or return [];
348
349 my $cached = $self->get_cached('dynlevel2');
350 $cached && $cached->[0] == $parent->{id}
351 and return $cached->[1];
352
353 require Articles;
3c6b4eaf 354 my $result = $self->access_filter(Articles->listedChildren($parent->{id}));
54c97cf6
TC
355 $self->set_cached(dynlevel2 => [ $parent->{id}, $result ]);
356
357 return $result;
358}
359
76c6b28e
TC
360=item iterator dynlevel3s
361
362Iterate over the children of the dynlevel2 article.
363
364=cut
365
54c97cf6
TC
366sub iter_dynlevel3s {
367 my ($self, $unused, $args) = @_;
368
369 my $req = $self->{req};
370 my $parent = $req->get_article('dynlevel2')
371 or return [];
372
373 my $cached = $self->get_cached('dynlevel3');
374 $cached && $cached->[0] == $parent->{id}
375 and return $cached->[1];
376
377 require Articles;
3c6b4eaf 378 my $result = $self->access_filter( Articles->listedChildren($parent->{id}));
54c97cf6
TC
379 $self->set_cached(dynlevel3 => [ $parent->{id}, $result ]);
380
381 return $result;
382}
383
76c6b28e
TC
384=item dynallkids_of
385
386Also dynallkids_of2, dynallkids_of3
387
388Iterate over all children of the each of the specified article names
389or ids.
390
391=cut
392
54c97cf6 393sub iter_dynallkids_of {
773c9c63 394 my ($self, $unused, $args, $acts, $templater, $state) = @_;
54c97cf6 395
773c9c63 396 $state->{parentid} = undef;
54c97cf6
TC
397 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
398 for my $id (@ids) {
399 unless ($id =~ /^\d+$|^-1$/) {
400 $id = $self->{req}->get_article($id);
401 }
402 }
1d9a2f7f
TC
403 @ids = grep defined && /^\d+$|^-1$/,
404 map ref() ? $_->{id} : $_, @ids;
3c6b4eaf 405
773c9c63
TC
406 @ids == 1 and $state->{parentid} = $ids[0];
407
3c6b4eaf
TC
408 require Articles;
409 return $self->access_filter(map Articles->all_visible_kids($_), @ids);
54c97cf6
TC
410}
411
81aa5f57
TC
412sub tags_for_dynallkids_of {
413 my ($self, $unused, $args, $acts, $templater) = @_;
414
415 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
416 for my $id (@ids) {
417 unless ($id =~ /^\d+$|^-1$/) {
418 $id = $self->{req}->get_article($id);
419 }
420 }
421 @ids = grep defined && /^\d+$|^-1$/,
422 map ref() ? $_->{id} : $_, @ids;
423
424 @ids
425 or return { tags => [], members => [] };
426
427 require Articles;
428 if (@ids == 1) {
429 return Articles->all_visible_kid_tags($ids[0]);
430 }
431 else {
432 my %tags;
433 my @members;
434 for my $id (@ids) {
435 my $more_tags = Articles->all_visible_kid_tags($id);
436 for my $tag (@{$more_tags->{tags}}) {
437 $tags{$tag->id} = $tag;
438 }
439 push @members, @{$more_tags->{members}};
440 }
441
442 return
443 {
444 tags => [ values %tags ],
445 members => \@members,
446 };
447 }
448}
449
1d9a2f7f
TC
450*iter_dynallkids_of2 = \&iter_dynallkids_of;
451*iter_dynallkids_of3 = \&iter_dynallkids_of;
452
81aa5f57
TC
453*tags_for_dynallkids_of2 = \&tags_dynallkids_of;
454*tags_for_dynallkids_of3 = \&tags_dynallkids_of;
455
76c6b28e
TC
456=item dynchildren_of
457
458Iterate over direct children of each of the specified article names or
459ids.
460
461=cut
462
54c97cf6
TC
463sub iter_dynchildren_of {
464 my ($self, $unused, $args, $acts, $templater) = @_;
465
466 my @ids = map { split } DevHelp::Tags->get_parms($args, $acts, $templater);
467 for my $id (@ids) {
468 unless ($id =~ /^\d+$|^-1$/) {
469 $id = $self->{req}->get_article($id);
470 }
471 }
472 @ids = grep defined && /^\d+$|^-1$/, @ids;
3c6b4eaf
TC
473
474 require Articles;
475 return $self->access_filter( map Articles->listedChildren($_), @ids);
476}
477
76c6b28e
TC
478=item iterator dyncart
479
480Iterate over the contents of the cart.
481
482=cut
483
cae48ae5
TC
484sub iter_dyncart {
485 my ($self, $unused, $args) = @_;
486
487 my $cart = $self->_cart
488 or return [];
489
490 return $cart->{cart};
491}
492
76c6b28e
TC
493=item dyncarttotal
494
495The total cost of the items in the cart, in cents.
496
497=cut
498
cae48ae5
TC
499sub tag_dyncarttotal {
500 my ($self, $field, $args) = @_;
501
502 my $cart = $self->_cart
503 or return 0;
504
505 return $cart->{$field};
506}
507
47c75494
TC
508=item iterator dynvimages
509
510A dynamic version of the vimages iterator.
511
512Items are vimage (which acts like other image tags) and dynvthumbimage.
513
514=cut
515
516sub iter_dynvimages {
517 my ($self, $context, $args, $acts, $templater) = @_;
518
519 my $re;
520 my $num;
521 if ($args =~ s!\s+named\s+/([^/]+)/$!!) {
522 $re = $1;
523 }
524 elsif ($args =~ s!\s+numbered\s+(\d+)$!!) {
525 $num = $1;
526 }
527 my @ids = map { split /[, ]/ }
528 DevHelp::Tags->get_parms($args, $acts, $templater);
529 my @images;
530 for my $article_id (@ids) {
531 my @articles = $self->_find_articles($article_id);
532 for my $article (@articles) {
533 my @aimages = $article->images;
534 if (defined $re) {
6ed138ca 535 push @images, grep $_->{name} =~ /$re/, @aimages;
47c75494
TC
536 }
537 elsif (defined $num) {
538 if ($num >= 0 && $num <= @aimages) {
539 push @images, $aimages[$num-1];
540 }
541 }
542 else {
543 push @images, @aimages;
544 }
545 }
546 }
547
548 return \@images;
549}
550
551=item dynvimage field
552
553=item dynvimage
554
555Item for iterator dynvimages
556
557=cut
558
559sub tag_dynvimage {
560 my ($self, $args) = @_;
561
562 my $im = $self->{req}->get_article('dynvimage')
563 or return '** not in dynvimages iterator **';
564
565 my ($align, $rest) = split ' ', $args, 2;
566
567 return $self->_format_image($im, $align, $rest);
568}
569
570=item dynvthumbimage geometry field
571
572=item dynvthumbimage geometry
573
574Thumbnail of the current vimage.
575
576=cut
577
578sub tag_dynvthumbimage {
579 my ($self, $args) = @_;
580
581 my $im = $self->{req}->get_article('dynvimage')
582 or return '** not in dynvimages iterator **';
583
584 my ($geo, $field) = split ' ', $args;
585
586 return $self->_thumbimage_low($geo, $im, $field, $self->{req}->cfg);
587}
588
589sub _find_articles {
590 my ($self, $article_id) = @_;
591
592 if ($article_id =~ /^\d+$/) {
593 my $result = Articles->getByPkey($article_id);
594 $result or print STDERR "** Unknown article id $article_id **\n";
595 return $result ? $result : ();
596 }
597 elsif ($article_id =~ /^alias\((\w+)\)$/) {
598 my $result = Articles->getBy(linkAlias => $1);
599 $result or print STDERR "** Unknown article alias $article_id **\n";
600 return $result ? $result : ();
601 }
602 elsif ($article_id =~ /^childrenof\((.*)\)$/) {
603 my $id = $1;
604 if ($id eq '-1') {
605 return Articles->all_visible_kids(-1);
606 }
607 else {
608 my @parents = $self->_find_articles($id)
609 or return;
610 return map $_->all_visible_kids, @parents
611 }
612 }
613 else {
614 my $article = $self->{req}->get_article($article_id);
615 $article
616 and return $article;
617 }
618
619 print STDERR "** Unknown article identifier $article_id **\n";
620
621 return;
622}
623
76c6b28e
TC
624=item iterator wishlist
625
626Iterate over the items in the logged in user's wishlist.
627
628=cut
629
d49667a2 630sub iter_wishlist {
76c6b28e 631 my ($self) = @_;
d49667a2 632
76c6b28e 633 my $user = $self->req->siteuser
d49667a2
TC
634 or return [];
635 return [ $user->wishlist ];
636}
637
76c6b28e 638=item iterator dynunused_tagcats
3c6b4eaf 639
76c6b28e
TC
640Iterate over the the tag categories of unused tags in the articles
641selected by the given tags: and filter: parameters.
3c6b4eaf 642
81aa5f57
TC
643Usage:
644
645=over
646
647C<<iterator begin dynunused_tagcats I<iterator-name> I<onlyone-opt> I<iterator arguments> tags:I<tags-filter> >>
648
649=back
650
76c6b28e 651You must supply a tags: filter, even if it's just "".
3c6b4eaf 652
76c6b28e
TC
653There will be an iteration with an empty I<name> for each tag without
654a category.
54c97cf6 655
76c6b28e
TC
656If a parameter "onlyone" is supplied then the list of tag categories
657will not include tag categories that appear in the tags filter.
70789617 658
22606317
TC
659If a filter C<<category: I<expression> >> is supplied, then only
660category names matching I<expression> will be included.
661
76c6b28e 662Each entry has:
70789617 663
76c6b28e 664=over
70789617 665
76c6b28e 666=item * name - name of the category
70789617 667
76c6b28e 668=item * nocat - a category-less tag
70789617 669
76c6b28e 670=item * ind - a unique key for this category.
70789617 671
76c6b28e 672=back
70789617 673
76c6b28e 674=cut
54c97cf6 675
76c6b28e
TC
676sub iter_dynunused_tagcats {
677 my ($self, $unused, $args, $acts, $templater, $state) = @_;
54c97cf6 678
76c6b28e 679 unless ($args =~ s/^(\w+)\s*//) {
81aa5f57 680 print STDERR "* dynunused_tagcats: missing iterator name *\n";
76c6b28e 681 return [];
57fd870e 682 }
76c6b28e
TC
683
684 my $iter = $1;
81aa5f57 685
76c6b28e
TC
686 my $method = "iter_$iter";
687 unless ($self->can($method)) {
688 print STDERR "* Unknown iterator $iter *\n";
689 return [];
57fd870e 690 }
81aa5f57
TC
691
692 my $tags_method = "tags_for_$iter";
693
694 my $filter = $self->{filter};
695 my $selected_tags = $filter->{tags};
696 unless ($selected_tags) {
697 print STDERR "* dynunused_tagcats($iter): no tags: supplied *\n";
698 return [];
699 }
57fd870e 700
76c6b28e 701 my $only_one = $args =~ s/^\s*onlyone\s+//;
81aa5f57
TC
702 my $tags;
703 my %selected_cats = map { $_ => 1 }
704 map { lc ((BSE::TB::Tags->split_name($_))[0]) }
705 @{$selected_tags || []};
54c97cf6 706
22606317
TC
707 my $only_cat;
708 if ($args =~ s/\bcategory:\s*(.*)$//) {
709 ($only_cat) = $templater->get_parms($1, $acts);
710 }
711
76c6b28e
TC
712 my $context = $self->{context}{$iter};
713 my %state =
714 (
715 plural => $iter,
716 single => "unknown",
717 context => $context,
718 );
54c97cf6 719
81aa5f57
TC
720 if ($self->can($tags_method)
721 && !$self->cfg->entry('basic', 'dynamic_access_filter', 1)
722 && !$filter->{filter}) {
723 my $info = $self->$tags_method($context, $args, $acts, $templater);
724 my @tag_ids;
725 my $all_found = 1;
726 my %named = map { lc $_->name => $_ } @{$info->{tags}};
727 my %name_by_id = map { $_->id => $_->name } @{$info->{tags}};
728
729 # find which article ids have the tags we need
730 TAGS:
731 for my $tag_name (@$selected_tags) {
732 my $tag = $named{lc $tag_name};
733 unless ($tag) {
734 # no articles can match an unknown tag, so there's no unused tags
735 return [];
736 }
737 push @tag_ids, $tag->{id};
738 }
739 my %lookup;
740 for my $member (@{$info->{members}}) {
741 $lookup{$member->{owner_id}}{$member->{tag_id}} = $name_by_id{$member->{tag_id}};
742 }
76c6b28e 743
81aa5f57
TC
744 my %extras;
745 ARTICLE:
746 for my $article_id (keys %lookup) {
747 my $tags = $lookup{$article_id} || {};
748 for my $tag_id (@tag_ids) {
749 delete $tags->{$tag_id}
750 or next ARTICLE;
751 }
752 ++$extras{$_} for values %$tags;
753 }
754 $tags = \%extras;
755
756 delete $filter->{tags};
757 }
758 else {
759 my $ignored = $self->_do_filter
760 (\%state, $filter, $args, $acts, $templater, $self->$method
761 ($context, $args, $acts, $templater, \%state));
762 keys %$filter
763 or $self->{filter} = undef;
764
765 $tags = $self->{tags}{$iter};
766 }
76c6b28e 767
5014ba54 768 require Articles;
76c6b28e 769
5014ba54
TC
770 return Articles->categorize_tags
771 (
772 [ keys %$tags ],
773 $selected_tags,
76c6b28e 774 {
5014ba54
TC
775 onlycat => $only_cat,
776 onlyone => $only_one,
777 counts => $tags,
778 },
779 );
54c97cf6
TC
780}
781
76c6b28e 782=item iterator dynunsed_tags
54c97cf6 783
76c6b28e 784Iterate over the unused tags in a category from dynunused_tagcats.
54c97cf6 785
76c6b28e 786Each entry has:
54c97cf6 787
76c6b28e 788=over
590bd52e 789
76c6b28e 790=item * name - the full name of the tag, including category
590bd52e 791
76c6b28e 792=item * cat - the category only
57fd870e 793
76c6b28e 794=item * val - the value only
57fd870e 795
76c6b28e 796=back
57fd870e 797
76c6b28e 798=cut
57fd870e 799
76c6b28e
TC
800sub iter_dynunused_tags {
801 my ($self, $unused, $args) = @_;
802
803 my $cat = $self->{current}{dynunused_tagcats}
804 or return;
805
806 return $cat->{vals};
57fd870e
TC
807}
808
76c6b28e
TC
809=item dyntags
810=synopsis <:iterator begin dyntags [lcgi tags]:>
57fd870e 811
76c6b28e 812Iterate over a list of tags.
590bd52e 813
76c6b28e 814=cut
590bd52e 815
76c6b28e
TC
816sub iter_dyntags {
817 my ($self, $unused, $args, $acts, $templater) = @_;
57fd870e 818
76c6b28e
TC
819 my @tags = grep /\S/, map { split '/' } $templater->get_parms($args, $acts);
820
821 my @out;
822 for my $tag (@tags) {
823 my ($cat, $val) = BSE::TB::Tags->split_name($tag);
824
825 push @out,
826 {
827 name => BSE::TB::Tags->make_name($cat, $val),
828 cat => $cat,
829 val => $val
830 };
57fd870e
TC
831 }
832
76c6b28e 833 return \@out;
57fd870e
TC
834}
835
76c6b28e
TC
836sub access_filter {
837 my ($self, @articles) = @_;
57fd870e 838
76c6b28e 839 my $req = $self->{req};
57fd870e 840
76c6b28e 841 my $admin_sees_all = $req->cfg->entry('basic', 'admin_sees_all', 1);
54c97cf6 842
76c6b28e
TC
843 $admin_sees_all && $self->{admin} and
844 return \@articles;
54c97cf6 845
81aa5f57
TC
846 $req->cfg->entry('basic', 'dynamic_access_filter', 1)
847 or return \@articles;
848
76c6b28e 849 return [ grep $req->siteuser_has_access($_), @articles ];
54c97cf6
TC
850}
851
76c6b28e 852=item dthumbimage
54c97cf6 853
76c6b28e 854Either:
54c97cf6 855
76c6b28e 856=over
54c97cf6 857
76c6b28e 858C<< dynthumbimage I<article> I<geometry> I<image> I<field> >>
54c97cf6 859
76c6b28e 860or
54c97cf6 861
76c6b28e 862C<< dthumbimage I<article> I<geometry> I<image> >>
c76e86ea 863
76c6b28e 864=back
590bd52e 865
76c6b28e
TC
866Similar to thumbimage/gthumbimage, this allows you to retrieve images
867from a given article, which article can either be a number or a named
868article in the current context.
590bd52e 869
76c6b28e 870geometry and field are as for the static thumbimage tag.
773c9c63 871
76c6b28e 872image is a comma separated list of match operators, eg:
773c9c63 873
76c6b28e 874 <:dthumbimage result search search,/^display_$/,1 :>
773c9c63 875
76c6b28e
TC
876on a search page will display either the image with an id of search,
877the first image found with an identifier starting with "display_" or
878the first image of the article.
c76e86ea 879
76c6b28e 880Possible match operators are:
54c97cf6 881
76c6b28e 882=over
54c97cf6 883
76c6b28e 884=item *
54c97cf6 885
76c6b28e 886/regexp/ - a regular expression matched against the image identifier
54c97cf6 887
76c6b28e 888=item *
54c97cf6 889
76c6b28e 890index - a numeric image index, where 1 is the first image
cae48ae5 891
76c6b28e 892=item *
cae48ae5 893
76c6b28e 894identifier - a literal image identifier
cae48ae5 895
76c6b28e 896=back
cae48ae5 897
76c6b28e 898=cut
cae48ae5 899
b864cc90
TC
900sub tag_dthumbimage {
901 my ($self, $args) = @_;
902
903 my ($article_id, $geometry, $image_tags, $field) = split ' ', $args;
904
905 my $article;
906 if ($article_id =~ /^\d+$/) {
907 require Articles;
908 $article = Articles->getByPkey($args);
909 }
910 else {
911 $article = $self->{req}->get_article($article_id);
912 }
913 $article
914 or return '';
915
916 my @images = $article->images;
917 my $im;
918 for my $tag (split /,/, $image_tags) {
919 if ($tag =~ m!^/(.*)/$!) {
920 my $re = $1;
921 ($im) = grep $_->{name} =~ /$re/i, @images
922 and last;
923 }
924 elsif ($tag =~ /^\d+$/) {
925 if ($tag >= 1 && $tag <= @images) {
926 $im = $images[$tag-1];
927 last;
928 }
929 }
930 elsif ($tag =~ /^[^\W\d]\w*$/) {
931 ($im) = grep $_->{name} eq $tag, @images
932 and last;
933 }
934 }
935 $im
936 or return '';
937
938 return $self->_thumbimage_low($geometry, $im, $field, $self->{req}->cfg);
939}
940
76c6b28e
TC
941=item dgthumbimage
942
943=over
944
945C<<dgthumbimage I<geometry> I<name> I<field> >>
946
947C<<dgthumbimage I<geometry> I<name> >>
948
949=back
950
951Format a thumbnail for a global image, in dynamic context.
952
953=cut
954
955sub tag_dgthumbimage {
956 my ($self, $args, $acts, $func, $templater) = @_;
ef4a712d
TC
957
958 my ($geometry, $name, $field) =
959 DevHelp::Tags->get_parms($args, $acts, $templater);
960
961 require BSE::TB::Images;
962 my ($im) = BSE::TB::Images->getBy(articleId => -1,
963 name => $name)
964 or return "* no such global image $name *";
965
966 return $self->_thumbimage_low($geometry, $im, $field, $self->{req}->cfg);
967}
968
c6fc339f
TC
969=item recaptcha
970
971Category: dynamic
972
973Produce a recaptcha block.
974
975No parameters, though this may change.
976
977=cut
978
979sub tag_recaptcha {
f8f6cdfe 980 my ($self, $req, $args) = @_;
c6fc339f 981
f8f6cdfe 982 defined $args or $args = '';
c6fc339f 983 require Captcha::reCAPTCHA;
f8f6cdfe 984 my $section = $args =~ /\S/ ? "recaptcha $args" : "recaptcha";
c6fc339f
TC
985 my $api_key = $req->cfg->entry('recaptcha', 'api_public_key')
986 or return "** No reCAPTCHA api_public_key defined **";
987
f8f6cdfe
TC
988 my %opts = $req->cfg->entries($section);
989 delete @opts{qw/api_public_key api_private_key/};
990
c6fc339f
TC
991 my $c = Captcha::reCAPTCHA->new;
992
f8f6cdfe 993 return $c->get_html($api_key, $req->recaptcha_result, scalar $req->is_ssl, \%opts);
c6fc339f
TC
994}
995
ebec7b06
TC
996=item dyncatmsg msgid parameters...
997
998Return a message from the message catalog.
999
1000=cut
1001
1002sub tag_dyncatmsg {
1003 my ($self, $req, $args, $acts, $func, $templater) = @_;
1004
1005 my ($id, @params) = DevHelp::Tags->get_parms($args, $acts, $templater);
1006 $id or return '* no message id for dyncatmsg *';
1007 $id =~ s/^msg:// or return '* invalid message id, no msg: prefix *';
1008 my $cat = $req->message_catalog;
1009
1010 my $html = $cat->html($req->language, $id, \@params);
1011
1012 if ($self->{admin}) {
1013 $html = qq(<div class="bse_catmsg">$html</div>);
1014 }
1015
1016 return $html;
1017}
1018
c777cbaa
TC
1019my %num_file_fields = map { $_=> 1 }
1020 qw/id owner_id size_in_bytes/;
1021
1022sub iter_userfiles {
1023 my ($self, $unused, $args) = @_;
1024
1025 my $req = $self->{req};
1026 my $user = $req->siteuser
1027 or return [];
1028
1029 my @files = map $_->data_only, $user->visible_files($req->cfg);
1030 require BSE::TB::OwnedFiles;
1031 my %catnames = map { $_->{id} => $_->{name} } BSE::TB::OwnedFiles->categories($req->cfg);
1032
1033 # produce a url for each file
1034 my $base = '/cgi-bin/user.pl?a_downufile=1&id=';
1035 for my $file (@files) {
1036 $file->{url} = $base . $file->{id};
1037 $file->{catname} = $catnames{$file->{category}} || $file->{category};
1038 $file->{new} = $file->{modwhen} gt $user->previousLogon;
1039 }
1040 defined $args or $args = '';
1041
1042 my $sort;
1043 if ($args =~ s/\bsort:\s?(-?\w+(?:,-?\w+)*)\b//) {
1044 $sort = $1;
1045 }
1046 my $cgi_sort = $req->cgi->param('userfile_sort');
1047 $cgi_sort
1048 and $sort = $cgi_sort;
1049 if ($sort && @files > 1) {
1050 my @fields = map
1051 {
1052 my $work = $_;
1053 my $rev = $work =~ s/^-//;
1054 [ $rev, $work ]
1055 } split /,/, $sort;
1056
1057 @fields = grep exists $files[0]{$_->[1]}, @fields;
1058
1059 @files = sort
1060 {
1061 for my $field (@fields) {
1062 my $name = $field->[1];
1063 my $diff = $num_file_fields{$name}
1064 ? $a->{$name} <=> lc $b->{$name}
1065 : $a->{$name} cmp lc $b->{$name};
1066 if ($diff) {
1067 return $field->[0] ? -$diff : $diff;
1068 }
1069 }
1070 return 0;
1071 } @files;
1072 }
1073
1074 $args =~ /\S/
1075 or return \@files;
1076
1077 if ($args =~ /^\s*filter:(.*)$/) {
1078 my $expr = $1;
052ad8b5 1079 my $func = eval 'sub { my ($file, $state) = @_;' . $expr . '}';
c777cbaa
TC
1080 unless ($func) {
1081 print STDERR "** Cannot compile userfile filter $expr: $@\n";
1082 return;
1083 }
052ad8b5
TC
1084 my %state;
1085 return [ grep $func->($_, \%state), @files ];
c777cbaa
TC
1086 }
1087
1088 if ($args =~ /^\s*(!)?(\w+(?:,\w+)*)\s*$/) {
1089 my ($not, $cats) = ( $1, $2 );
1090 my %matches = map { $_ => 1 } split ',', $cats, -1;
1091 if ($not) {
1092 return [ grep !$matches{$_->{category}}, @files ];
1093 }
1094 else {
1095 return [ grep $matches{$_->{category}}, @files ];
1096 }
1097 }
1098
1099 print STDERR "** unparsable arguments to userfile: $args\n";
1100
1101 return [];
1102}
c6fc339f 1103
76c6b28e
TC
1104=item iterator paid_files
1105
1106Iterates over the files the user has paid for.
1107
1108=cut
1109
736c2142
TC
1110sub iter_paidfiles {
1111 my ($self, $unused, $args) = @_;
1112
1113 my $user = $self->req->siteuser
1114 or return [];
1115
1116 return [ $user->paid_files ];
1117}
1118
773c9c63
TC
1119sub tag_dynmove {
1120 my ($self, $rindex, $rrdata, $url_prefix, $args, $acts, $templater) = @_;
1121
1122 return '' unless $self->admin_mode;
1123
1124 return '' unless $$rrdata && @$$rrdata > 1;
1125
1126 require BSE::Arrows;
1127 *make_arrows = \&BSE::Arrows::make_arrows;
1128
1129 my ($img_prefix, $url_add) =
1130 DevHelp::Tags->get_parms($args, $acts, $templater);
1131 defined $img_prefix or $img_prefix = '';
1132 defined $url_add or $url_add = '';
1133 my $refresh_to = $ENV{SCRIPT_NAME} . "?id=" .
1134 $self->{req}->get_article('dynarticle')->{id} . $url_add;
1135 my $move = "$Constants::CGI_URI/admin/move.pl?";
1136 $move .= $url_prefix . '&' if $url_prefix;
1137 $move .= 'd=swap&id=' . $$$rrdata[$$rindex]{id} . '&';
1138 my $down_url = '';
1139 if ($$rindex < $#$$rrdata) {
1140 $down_url = $move . 'other=' . $$$rrdata[$$rindex+1]{id};
1141 }
1142 my $up_url = '';
1143 if ($$rindex > 0) {
1144 $up_url = $move . 'other=' . $$$rrdata[$$rindex-1]{id};
1145 }
1146
1147 return make_arrows($self->{req}->cfg, $down_url, $up_url, $refresh_to, $img_prefix);
1148}
1149
dfd483db
TC
1150=item price
1151
1152Return the price of a product.
1153
1154One of two parameters:
1155
1156=over
1157
1158=item *
1159
1160I<product> - the product to fetch the price for. This can be a name
1161or [] evaluating to a product id.
1162
1163=item *
1164
1165I<field> - "price" to fetch the price, "discount" to fetch the
1166difference from the base price, "discountpc" to fetch the discount in
1167percent (whole number). Returns the price if no I<field> is
1168specified.
1169
1170=back
1171
1172=cut
1173
1174sub tag_price {
1175 my ($self, $args, $acts, $func, $templater) = @_;
1176
1177 my ($id, $field) = $templater->get_parms($args, $acts);
1178 $field ||= "price";
1179
1180 my $work;
1181 if ($id =~ /^[0-9]+$/) {
1182 require Products;
1183 $work = Products->getByPkey($id)
1184 or return "** unknown product $id **";
1185 }
1186 else {
1187 $work = $self->{req}->get_article($id)
1188 or return "** unknown product name $id **";
1189 }
1190
1191 my ($price, $tier) = $work->price(user => scalar $self->{req}->siteuser);
1192
1193 if ($field eq "price") {
1194 return $price;
1195 }
1196 elsif ($field eq "discount") {
1197 return $work->retailPrice - $price;
1198 }
1199 elsif ($field eq "discountpc") {
1200 $work->retailPrice or return "";
1201 return sprintf("%.0f", ($work->retailPrice - $price) / $work->retailPrice * 100);
1202 }
1203 else {
1204 return "** unknown field $field **";
1205 }
1206}
1207
1208=item ifTieredPricing
1209
1210Conditional to check if there's tiered pricing.
1211
1212=cut
1213
1214sub tag_ifTieredPricing {
1215 require Products;
1216 my @tiers = Products->pricing_tiers;
1217
1218 return scalar @tiers;
1219}
1220
76c6b28e 1221=back
b864cc90 1222
76c6b28e
TC
1223=head2 Dynamic iterator filter syntax
1224
1225There a two types filters:
1226
1227=over
1228
1229=item * code filters - filters specified as perl code
1230
1231=item * tag filters - filtering on tags (articles only)
1232
1233=back
1234
1235=head3 Code filters
b864cc90 1236
76c6b28e 1237Specified as:
b864cc90 1238
76c6b28e
TC
1239=over
1240
1241C<< filter: I<perl code> >>
1242
1243=back
1244
1245The text C<ARTICLE> is replaced with the article being tested.
1246
1247The text C<<[I<column-name>]>> is replaced with that attribute of the
1248article.
1249
1250=head3 Tag filters
1251
1252Should be a simple [] expression specifying the tags to filter on:
b864cc90
TC
1253
1254=over
1255
76c6b28e 1256C<tags: [lcgi tags]>
b864cc90 1257
76c6b28e 1258=back
b864cc90 1259
76c6b28e 1260=cut
b864cc90 1261
76c6b28e 1262my $cols_re; # cache for below
b864cc90 1263
76c6b28e
TC
1264sub _get_filter {
1265 my ($self, $state, $rargs, $acts, $templater) = @_;
b864cc90 1266
76c6b28e 1267 my %filter;
b864cc90 1268
76c6b28e
TC
1269 if ($$rargs =~ s/tags:\s*(.*)\z//s) {
1270 my $expr = $1;
1271 my @match = $templater->get_parms($expr, $acts);
b864cc90 1272
76c6b28e
TC
1273 # always add the tags filter even if no tags were listed
1274 # this means the other tag stuff continues to work
1275 my @tags = grep length, map split('/'), @match;
1276
1277 $filter{tags} = \@tags;
1278 }
1279
1280 if ($$rargs =~ s/filter:\s+(.*)\z//s) {
1281 my $expr = $1;
1282 my $orig_expr = $expr;
1283 unless ($cols_re) {
1284 require Articles;
1285 my $cols_expr = '(' . join('|', Article->columns) . ')';
1286 $cols_re = qr/\[$cols_expr\]/;
1287 }
1288 $expr =~ s/$cols_re/\$article->{$1}/g;
1289 $expr =~ s/ARTICLE/\$article/g;
1290 #print STDERR "Expr $expr\n";
1291 my $filter;
1292 $filter = eval 'sub { my $article = shift; '.$expr.'; }';
1293 if ($@) {
1294 print STDERR "** Failed to compile filter expression >>$expr<< built from >>$orig_expr<<\n";
1295 return;
1296 }
1297
1298 $filter{code} = $filter;
1299 }
1300
1301 return \%filter;
1302}
1303
1304sub _do_filter {
81aa5f57 1305 my ($self, $state, $filter, $args, $acts, $templater, $articles) = @_;
76c6b28e
TC
1306
1307 $filter
1308 or return $articles;
1309
1310 if (my $code = delete $filter->{code}) {
1311 $articles = [ grep $code->($_), @$articles ];
1312 }
1313
1314 if (my $tags = delete $filter->{tags}) {
1315 my @out;
1316 my %extras;
1317
81aa5f57
TC
1318 my $tags_method = "tags_for_$state->{plural}";
1319
1320 if ($self->can($tags_method)) {
1321 # should have:
1322 # tags - tags used for these articles
1323 # members - member objects for these articles
1324 my $info = $self->$tags_method($state->{context}, $args, $acts, $templater);
1325
1326 my @tag_ids;
1327 my $all_found = 1;
1328 my %named = map { lc $_->name => $_ } @{$info->{tags}};
1329 my %name_by_id = map { $_->id => $_->name } @{$info->{tags}};
1330 TAGS:
1331 for my $tag_name (@$tags) {
1332 my $tag = $named{lc $tag_name};
1333 unless ($tag) {
1334 $all_found = 0;
1335 last TAGS;
1336 }
1337 #push @tags, $tag;
1338 push @tag_ids, $tag->{id};
1339 }
1340
1341 if ($all_found) {
1342 # build a lookup table
1343 my %lookup;
1344 for my $member (@{$info->{members}}) {
1345 $lookup{$member->{owner_id}}{$member->{tag_id}} = $name_by_id{$member->{tag_id}};
1346 }
1347
1348 ARTICLE:
1349 for my $art (@$articles) {
1350 my $tags = $lookup{$art->id} || {};
1351 for my $tag_id (@tag_ids) {
1352 delete $tags->{$tag_id}
1353 or next ARTICLE;
1354 }
1355 push @out, $art;
1356 ++$extras{$_} for values %$tags;
1357 }
1358 }
1359 }
1360 else {
1361 my @tag_ids;
1362 my @tags;
1363 my $all_found = 1;
1364 TAGS:
1365 for my $tag_name (@$tags) {
1366 my $tag = Articles->getTagByName($tag_name);
1367 unless ($tag) {
1368 # can't find a tag that doesn't exist
1369 $all_found = 0;
1370 last TAGS;
1371 }
1372 push @tags, $tag;
1373 push @tag_ids, $tag->id;
1374 }
1375 if ($all_found) {
1376 ARTICLE:
1377 for my $art (@$articles) {
1378 my %tags = map { $_->id => $_ } $art->tag_objects;
1379 for my $tag_id (@tag_ids) {
1380 $tags{$tag_id}
1381 or next ARTICLE;
1382 delete $tags{$tag_id};
1383 }
1384 push @out, $art;
1385 ++$extras{$_} for map $_->name, values %tags; # as long as they exist
1386 }
76c6b28e 1387 }
76c6b28e 1388 }
76c6b28e
TC
1389
1390 $articles = \@out;
81aa5f57
TC
1391
1392 $self->{tags}{$state->{plural}} = \%extras;
76c6b28e
TC
1393 }
1394
1395 return $articles;
1396}
1397
1398my $paged_re =
1399 qr(
1400 \bpaged:
1401 (?:(\w+)=)? # optional per page variable
1402 ([0-9]+)? # optional per page default
1403 (?:,(\w+))? # optional page selector
1404 )x;
1405
1406sub _get_paged {
1407 my ($self, $state, $rargs) = @_;
1408
1409 my $paged;
1410 if ($$rargs =~ s/$paged_re//) {
1411 $paged =
1412 {
1413 pp => $1 || "pp",
1414 perpage => $2 || 20,
1415 p => $3 || "p",
1416 };
1417 }
1418
1419 return $paged;
1420}
1421
1422sub _do_paged {
1423 my ($self, $state, $paged, $articles) = @_;
1424
1425 $state->{totalcount} = @$articles;
1426
1427 unless ($paged) {
1428 $state->{page} = 1;
1429 $state->{pagecount} = 1;
1430 $state->{poffset} = 0;
1431 $state->{perpage} = @$articles;
1432 $state->{nextpage} = '';
1433 $state->{prevpage} = '';
1434 $state->{firstnumber} = 1;
1435 $state->{lastnumber} = @$articles;
1436 return $articles;
1437 }
1438
1439 my ($page) = $self->cgi->param($paged->{p});
1440 defined $page or $page = 1;
1441 $page =~ /^[0-9]+$/ or $page = 1;
1442 $page >= 1 or $page = 1;
1443
1444 my ($pp) = $self->cgi->param($paged->{pp});
1445 defined $pp or $pp = $paged->{perpage};
1446 $pp =~ /^[0-9]+$/ or $pp = 20;
1447 $pp = int($pp);
1448 $pp >= 1 or $pp = 20;
1449 $state->{perpage} = $pp;
1450
1451 $state->{pagecount} = int((@$articles + $pp - 1) / $pp);
1452 $state->{pagecount} == 0 and $state->{pagecount} = 1;
1453 $page <= $state->{pagecount} or $page = $state->{pagecount};
1454
1455 $state->{page} = $page;
1456 $state->{nextpage} = $page < $state->{pagecount} ? $page + 1 : '';
1457 $state->{prevpage} = $page > 1 ? $page - 1 : '';
1458 $state->{poffset} = ($page - 1) * $pp;
1459 $state->{firstnumber} = 1 + $state->{poffset};
1460 my $end = $state->{poffset} + $pp - 1;
1461 $state->{lastnumber} = 1 + $end;
1462 $end < @$articles or $end = $#$articles;
1463
1464 return [ @$articles[$state->{poffset} .. $end] ];
1465}
1466
1467=head2 Common dynamic iterator tags
b864cc90
TC
1468
1469=over
1470
1471=item *
1472
76c6b28e
TC
1473I<single> I<field> - access to the fields of the current item in the
1474iteration.
b864cc90
TC
1475
1476=item *
1477
76c6b28e 1478I<single>C<_index> - the current index (zero-based) of the iteration.
b864cc90
TC
1479
1480=item *
1481
76c6b28e
TC
1482I<single>C<_number> - the current number (one-based) of the iteration.
1483
1484=item *
1485
1486I<single>C<_count> I<...> - the number of items matched
1487
1488=item *
1489
1490C<if>I<Plural> I<...> - test if there are any items matched.
1491
1492=item *
1493
1494C<ifLast>I<Single> - test if this is the last item in the iteration.
1495
1496=item *
1497
1498C<ifFirst>I<Single> - test if this is the first item in the iteration.
1499
1500=item *
1501
1502C<next_>I<single> I<field> - retrieve values from the next item in the
1503iteration.
1504
1505=item *
1506
1507C<previous_>I<single> I<field> - retrieve values from the previous
1508item in the iteration.
1509
1510=item *
1511
1512C<ifNext>I<Single> - test if there is a next item in the iteration.
1513
1514=item *
1515
1516C<ifPrevious>I<Single> - test if there is a previous item in the
1517iteration.
b864cc90
TC
1518
1519=back
1520
76c6b28e
TC
1521For article iterators only:
1522
1523=over
ef4a712d 1524
76c6b28e 1525=item *
ef4a712d 1526
76c6b28e
TC
1527C<move_>I<single> - in admin mode, a UI element to allow the article
1528to be moved up/down one position.
ef4a712d 1529
b864cc90
TC
1530=back
1531
1532=cut
76c6b28e
TC
1533
1534sub _dyn_iterate_populate {
1535 my ($self, $state, $args, $acts, $name, $templater) = @_;
1536
1537 my $method = "iter_$state->{plural}";
1538 my $paged = $self->_get_paged($state, \$args);
1539 local $self->{filter} = $self->_get_filter($state, \$args, $acts, $templater);
1540 my $items = $self->_do_filter
81aa5f57 1541 ($state, $self->{filter}, $args, $acts, $templater, $self->$method
76c6b28e
TC
1542 ($state->{context}, $args, $acts, $templater, $state));
1543
1544 return $self->_do_paged($state, $paged, $items);
1545}
1546
1547sub _dyn_iterate_reset {
1548 my ($self, $state, $args, $acts, $name, $templater) = @_;
1549
1550 my $rindex = $state->{rindex};
1551 my $rdata = $state->{rdata};
1552 $$rdata = $self->_dyn_iterate_populate($state, $args, $acts, $name, $templater);
1553 $$rindex = -1;
1554
1555 $state->{previous} = undef;
1556 $state->{item} = undef;
1557 if (@$$rdata) {
1558 $state->{next} = $$rdata->[0];
1559 }
1560 else {
1561 $state->{next} = undef;
1562 }
1563
1564 1;
1565}
1566
1567sub _dyn_iterate {
1568 my ($self, $state) = @_;
1569
1570 my $rindex = $state->{rindex};
1571 my $rdata = $state->{rdata};
1572 my $single = $state->{single};
1573 if (++$$rindex < @$$rdata) {
1574 $state->{previous} = $state->{item};
1575 $state->{item} = $state->{next};
1576 if ($$rindex < $#$$rdata) {
1577 $state->{next} = $$rdata->[$$rindex+1];
1578 }
1579 else {
1580 $state->{next} = undef;
1581 }
1582 $self->{req}->set_article("previous_$single" => $state->{previous});
1583 $self->{req}->set_article($single => $state->{item});
1584 $self->{req}->set_article("next_$single" => $state->{next});
1585 $self->{current}{$state->{plural}} = $state->{item};
1586 return 1;
1587 }
1588 else {
1589 $self->{req}->set_article($single => undef);
1590 $self->{current}{$state->{plural}} = undef;
1591 return;
1592 }
1593}
1594
1595sub _dyn_item_low {
1596 my ($self, $item, $args) = @_;
1597
1598 $item or return '';
1599 my $value = $item->{$args};
1600 defined $value
1601 or return '';
1602
1603 return escape_html($value);
1604}
1605
1606sub _dyn_item {
1607 my ($self, $state, $args) = @_;
1608
1609 my $rindex = $state->{rindex};
1610 my $rdata = $state->{rdata};
1611 my $item = $state->{item};
1612 unless ($state->{item}) {
1613 return "** $state->{single} only usable inside iterator $state->{plural} **";
1614 }
1615
1616 return $self->_dyn_item_low($item, $args);
1617}
1618
1619sub _dyn_next {
1620 my ($self, $state, $args) = @_;
1621
1622 return $self->_dyn_item_low($state->{next}, $args);
1623}
1624
1625sub _dyn_previous {
1626 my ($self, $state, $args) = @_;
1627
1628 return $self->_dyn_item_low($state->{previous}, $args);
1629}
1630
1631sub _dyn_item_object_low {
1632 my ($self, $item, $args, $state) = @_;
1633
1634 $item
1635 or return '';
1636 $item->can($args)
1637 or return "* $args not valid for $state->{single} *";
1638 my $value = $item->$args;
1639 defined $value
1640 or return '';
1641
1642 return escape_html($value);
1643}
1644
1645sub _dyn_item_object {
1646 my ($self, $state, $args) = @_;
1647
1648 unless ($state->{item}) {
1649 return "** $state->{single} only usable inside iterator $state->{plural} **";
1650 }
1651
1652 return $self->_dyn_item_object_low($state->{item}, $args, $state);
1653}
1654
1655sub _dyn_next_obj {
1656 my ($self, $state, $args) = @_;
1657
1658 return $self->_dyn_item_object_low($state->{next}, $args, $state);
1659}
1660
1661sub _dyn_previous_obj {
1662 my ($self, $state, $args) = @_;
1663
1664 return $self->_dyn_item_object_low($state->{previous}, $args, $state);
1665}
1666
1667sub _dyn_ifNext {
1668 my ($self, $state) = @_;
1669
1670 return defined $state->{next};
1671}
1672
1673sub _dyn_ifPrevious {
1674 my ($self, $state) = @_;
1675
1676 return defined $state->{previous};
1677}
1678
1679sub _dyn_article {
1680 my ($self, $state, $args) = @_;
1681
1682 my $rindex = $state->{rindex};
1683 my $rdata = $state->{rdata};
1684 unless ($state->{item}) {
1685 return "** $state->{single} only usable inside iterator $state->{plural} **";
1686 }
1687
1688 my $item = $state->{item}
1689 or return '';
1690
1691 return tag_article($item, $self->{req}->cfg, $args);
1692}
1693
1694sub _dyn_next_article {
1695 my ($self, $state, $args) = @_;
1696
1697 $state->{next} or return '';
1698
1699 return tag_article($state->{next}, $self->{req}->cfg, $args);
1700}
1701
1702sub _dyn_previous_article {
1703 my ($self, $state, $args) = @_;
1704
1705 $state->{previous} or return '';
1706
1707 return tag_article($state->{previous}, $self->{req}->cfg, $args);
1708}
1709
1710sub _dyn_index {
1711 my ($self, $state) = @_;
1712
1713 my $rindex = $state->{rindex};
1714 if ($$rindex < 0 || $$rindex >= @${$state->{rdata}}) {
1715 return "** $state->{single} only valid inside iterator **";
1716 }
1717
1718 return $state->{poffset} + $$rindex;
1719}
1720
1721sub _dyn_number {
1722 my ($self, $state) = @_;
1723
1724 my $rindex = $state->{rindex};
1725 if ($$rindex < 0 || $$rindex >= @${$state->{rdata}}) {
1726 return "** $state->{single} only valid inside iterator **";
1727 }
1728
1729 return $state->{poffset} + 1 + $$rindex;
1730}
1731
1732sub _dyn_count {
1733 my ($self, $state, $args, $acts, $name, $templater) = @_;
1734
1735 my $data = $self->_dyn_iterate_populate($state, $args, $acts, $name, $templater);
1736
1737 return scalar @$data;
1738}
1739
1740sub _dyn_if_first {
1741 my ($self, $rindex, $rdata) = @_;
1742
1743 $$rindex == 0;
1744}
1745
1746sub _dyn_if_last {
1747 my ($self, $rindex, $rdata) = @_;
1748
1749 $$rindex == $#$$rdata;
1750}
1751
1752sub dyn_iterator {
1753 my ($self, $plural, $single, $context, $rindex, $rdata) = @_;
1754
1755 my $method = $plural;
1756 my $index;
1757 defined $rindex or $rindex = \$index;
1758 my $data;
1759 defined $rdata or $rdata = \$data;
1760 my %state =
1761 (
1762 plural => $plural,
1763 single => $single,
1764 rindex => $rindex,
1765 rdata => $rdata,
1766 context => $context,
1767 poffset => 0,
1768 );
1769 return
1770 (
1771 "iterate_${plural}_reset" =>
1772 [ _dyn_iterate_reset => $self, \%state ],
1773 "iterate_$plural" =>
1774 [ _dyn_iterate => $self, \%state ],
1775 $single =>
1776 [ _dyn_item => $self, \%state ],
1777 "${single}_index" =>
1778 [ _dyn_index => $self, \%state ],
1779 "${single}_number" =>
1780 [ _dyn_number => $self, \%state ],
1781 "${single}_count" =>
1782 [ _dyn_count => $self, \%state ],
1783 "if\u$plural" =>
1784 [ _dyn_count => $self, \%state ],
1785 "ifLast\u$single" => [ _dyn_if_last => $self, $rindex, $rdata ],
1786 "ifFirst\u$single" => [ _dyn_if_first => $self, $rindex, $rdata ],
1787 "next_$single" => [ _dyn_next => $self, \%state ],
1788 "previous_$single" => [ _dyn_previous => $self, \%state ],
1789 "ifNext\u$single" => [ _dyn_ifNext => $self, \%state ],
1790 "ifPrevious\u$single" => [ _dyn_ifPrevious => $self, \%state ],
1791 );
1792}
1793
1794sub dyn_iterator_obj {
1795 my ($self, $plural, $single, $context, $rindex, $rdata) = @_;
1796
1797 my $method = $plural;
1798 my $index;
1799 defined $rindex or $rindex = \$index;
1800 my $data;
1801 defined $rdata or $rdata = \$data;
1802 my %state =
1803 (
1804 plural => $plural,
1805 single => $single,
1806 rindex => $rindex,
1807 rdata => $rdata,
1808 context => $context,
1809 poffset => 0,
1810 );
1811 return
1812 (
1813 "iterate_${plural}_reset" =>
1814 [ _dyn_iterate_reset => $self, \%state ],
1815 "iterate_$plural" =>
1816 [ _dyn_iterate => $self, \%state ],
1817 $single =>
1818 [ _dyn_item_object => $self, \%state ],
1819 "${single}_index" =>
1820 [ _dyn_index => $self, \%state ],
1821 "${single}_number" =>
1822 [ _dyn_number => $self, \%state ],
1823 "${single}_count" =>
1824 [ _dyn_count => $self, \%state ],
1825 "if\u$plural" =>
1826 [ _dyn_count => $self, \%state ],
1827 "ifLast\u$single" => [ _dyn_if_last => $self, $rindex, $rdata ],
1828 "ifFirst\u$single" => [ _dyn_if_first => $self, $rindex, $rdata ],
1829 "next_$single" => [ _dyn_next_obj => $self, \%state ],
1830 "previous_$single" => [ _dyn_previous_obj => $self, \%state ],
1831 "ifNext\u$single" => [ _dyn_ifNext => $self, \%state ],
1832 "ifPrevious\u$single" => [ _dyn_ifPrevious => $self, \%state ],
1833 );
1834}
1835
1836sub _dyn_article_move {
1837 my ($self, $state, $args, $acts, $func, $templater) = @_;
1838
1839 $state->{parentid}
1840 or return '';
1841
1842 return $self->tag_dynmove($state->{rindex}, $state->{rdata},
1843 "stepparent=$state->{parentid}",
1844 $args, $acts, $templater);
1845}
1846
1847sub dyn_article_iterator {
1848 my ($self, $plural, $single, $context, $rindex, $rdata) = @_;
1849
1850 my $method = $plural;
1851 my $index;
1852 defined $rindex or $rindex = \$index;
1853 my $data;
1854 defined $rdata or $rdata = \$data;
1855 my %state =
1856 (
1857 plural => $plural,
1858 single => $single,
1859 rindex => $rindex,
1860 rdata => $rdata,
1861 context => $context,
1862 poffset => 0,
1863 );
1864 $self->{context}{$plural} = $context;
1865
1866 require BSE::Util::Iterate;
1867 my $it = BSE::Util::Iterate->new;
1868 return
1869 (
1870 "iterate_${plural}_reset" =>
1871 [ _dyn_iterate_reset => $self, \%state ],
1872 "iterate_$plural" =>
1873 [ _dyn_iterate => $self, \%state],
1874 $single =>
1875 [ _dyn_article => $self, \%state ],
1876 "${single}_index" =>
1877 [ _dyn_index => $self, \%state ],
1878 "${single}_number" =>
1879 [ _dyn_number => $self, \%state ],
1880 "${single}_count" =>
1881 [ _dyn_count => $self, \%state ],
1882 "if\u$plural" =>
1883 [ _dyn_count => $self, \%state ],
1884 "ifLast\u$single" => [ _dyn_if_last => $self, $rindex, $rdata ],
1885 "ifFirst\u$single" => [ _dyn_if_first => $self, $rindex, $rdata ],
1886 "next_$single" => [ _dyn_next_article => $self, \%state ],
1887 "previous_$single" => [ _dyn_previous_article => $self, \%state ],
1888 "ifNext\u$single" => [ _dyn_ifNext => $self, \%state ],
1889 "ifPrevious\u$single" => [ _dyn_ifPrevious => $self, \%state ],
1890 "move_$single" => [ _dyn_article_move => $self, \%state ],
1891 "${plural}_page" => [ _dyn_state => $self, \%state, "page" ],
1892 "${plural}_perpage" => [ _dyn_state => $self, \%state, "perpage" ],
1893 "${plural}_nextpage" => [ _dyn_state => $self, \%state, "nextpage" ],
1894 "${plural}_prevpage" => [ _dyn_state => $self, \%state, "prevpage" ],
1895 "${plural}_pagecount" => [ _dyn_state => $self, \%state, "pagecount" ],
1896 "${single}_totalcount" => [ _dyn_state => $self, \%state, "totalcount" ],
1897 "${plural}_firstnumber" => [ _dyn_state => $self, \%state, "firstnumber" ],
1898 "${plural}_lastnumber" => [ _dyn_state => $self, \%state, "lastnumber" ],
1899 $it->make
1900 (
1901 single => "${single}_pagec",
1902 plural => "${plural}_pagec",
1903 code => [ _dyn_iter_pages => $self, \%state ],
1904 ),
1905 );
1906}
1907
1908sub _dyn_state {
1909 my ($self, $state, $name) = @_;
1910
1911 return $state->{$name};
1912}
1913
1914sub _dyn_iter_pages {
1915 my ($self, $state) = @_;
1916
1917 my @pages;
1918 for my $page (1 .. $state->{pagecount}) {
1919 push @pages,
1920 {
1921 page => $page,
1922 first => $page == 1,
1923 last => $page == $state->{pagecount},
1924 current => $page == $state->{page},
1925 next => $page == $state->{pagecount} ? '' : $page+1,
1926 prev => $page == 1 ? '' : $page-1,
1927 };
1928 }
1929
1930 return @pages;
1931}
1932
1933sub get_cached {
1934 my ($self, $id) = @_;
1935
1936 return $self->{_cache}{$id};
1937}
1938
1939sub set_cached {
1940 my ($self, $id, $value) = @_;
1941
1942 $self->{_cache}{$id} = $value;
1943}
1944
1945sub _cart {
1946 my ($self) = @_;
1947
1948 my $dyncart = $self->get_cached('cart');
1949 $dyncart and return $dyncart;
1950
1951 my $cart = $self->{req}->session->{cart}
1952 or return { cart => [], total_cost => 0, total_units => 0 };
1953
1954 my @cart;
1955 my $total_cost = 0;
1956 my $total_units = 0;
1957 for my $item (@$cart) {
1958 require Products;
1959 my $product = Products->getByPkey($item->{productId});
1960 my $extended = $product->price(user => scalar $self->{req}->siteuser)
1961 * $item->{units};
1962 my $link = $product->link;
1963 $link =~ /^\w+:/
1964 or $link = $self->{req}->cfg->entryErr('site', 'url') . $link;
1965 push @cart,
1966 {
1967 ( map { $_ => $product->{$_} } $product->columns ),
1968 %$item,
1969 extended => $extended,
1970 link => $link,
1971 };
1972 $total_cost += $extended;
1973 $total_units += $item->{units};
1974 }
1975 my $result =
1976 {
1977 cart => \@cart,
1978 total_cost => $total_cost,
1979 total_units => $total_units,
1980 };
1981 $self->set_cached(cart => $result);
1982
1983 return $result;
1984}
1985
19861;
1987