1 # Vend::Interpolate - Interpret Interchange tags
3 # $Id: Interpolate.pm,v 2.313 2009-05-01 13:50:00 pajamian Exp $
5 # Copyright (C) 2002-2008 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
8 # This program was originally based on Vend 0.2 and 0.3
9 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public
22 # License along with this program; if not, write to the Free
23 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
26 package Vend::Interpolate;
31 $VERSION = substr(q$Revision: 2.313 $, 10);
49 Vend::Interpolate -- Interchange tag interpolation routines
57 The Vend::Interpolate contains the majority of the Interchange Tag
58 Language implementation rouines. Historically, it contained the entire
59 tag language implementation for MiniVend, accounting for its name.
61 It contains most of the handler routines pointed to by Vend::Parse, which
62 accepts the parsing output of Vend::Parser. (Vend::Parser was originally based
65 There are two interpolative parsers in Vend::Interpolate,
66 iterate_array_list() and iterate_hash_list() -- these routines parse
67 the lists used in the widely employed [loop ..], [search-region ...],
68 [item-list], and [query ..] ITL tag constructs.
70 This module makes heavy use of precompiled regexes. You will notice variables
71 being used in the regular expression constructs. For example, C<$All> is a
72 a synonym for C<(?s:.)*>, C<$Some> is equivalent to C<(?s:.)*?>, etc.
73 This is not only for clarity of the regular expression, but for speed.
78 push @EXPORT, 'tag_sql_list';
87 $hole = new Safe::Hole;
91 # We generally know when we are testing these things, but be careful
92 no warnings qw(uninitialized numeric);
108 use POSIX qw(ceil strftime LC_CTYPE);
110 use vars qw(%Data_cache);
147 @Share_routines = qw/
161 use vars @Share_vars, @Share_routines,
162 qw/$ready_safe $safe_safe/;
163 use vars qw/%Filter %Ship_handler $Safe_data/;
165 $ready_safe = new Vend::Safe;
166 $ready_safe->trap(qw/:base_io/);
167 $ready_safe->untrap(qw/sort ftfile/);
170 #::logDebug("reset_state=$Vend::Calc_reset -- resetting calc from " . caller);
171 if(! $Global::Foreground and $Vend::Cfg->{ActionMap}{_mvsafe}) {
172 #::logDebug("already made");
173 $ready_safe = $Vend::Cfg->{ActionMap}{_mvsafe};
176 my $pkg = 'MVSAFE' . int(rand(100000));
178 $ready_safe = new Vend::Safe $pkg;
179 $ready_safe->share_from('MVSAFE', ['$safe']);
180 #::logDebug("new safe made=$ready_safe->{Root}");
182 $ready_safe->trap(@{$Global::SafeTrap});
183 $ready_safe->untrap(@{$Global::SafeUntrap});
185 $Document = new Vend::Document;
186 *Log = \&Vend::Util::logError;
187 *Debug = \&Vend::Util::logDebug;
188 *uneval = \&Vend::Util::uneval_it;
189 *HTML = \&Vend::Document::HTML;
190 $ready_safe->share(@Share_vars, @Share_routines);
191 $DbSearch = new Vend::DbSearch;
192 $TextSearch = new Vend::TextSearch;
193 $Tag = new Vend::Tags;
194 $Sub = new Vend::Subs;
203 $Vend::Calc_reset = 1;
204 undef $Vend::Calc_initialized;
209 #::logDebug("reset_state=$Vend::Calc_reset init_state=$Vend::Calc_initialized -- initting calc from " . caller);
210 reset_calc() unless $Vend::Calc_reset;
211 $CGI_array = \%CGI::values_array;
212 $CGI = \%CGI::values;
214 $Discounts = $::Discounts;
215 $Items = $Vend::Items;
216 $Config = $Vend::Cfg;
217 $Scratch = $::Scratch;
219 $Session = $Vend::Session;
220 $Search = $::Instance->{SearchObject} ||= {};
221 $Variable = $::Variable;
222 $Vend::Calc_initialized = 1;
226 # Define conditional ops
228 eq => sub { $_[0] eq $_[1] },
229 ne => sub { $_[0] ne $_[1] },
230 gt => sub { $_[0] gt $_[1] },
231 ge => sub { $_[0] ge $_[1] },
232 le => sub { $_[0] le $_[1] },
233 lt => sub { $_[0] lt $_[1] },
234 '>' => sub { $_[0] > $_[1] },
235 '<' => sub { $_[0] < $_[1] },
236 '>=' => sub { $_[0] >= $_[1] },
237 '<=' => sub { $_[0] <= $_[1] },
238 '==' => sub { $_[0] == $_[1] },
239 '!=' => sub { $_[0] != $_[1] },
242 $_[1] =~ s:^/(.*)/([imsx]*)\s*$:$1:;
243 $2 and substr($_[1], 0, 0) = "(?$2)";
244 eval { $re = qr/$_[1]/ };
246 logError("bad regex %s in if-PREFIX-data", $_[1]);
253 $_[1] =~ s:^/(.*)/([imsx]*)\s*$:$1:;
254 $2 and substr($_[1], 0, 0) = "(?$2)";
255 eval { $re = qr/$_[1]/ };
257 logError("bad regex %s in if-PREFIX-data", $_[1]);
263 my ($string, $filter) = @_;
264 my $newval = filter_value($filter, $string);
265 return $string eq $newval ? 1 : 0;
268 my ($string, $lenspec) = @_;
269 my ($min,$max) = split /-/, $lenspec;
270 if($min and length($string) < $min) {
273 elsif($max and length($string) > $max) {
277 return 0 unless length($string) > 0;
284 A => sub { -A $_[0] },
285 B => sub { -B $_[0] },
286 d => sub { -d $_[0] },
287 e => sub { -e $_[0] },
288 f => sub { -f $_[0] },
289 g => sub { -g $_[0] },
290 l => sub { -l $_[0] },
291 M => sub { -M $_[0] },
292 r => sub { -r $_[0] },
293 s => sub { -s $_[0] },
294 T => sub { -T $_[0] },
295 u => sub { -u $_[0] },
296 w => sub { -w $_[0] },
297 x => sub { -x $_[0] },
301 $cond_op{len} = $cond_op{length};
303 # Regular expression pre-compilation
308 my $Some = '(?s:.)*?';
309 my $Codere = '[-\w#/.]+';
310 my $Coderex = '[-\w:#=/.%]+';
311 my $Filef = '(?:%20|\s)+([^]]+)';
312 my $Mandx = '\s+([-\w:#=/.%]+)';
313 my $Mandf = '(?:%20|\s)+([-\w#/.]+)';
314 my $Spacef = '(?:%20|\s)+';
315 my $Spaceo = '(?:%20|\s)*';
317 my $Optx = '\s*([-\w:#=/.%]+)?';
318 my $Optr = '(?:\s+([^]]+))?';
319 my $Mand = '\s+([-\w#/.]+)';
320 my $Opt = '\s*([-\w#/.]+)?';
324 my $XAll = qr{(?s:.)*};
325 my $XSome = qr{(?s:.)*?};
326 my $XCodere = qr{[-\w#/.]+};
327 my $XCoderex = qr{[-\w:#=/.%]+};
328 my $XMandx = qr{\s+([-\w:#=/.%]+)};
329 my $XMandf = qr{(?:%20|\s)+([-\w#/.]+)};
330 my $XSpacef = qr{(?:%20|\s)+};
331 my $XSpaceo = qr{(?:%20|\s)*};
332 my $XOptx = qr{\s*([-\w:#=/.%]+)?};
333 my $XMand = qr{\s+([-\w#/.]+)};
334 my $XOpt = qr{\s*([-\w#/.]+)?};
336 my $Gvar = qr{\@\@([A-Za-z0-9]\w+[A-Za-z0-9])\@\@};
337 my $Evar = qr{\@_([A-Za-z0-9]\w+[A-Za-z0-9])_\@};
338 my $Cvar = qr{__([A-Za-z0-9]\w*?[A-Za-z0-9])__};
420 s/([A-Za-z0-9])/[\u$1\l$1]/g;
423 next if $tag =~ m{^_};
424 $T{$tag} = "\\[$T{$tag}";
425 next unless $tag =~ m{^/};
426 $T{$tag} = "$T{$tag}\]";
430 '/_alternate' => qr($T{_alternate}\]),
431 '/_calc' => qr($T{_calc}\]),
432 '/_change' => qr([-_]change\s+)i,
433 '/_data' => qr($T{_data}\]),
434 '/_exec' => qr($T{_exec}\]),
435 '/_field' => qr($T{_field}\]),
436 '/_filter' => qr($T{_filter}\]),
437 '/_last' => qr($T{_last}\]),
438 '/_modifier' => qr($T{_modifier}\]),
439 '/_next' => qr($T{_next}\]),
440 '/_pos' => qr($T{_pos}\]),
441 '/_sub' => qr($T{_sub}\]),
442 '_accessories' => qr($T{_accessories}($Spacef[^\]]+)?\]),
443 '_alternate' => qr($T{_alternate}$Opt\]($Some)),
444 '_calc' => qr($T{_calc}\]($Some)),
445 '_exec' => qr($T{_exec}$Mand\]($Some)),
446 '_filter' => qr($T{_filter}\s+($Some)\]($Some)),
447 '_sub' => qr($T{_sub}$Mand\]($Some)),
448 '_change' => qr($T{_change}$Mand$Opt\] \s*
453 '_code' => qr($T{_code}\]),
454 '_sku' => qr($T{_sku}\]),
455 'col' => qr(\[col(?:umn)?\s+
459 \[/col(?:umn)?\] )ix,
461 'comment' => qr($T{comment}(?:\s+$Some)?\]
462 (?!$All$T{comment}\])
466 '_description' => qr($T{_description}\]),
467 '_difference' => qr($T{_difference}(?:\s+(?:quantity=)?"?(\d+)"?)?$Optx\]),
468 '_discount' => qr($T{_discount}(?:\s+(?:quantity=)?"?(\d+)"?)?$Optx\]),
469 '_field_if' => qr($T{_field}(\d*)$Spacef(!?)\s*($Codere)$Optr\]($Some)),
470 '_field_if_wo' => qr($T{_field}$Spacef(!?)\s*($Codere$Optr)\]),
471 '_field' => qr($T{_field}$Mandf\]),
472 '_common' => qr($T{_common}$Mandf\]),
473 '_include' => qr($T{_include}$Filef\]),
474 '_increment' => qr($T{_increment}\]),
475 '_last' => qr($T{_last}\]\s*($Some)\s*),
476 '_line' => qr($T{_line}$Opt\]),
477 '_next' => qr($T{_next}\]\s*($Some)\s*),
478 '_options' => qr($T{_options}($Spacef[^\]]+)?\]),
479 '_header_param' => qr($T{_header_param}$Mandf$Optr\]),
480 '_header_param_if' => qr($T{_header_param}(\d*)$Spacef(!?)\s*($Codere)$Optr\]($Some)),
481 '_param_if' => qr((?:$T{_param}|$T{_modifier})(\d*)$Spacef(!?)\s*($Codere)$Optr\]($Some)),
482 '_param' => qr((?:$T{_param}|$T{_modifier})$Mandf\]),
483 '_parent_if' => qr($T{_parent}(\d*)$Spacef(!?)\s*($Codere)$Optr\]($Some)),
484 '_parent' => qr($T{_parent}$Mandf\]),
485 '_pos_if' => qr($T{_pos}(\d*)$Spacef(!?)\s*(-?\d+)$Optr\]($Some)),
486 '_pos' => qr($T{_pos}$Spacef(-?\d+)\]),
487 '_price' => qr!$T{_price}(?:\s+(\d+))?$Optx\]!,
488 '_quantity' => qr($T{_quantity}\]),
489 '_subtotal' => qr($T{_subtotal}$Optx\]),
490 '_tag' => qr([-_] tag [-_] ([-\w]+) \s+)x,
491 'condition' => qr($T{condition}$T($Some)$T{'/condition'}),
492 'condition_begin' => qr(^\s*$T{condition}\]($Some)$T{'/condition'}),
493 '_discount_price' => qr($T{_discount_price}(?:\s+(\d+))?$Optx\]),
494 'discount_price' => qr($T{discount_price}(?:\s+(\d+))?$Optx\]),
495 '_discount_subtotal' => qr($T{_discount_subtotal}$Optx\]),
496 'has_else' => qr($T{'/else'}\s*$),
497 'else_end' => qr($T{else}\]($All)$T{'/else'}\s*$),
498 'elsif_end' => qr($T{elsif}\s+($All)$T{'/elsif'}\s*$),
499 'matches' => qr($T{matches}\]),
500 'match_count' => qr($T{match_count}\]),
501 'more' => qr($T{more}\]),
502 'more_list' => qr($T{more_list}$Optx$Optx$Optx$Optx$Optx\]($Some)$T{'/more_list'}),
503 'no_match' => qr($T{no_match}\]($Some)$T{'/no_match'}),
504 'on_match' => qr($T{on_match}\]($Some)$T{'/on_match'}),
505 '_quantity_name' => qr($T{_quantity_name}\]),
506 '_modifier_name' => qr($T{_modifier_name}$Spacef(\w+)\]),
507 'then' => qr(^\s*$T{then}$T($Some)$T{'/then'}),
513 if ! defined $QR{$_};
521 my ($joiner, $default) = @_;
522 return $default unless defined $joiner and length $joiner;
523 if($joiner eq '\n') {
526 elsif($joiner =~ m{\\}) {
527 $joiner = $safe_safe->reval("qq{$joiner}");
529 return length($joiner) ? $joiner : $default;
532 sub substitute_image {
535 ## Allow no substitution of downloads
536 return if $::Pragma->{download};
538 ## If post_page routine processor returns true, return. Otherwise,
539 ## continue image rewrite
540 if($::Pragma->{post_page}) {
541 Vend::Dispatch::run_macro($::Pragma->{post_page}, $text)
545 unless ( $::Pragma->{no_image_rewrite} ) {
546 my $dir = $CGI::secure ?
547 ($Vend::Cfg->{ImageDirSecure} || $Vend::Cfg->{ImageDir}) :
548 $Vend::Cfg->{ImageDir};
551 $$text =~ s#(<i\w+\s+[^>]*?src=")(?!\w+:)([^/'][^"]+)#
553 $$text =~ s#(<body\s+[^>]*?background=")(?!\w+:)([^/'][^"]+)#
555 $$text =~ s#(<t(?:[dhr]|able)\s+[^>]*?background=")(?!\w+:)([^/'][^"]+)#
560 if($Vend::Cfg->{ImageAlias}) {
561 for (keys %{$Vend::Cfg->{ImageAlias}} ) {
562 $$text =~ s#(<i\w+\s+[^>]*?src=")($_)#
563 $1 . ($Vend::Cfg->{ImageAlias}->{$2} || $2)#ige;
564 $$text =~ s#(<body\s+[^>]*?background=")($_)#
565 $1 . ($Vend::Cfg->{ImageAlias}->{$2} || $2)#ige;
566 $$text =~ s#(<t(?:[dhr]|able)\s+[^>]*?background=")($_)#
567 $1 . ($Vend::Cfg->{ImageAlias}->{$2} || $2)#ige;
575 return readfile($Vend::Cfg->{DirConfig}{Variable}{$varname})
576 if $Vend::Cfg->{DirConfig}
577 and defined $Vend::Cfg->{DirConfig}{Variable}{$varname};
580 last VARDB if $::Pragma->{dynamic_variables_file_only};
581 last VARDB unless $Vend::Cfg->{VariableDatabase};
582 if($Vend::VarDatabase) {
583 last VARDB unless $Vend::VarDatabase->record_exists($varname);
584 return $Vend::VarDatabase->field($varname, 'Variable');
587 $Vend::VarDatabase = database_exists_ref($Vend::Cfg->{VariableDatabase})
588 or undef $Vend::Cfg->{VariableDatabase};
592 return $::Variable->{$varname};
595 sub vars_and_comments {
597 ## We never want to interpolate vars if in restricted mode
598 return if $Vend::restricted;
601 # Set whole-page pragmas from [pragma] tags
602 1 while $$html =~ s/\[pragma\s+(\w+)(?:\s+(\w+))?\]/
603 $::Pragma->{$1} = (length($2) ? $2 : 1), ''/ige;
605 undef $Vend::PageInit unless $::Pragma->{init_page};
607 if(defined $Vend::PageInit and ! $Vend::PageInit++) {
608 Vend::Dispatch::run_macro($::Pragma->{init_page}, $html);
611 # Substitute in Variable values
612 $$html =~ s/$Gvar/$Global::Variable->{$1}/g;
613 if($::Pragma->{dynamic_variables}) {
614 $$html =~ s/$Evar/dynamic_var($1) || $Global::Variable->{$1}/ge
616 $$html =~ s/$Evar/dynamic_var($1) || $Global::Variable->{$1}/ge;
617 $$html =~ s/$Cvar/dynamic_var($1)/ge;
620 $$html =~ s/$Evar/$::Variable->{$1} || $Global::Variable->{$1}/ge
622 $$html =~ s/$Evar/$::Variable->{$1} || $Global::Variable->{$1}/ge;
623 $$html =~ s/$Cvar/$::Variable->{$1}/g;
626 if($::Pragma->{pre_page}) {
627 Vend::Dispatch::run_macro($::Pragma->{pre_page}, $html);
630 # Strip out [comment] [/comment] blocks
631 1 while $$html =~ s%$QR{comment}%%go;
633 # Translate Interchange tags embedded in HTML comments like <!--[tag ...]-->
634 ! $::Pragma->{no_html_comment_embed}
636 $$html =~ s/<!--+\[/[/g
637 and $$html =~ s/\]--+>/]/g;
642 sub interpolate_html {
643 my ($html, $wantref, $opt) = @_;
645 return undef if ! defined($html);
646 return undef if $Vend::NoInterpolate;
653 if(defined $Vend::PageInit and ! $Vend::PageInit) {
654 defined $::Variable->{MV_AUTOLOAD}
655 and $html =~ s/^/$::Variable->{MV_AUTOLOAD}/;
658 #::logDebug("opt=" . uneval($opt));
660 vars_and_comments(\$html)
661 unless $opt and $opt->{onfly};
663 $^W = 1 if $::Pragma->{perl_warnings_in_page};
665 # Returns, could be recursive
666 my $parse = new Vend::Parse $wantref;
667 $parse->parse($html);
668 while($parse->{_buf}) {
669 if($toplevel and $parse->{SEND}) {
670 delete $parse->{SEND};
672 $parse->destination($parse->{_current_output});
676 return $parse->{OUT} if defined $wantref;
677 return ${$parse->{OUT}};
681 my($filter, $value, $tag, @passed_args) = @_;
682 #::logDebug("filter_value: filter='$filter' value='$value' tag='$tag'");
683 my @filters = Text::ParseWords::shellwords($filter);
686 if(! $Vend::Filters_initted++ and my $ref = $Vend::Cfg->{CodeDef}{Filter}) {
687 while (my($k, $v) = each %{$ref->{Routine}}) {
693 next unless length($_);
694 @args = @passed_args;
696 $value = sprintf($_, $value);
699 if (/^(\d+)([\.\$]?)$/) {
701 return $value unless ($len = length($value)) > $1;
702 my ($limit, $mod) = ($1, $2);
704 substr($value, $limit) = '';
707 substr($value, $1) = '...';
710 substr($value, 0, $len - $limit) = '...';
715 while( s/\.([^.]+)$//) {
719 substr($value , $_) = ''
720 if length($value) > $_;
723 if ( /^words(\d+)(\.?)$/ ) {
724 my @str = (split /\s+/, $value);
725 if (scalar @str > $1) {
727 $value = join(' ', @str[0..--$num]);
728 $value .= $2 ? '...' : '';
733 unless ($sub = $Filter{$_} || Vend::Util::codedef_routine('Filter', $_) ) {
734 logError ("Unknown filter '%s'", $_);
737 unshift @args, $value, $tag;
738 $value = $sub->(@args);
740 #::logDebug("filter_value returns: value='$value'");
745 my ($label, $opt, $body) = @_;
746 $label = 'default' unless $label;
747 $Vend::Session->{try}{$label} = '';
750 $save = delete $SIG{__DIE__} if defined $SIG{__DIE__};
753 $out = interpolate_html($body);
756 $SIG{__DIE__} = $save if defined $save;
758 $Vend::Session->{try}{$label} .= "\n"
759 if $Vend::Session->{try}{$label};
760 $Vend::Session->{try}{$label} .= $@;
762 if ($opt->{status}) {
763 return ($Vend::Session->{try}{$label}) ? 0 : 1;
765 elsif ($opt->{hide}) {
768 elsif ($opt->{clean}) {
769 return ($Vend::Session->{try}{$label}) ? '' : $out;
775 # Returns the text of a configurable database field or a
778 my($selector,$field,$key,$opt,$flag) = @_;
781 $Safe_data = 1 if $opt->{safe_data};
785 if ( not $db = database_exists_ref($selector) ) {
786 if($selector eq 'session') {
787 if(defined $opt->{value}) {
788 $opt->{value} = filter_value($opt->{filter}, $opt->{value}, $field)
790 if ($opt->{increment}) {
791 $Vend::Session->{$field} += (+ $opt->{value} || 1);
793 elsif ($opt->{append}) {
794 $Vend::Session->{$field} .= $opt->{value};
797 $Vend::Session->{$field} = $opt->{value};
802 my $value = $Vend::Session->{$field} || '';
803 $value = filter_value($opt->{filter}, $value, $field)
809 logError( "Bad data selector='%s' field='%s' key='%s'",
817 elsif($opt->{increment}) {
818 #::logDebug("increment_field: key=$key field=$field value=$opt->{value}");
819 return increment_field($Vend::Database{$selector},$key,$field,$opt->{value} || 1);
821 elsif (defined $opt->{value}) {
822 #::logDebug("alter table: table=$selector alter=$opt->{alter} field=$field value=$opt->{value}");
824 $opt->{alter} =~ s/\W+//g;
825 $opt->{alter} = lc($opt->{alter});
826 if ($opt->{alter} eq 'change') {
827 return $db->change_column($field, $opt->{value});
829 elsif($opt->{alter} eq 'add') {
830 return $db->add_column($field, $opt->{value});
832 elsif ($opt->{alter} eq 'delete') {
833 return $db->delete_column($field, $opt->{value});
836 logError("alter function '%s' not found", $opt->{alter});
841 $opt->{value} = filter_value($opt->{filter}, $opt->{value}, $field)
843 #::logDebug("set_field: table=$selector key=$key field=$field foreign=$opt->{foreign} value=$opt->{value}");
844 my $orig = $opt->{value};
846 $field =~ s/\.(.*)//;
848 my $current = database_field($selector,$key,$field,$opt->{foreign});
849 $opt->{value} = dotted_hash($current, $hk, $orig);
851 my $result = set_field(
859 return $orig if $opt->{serial};
863 elsif ($opt->{serial}) {
864 $field =~ s/\.(.*)//;
868 database_field($selector,$key,$field,$opt->{foreign}),
873 elsif ($opt->{hash}) {
874 return undef unless $db->record_exists($key);
875 return $db->row_hash($key);
877 elsif ($opt->{filter}) {
880 ed(database_field($selector,$key,$field,$opt->{foreign})),
885 #The most common , don't enter a block, no accoutrements
886 return ed(database_field($selector,$key,$field,$opt->{foreign}));
889 sub input_filter_do {
890 my($varname, $opt, $routine) = @_;
891 #::logDebug("filter var=$varname opt=" . uneval_it($opt));
892 return undef unless defined $CGI::values{$varname};
893 #::logDebug("before filter=$CGI::values{$varname}");
894 $routine = $opt->{routine} || ''
896 if($routine =~ /\S/) {
897 $routine = interpolate_html($routine);
898 $CGI::values{$varname} = tag_calc($routine);
901 $CGI::values{$varname} = filter_value($opt->{op}, $CGI::values{$varname}, $varname);
903 #::logDebug("after filter=$CGI::values{$varname}");
908 my ($varname, $opt, $routine) = @_;
910 return if ! ref $Vend::Session->{Filter};
911 delete $Vend::Session->{Filter}{$_};
914 $opt->{routine} = $routine if $routine =~ /\S/;
915 $Vend::Session->{Filter} = {} if ! $Vend::Session->{Filter};
916 $Vend::Session->{Filter}{$varname} = $opt->{op} if $opt->{op};
921 my($base,$term,$operator,$comp, @addl) = @_;
924 # Only lowercase the first word-characters part of the conditional so that
925 # file-T doesn't turn into file-t (which is something different).
926 $base =~ s/(\w+)/\L$1/;
928 $base =~ s/^!// and $reverse = 1;
931 $noop = 1, $operator = '' unless defined $operator;
936 if($operator =~ /^([^\s.]+)\.(.+)/) {
940 if($comp =~ /^\w[-\w]+=/) {
941 $arg = get_option_hash($comp);
947 $Tag ||= new Vend::Tags;
948 #::logDebug("ready to call tag=$tag with arg=$arg");
949 $comp = $Tag->$tag($arg);
952 if($sub = $cond_op{$operator}) {
956 $newcomp =~ s/^(["'])(.*)\1$/$2/s or
957 $newcomp =~ s/^qq?([{(])(.*)[})]$/$2/s or
958 $newcomp =~ s/^qq?(\S)(.*)\1$/$2/s;
963 #::logDebug("cond: base=$base term=$term op=$operator comp=$comp newcomp=$newcomp nooop=$noop\n");
964 #::logDebug (($reverse ? '!' : '') . "cond: base=$base term=$term op=$operator comp=$comp");
966 #::logDebug ("cond: base=$base term=$term op=$operator comp=$comp\n");
969 if($base eq 'total') {
974 if($base eq 'session') {
975 $op = qq%$Vend::Session->{$term}%;
976 $op = "q{$op}" unless defined $noop;
977 $op .= qq% $operator $comp%
980 elsif($base eq 'scratch') {
981 $op = qq%$::Scratch->{$term}%;
982 $op = "q{$op}" unless defined $noop;
983 $op .= qq% $operator $comp%
986 elsif($base eq 'scratchd') {
987 $op = qq%$::Scratch->{$term}%;
988 $op = "q{$op}" unless defined $noop;
989 $op .= qq% $operator $comp%
991 delete $::Scratch->{$term};
993 elsif($base =~ /^e?value/) {
994 $op = qq%$::Values->{$term}%;
995 $op = "q{$op}" unless defined $noop;
996 $op .= qq% $operator $comp%
999 elsif($base eq 'cgi') {
1000 $op = qq%$CGI::values{$term}%;
1001 $op = "q{$op}" unless defined $noop;
1002 $op .= qq% $operator $comp%
1005 elsif($base eq 'pragma') {
1006 $op = qq%$::Pragma->{$term}%;
1007 $op = "q{$op}" unless defined $noop;
1008 $op .= qq% $operator $comp%
1011 elsif($base eq 'explicit') {
1013 $status = $ready_safe->reval($comp);
1015 elsif($base =~ /^var(?:iable)?$/) {
1016 $op = qq%$::Variable->{$term}%;
1017 $op = "q{$op}" unless defined $noop;
1018 $op .= qq% $operator $comp%
1021 elsif($base eq 'global') {
1022 $op = qq%$Global::Variable->{$term}%;
1023 $op = "q{$op}" unless defined $noop;
1024 $op .= qq% $operator $comp%
1027 elsif($base eq 'items') {
1030 $cart = $::Carts->{$term} || undef;
1033 $cart = $Vend::Items;
1035 $op = defined $cart ? scalar @{$cart} : 0;
1037 $op .= qq% $operator $comp%
1040 elsif($base eq 'data') {
1041 my($d,$f,$k) = split /::/, $term, 3;
1042 $op = database_field($d,$k,$f);
1043 #::logDebug ("tag_if db=$d fld=$f key=$k\n");
1044 $op = "q{$op}" unless defined $noop;
1045 $op .= qq% $operator $comp%
1048 elsif($base eq 'field') {
1049 my($f,$k) = split /::/, $term;
1050 $op = product_field($f,$k);
1051 #::logDebug("tag_if field fld=$f key=$k\n");
1052 $op = "q{$op}" unless defined $noop;
1053 $op .= qq% $operator $comp%
1056 elsif($base eq 'discount') {
1057 # Use switch_discount_space to ensure that the hash is set properly.
1058 switch_discount_space($Vend::DiscountSpaceName)
1059 unless ref $::Discounts eq 'HASH';
1060 $op = qq%$::Discounts->{$term}%;
1061 $op = "q{$op}" unless defined $noop;
1062 $op .= qq% $operator $comp%
1065 elsif($base eq 'ordered') {
1066 $operator = 'main' unless $operator;
1070 $attrib = 'quantity';
1073 ($attrib,$comp) = split /\s+/, $comp;
1075 foreach $i (@{$::Carts->{$operator}}) {
1076 next unless $i->{code} eq $term;
1077 ($op++, next) if $attrib eq 'lines';
1078 $op = $i->{$attrib};
1081 $op = "q{$op}" unless defined $noop;
1082 $op .= qq% $comp% if $comp;
1084 elsif($base =~ /^file(-([A-Za-z]))?$/) {
1085 #$op =~ s/[^rwxezfdTsB]//g;
1086 #$op = substr($op,0,1) || 'f';
1087 my $fop = $2 || 'f';
1088 if(! $file_op{$fop}) {
1089 logError("Unrecognized file test '%s'. Returning false.", $fop);
1093 $op = $file_op{$fop}->($term);
1096 elsif($base =~ /^errors?$/) {
1098 if(! $term or $total) {
1099 $err = is_hash($Vend::Session->{errors})
1100 ? scalar (keys %{$Vend::Session->{errors}})
1104 $err = is_hash($Vend::Session->{errors})
1105 ? $Vend::Session->{errors}{$term}
1109 $op .= qq% $operator $comp%
1112 elsif($base =~ /^warnings?$/) {
1114 if(my $ary = $Vend::Session->{warnings}) {
1115 ref($ary) eq 'ARRAY' and $warn = scalar(@$ary);
1119 elsif($base eq 'validcc') {
1121 $status = Vend::Order::validate_whole_cc($term, $operator, $comp);
1123 elsif($base eq 'config') {
1124 my @terms = split /::|->|\./, $term;
1127 while(my $t = shift(@terms)) {
1132 $op = "q{$op}" unless defined $noop;
1133 $op .= qq% $operator $comp%
1136 elsif($base =~ /^module.version/) {
1139 $op = ${"${term}::VERSION"};
1140 $op = "q{$op}" unless defined $noop;
1141 $op .= qq% $operator $comp%
1145 elsif($base =~ /^accessor/) {
1147 $op = qq%$Vend::Cfg->{Accessories}->{$term}%;
1148 $op = "q{$op}" unless defined $noop;
1149 $op .= qq% $operator $comp%;
1152 for(@{$Vend::Cfg->{UseModifier}}) {
1153 next unless product_field($_,$term);
1159 elsif($base eq 'control') {
1161 if (defined $::Scratch->{control_index}
1162 and defined $::Control->[$Scratch->{control_index}]) {
1163 $op = qq%$::Control->[$::Scratch->{control_index}]{$term}%;
1165 unless defined $noop;
1166 $op .= qq% $operator $comp%
1170 elsif($base eq 'env') {
1172 if (my $h = ::http()) {
1178 $op = qq%$env->{$term}%;
1179 $op = "q{$op}" unless defined $noop;
1180 $op .= qq% $operator $comp%
1185 $op = "q{$op}" unless defined $noop;
1186 $op .= qq% $operator $comp%
1190 #::logDebug("noop='$noop' op='$op'");
1193 last RUNSAFE if defined $status;
1196 $status = $sub->($op, $newcomp);
1200 $status = $op ? 1 : 0;
1204 $ready_safe->trap(@{$Global::SafeTrap});
1205 $ready_safe->untrap(@{$Global::SafeUntrap});
1206 $status = $ready_safe->reval($op) ? 1 : 0;
1208 logError "Bad if '@_': $@";
1213 $status = $reverse ? ! $status : $status;
1216 my $chain = /^\[[Aa]/;
1217 last if ($chain ^ $status);
1218 $status = ${(new Vend::Parse)->parse($_)->{OUT}} ? 1 : 0;
1220 #::logDebug("if status=$status");
1225 sub find_close_square {
1227 my $first = index($chunk, ']');
1228 return undef if $first < 0;
1229 my $int = index($chunk, '[');
1231 while( $int > -1 and $int < $first) {
1233 $first = index($chunk, ']', $first + 1);
1234 $int = index($chunk, '[', $pos);
1236 return substr($chunk, 0, $first);
1242 unless $$text =~ s# \s* \[
1243 ( (?:[Aa][Nn][Dd]|[Oo][Rr]) \s+
1246 my $expr = find_close_square($$text);
1247 return undef unless defined $expr;
1248 $$text = substr( $$text,length($expr) + 1 );
1255 my ($then, $else, $elsif, $andor, @addl);
1256 $else = $elsif = '';
1258 push (@addl, $andor) while $andor = find_andor(\$body);
1260 $body =~ s#$QR{then}##o
1263 $body =~ s#$QR{has_else}##o
1264 and $else = find_matching_else(\$body);
1266 $body =~ s#$QR{elsif_end}##o
1269 $body = $then if defined $then;
1271 return($body, $elsif, $else, @addl);
1275 my ($cond,$body,$negate) = @_;
1276 #::logDebug("Called tag_if: $cond\n$body\n");
1277 my ($base, $term, $op, $operator, $comp);
1278 my ($else, $elsif, $else_present, @addl);
1280 ($base, $term, $operator, $comp) = split /\s+/, $cond, 4;
1281 if ($base eq 'explicit') {
1282 $body =~ s#$QR{condition_begin}##o
1283 and ($comp = $1, $operator = '');
1285 #::logDebug("tag_if: base=$base term=$term op=$operator comp=$comp");
1288 ($base =~ s/^\W+// or $base = "!$base") if $negate;
1290 $else_present = 1 if
1291 $body =~ /\[[EeTtAaOo][hHLlNnRr][SsEeDd\s]/;
1293 ($body, $elsif, $else, @addl) = split_if($body)
1296 #::logDebug("Additional ops found:\n" . join("\n", @addl) ) if @addl;
1298 unless(defined $operator) {
1303 my $status = conditional ($base, $term, $operator, $comp, @addl);
1305 #::logDebug("Result of if: $status\n");
1312 $else = '[else]' . $else . '[/else]' if length $else;
1313 my $pertinent = Vend::Parse::find_matching_end('elsif', \$elsif);
1314 unless(defined $pertinent) {
1315 $pertinent = $elsif;
1318 $elsif .= '[/elsif]' if $elsif =~ /\S/;
1319 $out = '[if ' . $pertinent . $elsif . $else . '[/if]';
1321 elsif (length $else) {
1327 # This generates a *session-based* Autoload routine based
1328 # on the contents of a preset Profile (see the Profile directive).
1330 # Normally used for setting pricing profiles with CommonAdjust,
1331 # ProductFiles, etc.
1333 sub restore_profile {
1335 return unless $save = $Vend::Session->{Profile_save};
1337 $Vend::Cfg->{$_} = $save->{$_};
1343 my($profile, $opt) = @_;
1344 #::logDebug("in tag_profile=$profile opt=" . uneval_it($opt));
1346 $opt = {} if ! $opt;
1347 my $tag = $opt->{tag} || 'default';
1350 if($opt->{restore}) {
1352 if(ref $Vend::Session->{Autoload}) {
1353 @{$Vend::Session->{Autoload}} =
1354 grep $_ !~ /^$tag-/, @{$Vend::Session->{Autoload}};
1357 return if ! ref $Vend::Session->{Autoload};
1358 $opt->{joiner} = ' ' unless defined $opt->{joiner};
1359 return join $opt->{joiner},
1360 grep /^\w+-\w+$/, @{ $Vend::Session->{Autoload} };
1363 if($profile =~ s/(\w+)-//) {
1367 elsif (! $opt->{set} and ! $opt->{run}) {
1368 $opt->{set} = $opt->{run} = 1;
1371 if( "$profile$tag" =~ /\W/ ) {
1373 "profile: invalid characters (tag=%s profile=%s), must be [A-Za-z_]+",
1377 return $opt->{failure};
1381 #::logDebug("running profile=$profile tag=$tag");
1382 my $prof = $Vend::Cfg->{Profile_repository}{$profile};
1384 logError( "profile %s (%s) non-existant.", $profile, $tag );
1385 return $opt->{failure};
1387 #::logDebug("found profile=$profile");
1388 $Vend::Cfg->{Profile} = $prof;
1390 #::logDebug("restored profile");
1392 for my $one (keys %$prof) {
1393 #::logDebug("doing profile $one");
1394 next unless defined $Vend::Cfg->{$one};
1396 my $val = $prof->{$one};
1397 if( ! ref $Vend::Cfg->{$one} ) {
1400 elsif( ref($Vend::Cfg->{$one}) eq 'HASH') {
1401 if( ref($val) ne 'HASH') {
1402 $string = '{' . $prof->{$one} . '}'
1403 unless $prof->{$one} =~ /^{/
1404 and $prof->{$one} =~ /}\s*$/;
1407 elsif( ref($Vend::Cfg->{$one}) eq 'ARRAY') {
1408 if( ref($val) ne 'ARRAY') {
1409 $string = '[' . $prof->{$one} . ']'
1410 unless $prof->{$one} =~ /^\[/
1411 and $prof->{$one} =~ /]\s*$/;
1415 logError( "profile: cannot handle object of type %s.",
1418 logError("profile: profile for $one not changed.");
1422 #::logDebug("profile value=$val, string=$string");
1424 $val = $ready_safe->reval($string) if $string;
1427 logError( "profile: bad object %s: %s", $one, $string );
1430 $Vend::Session->{Profile_save}{$one} = $Vend::Cfg->{$one}
1431 unless defined $Vend::Session->{Profile_save}{$one};
1433 #::logDebug("set $one to value=$val, string=$string");
1434 $Vend::Cfg->{$one} = $val;
1436 return $opt->{success}
1440 #::logDebug("setting profile=$profile tag=$tag");
1442 if(! $Vend::Session->{Autoload}) {
1445 elsif(ref $Vend::Session->{Autoload}) {
1446 $al = $Vend::Session->{Autoload};
1449 $al = [ $Vend::Session->{Autoload} ];
1453 @$al = grep $_ !~ m{^$tag-\w+$}, @$al;
1456 push @$al, "$tag-$profile";
1457 #::logDebug("profile=$profile Autoload=" . uneval_it($al));
1458 $Vend::Session->{Autoload} = $al;
1460 return $opt->{success};
1463 *tag_options = \&Vend::Options::tag_options;
1466 my ($ary, $max) = @_;
1467 $max = $::Limit->{option_list} if ! $max;
1469 for (my $i = 0; $i < scalar(@$ary); $i++) {
1470 $ary->[$i] =~ /^\s* ([a-zA-Z0-9]+) \s* \.\.+ \s* ([a-zA-Z0-9]+) \s* $/x
1475 "Refuse to add %d options to option list via range, max %d.",
1481 push @do, $i, \@new;
1485 while($new = pop(@do)) {
1487 splice @$ary, $idx, 1, @$new;
1492 sub tag_accessories {
1493 my($code,$extra,$opt,$item) = @_;
1497 #::logDebug("tag_accessories: item is a hash");
1501 # Had extra if got here
1502 #::logDebug("tag_accessories: code=$code opt=" . uneval_it($opt) . " item=" . uneval_it($item) . " extra=$extra");
1503 my($attribute, $type, $field, $db, $name, $outboard, $passed);
1504 $opt = {} if ! $opt;
1508 @{$opt}{qw/attribute type column table name outboard passed/} =
1509 split /\s*,\s*/, $extra;
1511 ($attribute, $type, $field, $db, $name, $outboard, $passed) =
1512 @{$opt}{qw/attribute type column table name outboard passed/};
1514 ## Code only passed when we are a product
1517 my $col = $opt->{column} || $opt->{attribute};
1518 my $key = $opt->{outboard} || $code;
1519 last GETACC if ! $col;
1521 $opt->{passed} ||= tag_data($opt->{table}, $col, $key);
1524 $opt->{passed} ||= product_field($col, $key);
1528 return unless $opt->{passed} || $opt->{type};
1529 $opt->{type} ||= 'select';
1533 $opt->{type} =~ /^(text|password|hidden)/i;
1536 return Vend::Form::display($opt, $item);
1542 my ($tables, $opt, $text) = @_;
1544 $opt->{no_return} = 1 unless defined $opt->{no_return};
1546 while ( $text =~ s/(.*?)<%//s || $text =~ s/(.+)//s ) {
1548 ; my \$html = <<'_MV_ASP_EOF$^T';
1554 $text =~ s/(.*?)%>//s
1557 if ($bit =~ s/^\s*=\s*//) {
1559 push @code, "; HTML( $bit );"
1562 push @code, $bit, ";\n";
1565 my $asp = join "", @code;
1566 #::logDebug("ASP CALL:\n$asp\n");
1567 return tag_perl ($tables, $opt, $asp);
1572 $safe_safe = new Vend::Safe;
1575 my ($tables, $opt,$body) = @_;
1576 my ($result,@share);
1577 #::logDebug("tag_perl MVSAFE=$MVSAFE::Safe opts=" . uneval($opt));
1579 if($Vend::NoInterpolate) {
1580 logGlobal({ level => 'alert' },
1581 "Attempt to interpolate perl/ITL from RPC, no permissions."
1586 if ($MVSAFE::Safe) {
1587 #::logDebug("tag_perl: Attempt to call perl from within Safe.");
1591 #::logDebug("tag_perl: tables=$tables opt=" . uneval($opt) . " body=$body");
1592 #::logDebug("tag_perl initialized=$Vend::Calc_initialized: carts=" . uneval($::Carts));
1593 if($opt->{subs} or $opt->{arg} =~ /\bsub\b/) {
1595 for(keys %{$Global::GlobalSub}) {
1596 #::logDebug("tag_perl share subs: GlobalSub=$_");
1597 next if defined $Global::AdminSub->{$_}
1598 and ! $Global::AllowGlobal->{$Vend::Cat};
1599 *$_ = \&{$Global::GlobalSub->{$_}};
1602 for(keys %{$Vend::Cfg->{Sub} || {}}) {
1603 #::logDebug("tag_perl share subs: Sub=$_");
1604 *$_ = \&{$Vend::Cfg->{Sub}->{$_}};
1610 my (@tab) = grep /\S/, split /\s+/, $tables;
1611 foreach my $tab (@tab) {
1613 my $db = database_exists_ref($tab);
1617 if($db->config('type') == 10) {
1618 my @extra_tabs = $db->_shared_databases();
1619 push (@tab, @extra_tabs);
1621 } elsif ($db->can('dbh')) {
1627 $Sql{$tab} = $hole->wrap($dbh);
1629 $Db{$tab} = $hole->wrap($db);
1630 if($db->config('name') ne $tab) {
1631 $Db{$db->config('name')} = $Db{$tab};
1635 $Sql{$tab} = $db->[$Vend::Table::DBI::DBI]
1642 $Tag = $hole->wrap($Tag) if $hole and ! $Vend::TagWrapped++;
1644 init_calc() if ! $Vend::Calc_initialized;
1645 $ready_safe->share(@share) if @share;
1647 if($Vend::Cfg->{Tie_Watch}) {
1649 for(@{$Vend::Cfg->{Tie_Watch}}) {
1650 logGlobal("touching $_");
1651 my $junk = $Config->{$_};
1656 $Items = $Vend::Items;
1658 $body = readfile($opt->{file}) . $body
1661 # Skip costly eval of code entirely if perl tag was called with no code,
1662 # likely used only for the side-effect of opening database handles
1663 return if $body !~ /\S/;
1665 $body =~ tr/\r//d if $Global::Windows;
1669 ( $opt->{global} or (! defined $opt->{global} and $Global::PerlAlwaysGlobal->{$Vend::Cat} ) )
1671 $Global::AllowGlobal->{$Vend::Cat}
1674 $MVSAFE::Safe = 0 unless $MVSAFE::Unsafe;
1677 if(! $MVSAFE::Safe) {
1678 if ($Global::PerlNoStrict->{$Vend::Cat} || $opt->{no_strict}) {
1680 $result = eval($body);
1683 $result = eval($body);
1687 $result = $ready_safe->reval($body);
1690 undef $MVSAFE::Safe;
1693 #::logDebug("tag_perl failed $@");
1696 $Vend::Session->{try}{$Vend::Try} .= "\n"
1697 if $Vend::Session->{try}{$Vend::Try};
1698 $Vend::Session->{try}{$Vend::Try} .= $@;
1700 if($opt->{number_errors}) {
1701 my @lines = split("\n",$body);
1703 map { $_ = sprintf("% 4d %s",$counter++,$_); } @lines;
1704 $body = join("\n",@lines);
1706 if($opt->{trim_errors}) {
1707 if($msg =~ /line (\d+)\.$/) {
1708 my @lines = split("\n",$body);
1709 my $start = $1 - $opt->{trim_errors} - 1;
1710 my $length = (2 * $opt->{trim_errors}) + 1;
1711 @lines = splice(@lines,$start,$length);
1712 $body = join("\n",@lines);
1715 if($opt->{eval_label}) {
1716 $msg =~ s/\(eval \d+\)/($opt->{eval_label})/g;
1718 if($opt->{short_errors}) {
1720 logError( "Safe: %s" , $msg );
1721 logGlobal({ level => 'debug' }, "Safe: %s" , $msg );
1723 logError( "Safe: %s\n%s\n" , $msg, $body );
1724 logGlobal({ level => 'debug' }, "Safe: %s\n%s\n" , $msg, $body );
1726 return $opt->{failure};
1728 #::logDebug("tag_perl initialized=$Vend::Calc_initialized: carts=" . uneval($::Carts));
1730 if ($opt->{no_return}) {
1731 $Vend::Session->{mv_perl_result} = $result;
1732 $result = join "", @Vend::Document::Out;
1733 @Vend::Document::Out = ();
1735 #::logDebug("tag_perl succeeded result=$result\nEND");
1740 return $_[0] if ! $_[0] or $Safe_data or $::Pragma->{safe_data};
1741 $_[0] =~ s/\[/[/g;
1746 my($type, $opt, $text) = @_;
1748 $type = 'html interchange' unless $type;
1749 $type =~ s/minivend/interchange/g;
1751 if ($type =~ /interchange/i) {
1752 $text =~ s/\[/[/g;
1754 if($type =~ /html/i) {
1755 $text =~ s/\</</g;
1761 my($pragma, $opt, $text) = @_;
1764 # pragma value may come in attached to the pragma name from [tag pragma name value][/tag]
1765 $pragma =~ s/^(\w+)(?:\s+(\w+))?.*/$1/ and $value = $2;
1767 # or as a specified option [tag op=pragma arg="name" value="value"][/tag]
1768 $value = defined $opt->{value} ? $opt->{value} : 1
1769 unless defined $value;
1771 # or as a tag body like [tag pragma name]value[/pragma]
1772 if(! defined $opt->{value} and $text =~ /\S/) {
1776 $::Pragma->{$pragma} = $value;
1781 my($flag, $opt, $text) = @_;
1785 ($flag, $text) = split /\s+/, $flag;
1787 my $value = defined $opt->{value} ? $opt->{value} : 1;
1788 my $fmt = $opt->{status} || '';
1791 #::logDebug("tag flag=$flag text=$text value=$value opt=". uneval_it($opt));
1792 if($flag eq 'write' || $flag eq 'read') {
1793 my $arg = $opt->{table} || $text;
1794 $value = 0 if $flag eq 'read';
1795 my (@args) = Text::ParseWords::shellwords($arg);
1797 foreach $dbname (@args) {
1798 # Handle table:column:key
1800 #::logDebug("tag flag write $dbname=$value");
1801 $Vend::WriteDatabase{$dbname} = $value;
1804 elsif($flag =~ /^transactions?/i) {
1805 my $arg = $opt->{table} || $text;
1806 my (@args) = Text::ParseWords::shellwords($arg);
1808 foreach $dbname (@args) {
1809 # Handle table:column:key
1811 $Vend::TransactionDatabase{$dbname} = $value;
1812 $Vend::WriteDatabase{$dbname} = $value;
1814 # we can't do anything else if in Safe
1815 next if $MVSAFE::Safe;
1817 # Now we close and reopen
1818 my $db = database_exists_ref($dbname)
1821 # need to reopen in transactions mode.
1824 $db = database_exists_ref($dbname);
1828 $Sql{$dbname} = $db->dbh()
1832 elsif($flag eq 'commit' || $flag eq 'rollback') {
1833 my $arg = $opt->{table} || $text;
1834 $value = 0 if $flag eq 'rollback';
1835 my $method = $value ? 'commit' : 'rollback';
1836 my (@args) = Text::ParseWords::shellwords($arg);
1838 foreach $dbname (@args) {
1839 # Handle table:column:key
1841 #::logDebug("tag commit $dbname=$value");
1842 my $db = database_exists_ref($dbname);
1843 next unless $db->isopen();
1844 next unless $db->config('Transactions');
1846 logError("attempt to $method on unknown database: %s", $dbname);
1849 if( ! $db->$method() ) {
1850 logError("problem doing $method for table: %s", $dbname);
1855 elsif($flag eq 'checkhtml') {
1856 $Vend::CheckHTML = $value;
1857 @status = ("Set CheckHTML flag: %s", $value);
1860 @status = ("Unknown flag operation '%s', ignored.", $flag);
1861 $status[0] = $opt->{status} if $opt->{status};
1862 logError( @status );
1864 return '' unless $opt->{show};
1865 $status[0] = $opt->{status} if $opt->{status};
1866 return errmsg(@status);
1870 my ($args, $opt, $text) = @_;
1871 $opt->{base} = $opt->{table} || $opt->{database} || undef
1872 unless defined $opt->{base};
1873 unless (defined $opt->{base}) {
1874 @{$opt}{ qw/base file type/ } = split /\s+/, $args;
1876 if($opt->{delete}) {
1877 undef $opt->{delete} unless $opt->{verify};
1879 #::logDebug("exporting " . join (",", @{$opt}{ qw/base file type field delete/ }));
1880 my $status = Vend::Data::export_database(
1881 @{$opt}{ qw/base file type/ }, $opt,
1883 return $status unless $opt->{hide};
1888 my ($table, $opt, $text) = @_;
1889 if($opt->{delete}) {
1890 undef $opt->{delete} unless $opt->{verify};
1892 #::logDebug("exporting " . join (",", @{$opt}{ qw/table file type field delete/ }));
1893 my $status = Vend::Data::export_database(
1894 @{$opt}{ qw/table file type/ }, $opt,
1896 return $status unless $opt->{hide};
1901 my ($option, $opt, $text) = @_;
1906 #::logDebug("mime call, opt=" . uneval($opt));
1907 $Vend::TIMESTAMP = POSIX::strftime("%y%m%d%H%M%S", localtime())
1908 unless defined $Vend::TIMESTAMP;
1910 $::Instance->{MIME_BOUNDARY} =
1911 $::Instance->{MIME_TIMESTAMP} . '-' .
1912 $Vend::SessionID . '-' .
1913 $Vend::Session->{pageCount} .
1915 unless defined $::Instance->{MIME_BOUNDARY};
1917 my $msg_type = $opt->{type} || "multipart/mixed";
1918 if($option eq 'reset') {
1919 undef $::Instance->{MIME_TIMESTAMP};
1920 undef $::Instance->{MIME_BOUNDARY};
1923 elsif($option eq 'boundary') {
1924 $out = "--$::Instance->{MIME_BOUNDARY}";
1926 elsif($option eq 'id') {
1927 $::Instance->{MIME} = 1;
1930 elsif($option eq 'header') {
1934 Content-Type: $msg_type; BOUNDARY="$::Instance->{MIME_BOUNDARY}"
1938 elsif ( $text !~ /\S/) {
1943 $::Instance->{MIME} = 1;
1944 my $desc = $opt->{description} || $option;
1945 my $type = $opt->{type} || 'text/plain; charset=US-ASCII';
1946 my $disposition = $opt->{attach_only}
1947 ? qq{attachment; filename="$desc"}
1949 my $encoding = $opt->{transfer_encoding};
1951 push @headers, "Content-Type: $type";
1952 push @headers, "Content-ID: $id";
1953 push @headers, "Content-Disposition: $disposition";
1954 push @headers, "Content-Description: $desc";
1955 push @headers, "Content-Transfer-Encoding: $opt->{transfer_encoding}"
1956 if $opt->{transfer_encoding};
1957 my $head = join "\n", @headers;
1959 --$::Instance->{MIME_BOUNDARY}
1966 #::logDebug("tag mime returns:\n$out");
1971 my($file, $opt, $data) = @_;
1977 $file = $opt->{file} || $Vend::Cfg->{LogFile};
1978 if($file =~ s/^\s*>\s*//) {
1982 $file = Vend::Util::escape_chars($file);
1983 unless(Vend::File::allowed_file($file)) {
1984 Vend::File::log_file_violation($file, 'log');
1988 $file = ">$file" if $opt->{create};
1990 unless($opt->{process} and $opt->{process} =~ /\bnostrip\b/i) {
1991 $data =~ s/\r\n/\n/g;
1993 $data =~ s/\s+$/\n/;
1996 my ($delim, $record_delim);
1997 for(qw/delim record_delim/) {
1998 next unless defined $opt->{$_};
1999 $opt->{$_} = $ready_safe->reval(qq{$opt->{$_}});
2003 if($opt->{type} =~ /^text/) {
2004 $status = Vend::Util::writefile($file, $data, $opt);
2006 elsif($opt->{type} =~ /^\s*quot/) {
2007 $record_delim = $opt->{record_delim} || "\n";
2008 @lines = split /$record_delim/, $data;
2010 @fields = Text::ParseWords::shellwords $_;
2011 $status = logData($file, @fields)
2015 elsif($opt->{type} =~ /^(?:error|debug)/) {
2017 $data = format_log_msg($data) unless $data =~ s/^\\//;;
2018 $status = Vend::Util::writefile($file, $data, $opt);
2020 elsif ($opt->{type} =~ /^debug/) {
2021 $status = Vend::Util::logDebug($data);
2024 $status = Vend::Util::logError($data);
2029 $record_delim = $opt->{record_delim} || "\n";
2030 $delim = $opt->{delimiter} || "\t";
2031 @lines = split /$record_delim/, $data;
2033 @fields = split /$delim/, $_;
2034 $status = logData($file, @fields)
2039 return $status unless $opt->{hide};
2044 '<Interchange.' . $::VERSION . '.' .
2045 $Vend::TIMESTAMP . '.' .
2046 $Vend::SessionID . '.' .
2047 ++$Vend::Session->{pageCount} . '@' .
2048 $Vend::Cfg->{VendURL} . '>';
2053 my ($opt, $text) = @_;
2056 my $name = lc $opt->{name};
2060 $name =~ s/(\w+)/\u$1/g;
2061 my $content = $opt->{content} || $text;
2062 $content =~ s/^\s+//;
2063 $content =~ s/\s+$//;
2064 $content =~ s/[\r\n]/; /g;
2065 $text = "$name: $content";
2067 if($Vend::StatusLine and ! $opt->{replace}) {
2068 $Vend::StatusLine =~ s/\s*$/\r\n/;
2069 $Vend::StatusLine .= $text;
2072 $Vend::StatusLine = $text;
2074 return $text if $opt->{show};
2079 my ($locale, $opt, $fmt) = @_;
2083 $current = POSIX::setlocale(&POSIX::LC_TIME);
2084 POSIX::setlocale(&POSIX::LC_TIME, $locale);
2087 local($ENV{TZ}) = $opt->{tz} if $opt->{tz};
2089 my $now = $opt->{time} || time();
2090 $fmt = '%Y%m%d' if $opt->{sortable};
2092 if($opt->{adjust} || $opt->{hours}) {
2093 my $adjust = $opt->{adjust};
2094 if ($opt->{hours}) {
2095 $adjust ||= $opt->{hours};
2096 $adjust .= ' hours';
2099 elsif ($adjust !~ /[A-Za-z]/) {
2100 $adjust =~ s/(?<=\d)(\d[05])// and $adjust += $1 / 60;
2101 $adjust .= ' hours';
2104 $now = adjust_time($adjust, $now, $opt->{compensate_dst});
2107 $fmt ||= $opt->{format} || $opt->{fmt} || '%c';
2108 my $out = $opt->{gmt} ? ( POSIX::strftime($fmt, gmtime($now) ))
2109 : ( POSIX::strftime($fmt, localtime($now) ));
2110 $out =~ s/\b0(\d)\b/$1/g if $opt->{zerofix};
2111 POSIX::setlocale(&POSIX::LC_TIME, $current) if defined $current;
2115 use vars qw/ %Tag_op_map /;
2121 HEADER => \&http_header,
2122 EXPORT => \&tag_export,
2127 $opt->{search} = "ra=yes\nst=db\nml=100000\nfi=$table";
2128 #::logDebug("tag each: table=$table opt=" . uneval($opt));
2129 return tag_loop_list('', $opt, shift);
2132 SHOW_TAGS => \&show_tags,
2137 #::logDebug("tag op: op=$op opt=" . uneval(\@_));
2138 return $_[3] if ! defined $Tag_op_map{$op};
2140 #::logDebug("tag args now: op=$op opt=" . uneval(\@_));
2141 return &{$Tag_op_map{$op}}(@_);
2145 my $file = shift || 'etc/counter';
2147 #::logDebug("counter: file=$file start=$opt->{start} sql=$opt->{sql} routine=$opt->{inc_routine} caller=" . scalar(caller()) );
2149 my ($tab, $seq) = split /:+/, $opt->{sql}, 2;
2150 my $db = database_exists_ref($tab);
2153 if($opt->{bypass}) {
2154 $dsn = $opt->{dsn} || $ENV{DBI_DSN};
2155 $dbh = DBI->connect(
2164 $dsn = $db->config('DSN');
2170 my $diemsg = errmsg(
2171 "Counter sequence '%s' failed, using file.\n",
2176 "No database handle for counter sequence '%s', using file.",
2180 elsif($seq =~ /^\s*SELECT\W/i) {
2181 #::logDebug("found custom SQL SELECT for sequence: $seq");
2182 my $sth = $dbh->prepare($seq) or die $diemsg;
2183 $sth->execute or die $diemsg;
2184 ($val) = $sth->fetchrow_array;
2186 elsif($dsn =~ /^dbi:mysql:/i) {
2188 $dbh->do("INSERT INTO $seq VALUES (0)") or die $diemsg;
2189 my $sth = $dbh->prepare("select LAST_INSERT_ID()")
2191 $sth->execute() or die $diemsg;
2192 ($val) = $sth->fetchrow_array;
2194 elsif($dsn =~ /^dbi:Pg:/i) {
2195 my $sth = $dbh->prepare("select nextval('$seq')")
2199 ($val) = $sth->fetchrow_array;
2201 elsif($dsn =~ /^dbi:Oracle:/i) {
2202 my $sth = $dbh->prepare("select $seq.nextval from dual")
2206 ($val) = $sth->fetchrow_array;
2211 logOnce('error', $@) if $@;
2213 return $val if defined $val;
2216 unless (allowed_file($file)) {
2217 log_file_violation ($file, 'counter');
2221 $file = $Vend::Cfg->{VendRoot} . "/$file"
2222 unless Vend::Util::file_name_is_absolute($file);
2224 for(qw/inc_routine dec_routine/) {
2225 my $routine = $opt->{$_}
2228 if( ! ref($routine) ) {
2229 $opt->{$_} = $Vend::Cfg->{Sub}{$routine};
2230 $opt->{$_} ||= $Global::GlobalSub->{$routine};
2234 my $ctr = new Vend::CounterFile
2236 $opt->{start} || undef,
2238 $opt->{inc_routine},
2239 $opt->{dec_routine};
2240 return $ctr->value() if $opt->{value};
2241 return $ctr->dec() if $opt->{decrement};
2245 # Returns the text of a user entered field named VAR.
2246 sub tag_value_extended {
2247 my($var, $opt) = @_;
2249 my $vspace = $opt->{values_space};
2251 if (defined $vspace) {
2252 if ($vspace eq '') {
2253 $vref = $Vend::Session->{values};
2256 $vref = $Vend::Session->{values_repository}{$vspace} ||= {};
2263 my $yes = $opt->{yes} || 1;
2264 my $no = $opt->{'no'} || '';
2267 $opt->{test} =~ /(?:is)?put/i
2269 return defined $CGI::put_ref ? $yes : $no;
2270 $opt->{test} =~ /(?:is)?file/i
2272 return defined $CGI::file{$var} ? $yes : $no;
2273 $opt->{test} =~ /defined/i
2275 return defined $CGI::values{$var} ? $yes : $no;
2276 return length $CGI::values{$var}
2277 if $opt->{test} =~ /length|size/i;
2281 if($opt->{put_contents}) {
2282 return undef if ! defined $CGI::put_ref;
2283 return $$CGI::put_ref;
2286 my $val = $CGI::values{$var} || $vref->{$var} || return undef;
2287 $val =~ s/</</g unless $opt->{enable_html};
2288 $val =~ s/\[/[/g unless $opt->{enable_itl};
2290 if($opt->{file_contents}) {
2291 return '' if ! defined $CGI::file{$var};
2292 return $CGI::file{$var};
2295 if($opt->{put_ref}) {
2296 return $CGI::put_ref;
2299 if($opt->{outfile}) {
2300 my $file = $opt->{outfile};
2304 unless (Vend::File::allowed_file($file)) {
2305 Vend::File::log_file_violation($file, 'value-extended');
2310 my $replace = $^O =~ /win32/i ? "\r\n" : "\n";
2311 if($CGI::file{$var} !~ /\n/) {
2312 # Must be a mac file.
2313 $CGI::file{$var} =~ s/\r/$replace/g;
2315 elsif ( $CGI::file{$var} =~ /\r\n/) {
2316 # Probably a PC file
2317 $CGI::file{$var} =~ s/\r\n/$replace/g;
2320 $CGI::file{$var} =~ s/\n/$replace/g;
2323 if($opt->{maxsize} and length($CGI::file{$var}) > $opt->{maxsize}) {
2325 "Uploaded file write of %s bytes greater than maxsize %s. Aborted.",
2326 length($CGI::file{$var}),
2331 #::logDebug(">$file \$CGI::file{$var}" . uneval($opt));
2332 $opt->{encoding} ||= $CGI::file_encoding{$var};
2333 Vend::Util::writefile(">$file", \$CGI::file{$var}, $opt)
2339 if (defined $opt->{joiner}) {
2340 $joiner = $opt->{joiner};
2341 if($joiner eq '\n') {
2344 elsif($joiner =~ m{\\}) {
2345 $joiner = $ready_safe->reval("qq{$joiner}");
2352 my $index = defined $opt->{'index'} ? $opt->{'index'} : '*';
2354 $index = '*' if $index =~ /^\s*\*?\s*$/;
2358 @ary = split /\0/, $val;
2360 elsif($val =~ /ARRAY/) {
2364 logError( "value-extended %s: passed non-scalar, non-array object", $var);
2367 return join " ", 0 .. $#ary if $opt->{elements};
2370 @ary = @ary[$ready_safe->reval( $index eq '*' ? "0 .. $#ary" : $index )];
2372 logError("value-extended $var: bad index") if $@;
2374 if($opt->{filter}) {
2376 $_ = filter_value($opt->{filter}, $_, $var);
2379 return join $joiner, @ary;
2382 sub format_auto_transmission {
2385 ## Auto-transmission from Vend::Data::update_data
2386 ## Looking for structure like:
2388 ## [ '### BEGIN submission from', 'ckirk' ],
2389 ## [ 'username', 'ckirk' ],
2390 ## [ 'field2', 'value2' ],
2391 ## [ 'field1', 'value1' ],
2392 ## [ '### END submission from', 'ckirk' ],
2393 ## [ 'mv_data_fields', [ username, field1, field2 ]],
2396 return $ref unless ref($ref);
2400 my $header = shift @$ref;
2401 my $fields = pop @$ref;
2402 my $trailer = pop @$ref;
2404 $body .= "$header->[0]: $header->[1]\n";
2406 for my $line (@$ref) {
2407 $message{$line->[0]} = $line->[1];
2411 if(ref $fields->[1]) {
2412 @order = @{$fields->[1]};
2415 @order = sort keys %message;
2420 if($message{$_} =~ s/\r?\n/\n/g) {
2421 $body .= "\n$message{$_}\n";
2424 $body .= $message{$_};
2429 $body .= "$trailer->[0]: $trailer->[1]\n";
2434 my($to, $opt, $body) = @_;
2452 return if ! defined $CGI::values{"mv_email_$k"};
2453 $abort = 1 if ! $::Scratch->{mv_email_enable};
2454 $check = 1 if $::Scratch->{mv_email_enable};
2455 return $CGI::values{"mv_email_$k"};
2461 unless($opt->{raw}) {
2462 for my $header (@todo) {
2463 logError("invalid email header: %s", $header)
2464 if $header =~ /[^-\w]/;
2465 my $key = lc $header;
2467 my $val = $opt->{$key} || $setsub->($key);
2468 if($key eq 'subject' and ! length($val) ) {
2469 $val = errmsg('<no subject>');
2471 next unless length $val;
2472 $found{$key} = $val;
2475 $val =~ s/[\r\n]+\s*(\S)/\n\t$1/g;
2476 push @headers, "$header: $val";
2478 unless($found{to} or $::Scratch->{mv_email_enable} =~ /\@/) {
2480 error_opt($opt, "Refuse to send email message with no recipient.");
2482 elsif (! $found{to}) {
2483 $::Scratch->{mv_email_enable} =~ s/\s+/ /g;
2484 $found{to} = $::Scratch->{mv_email_enable};
2485 push @headers, "To: $::Scratch->{mv_email_enable}";
2490 $opt->{extra} =~ s/^\s+//mg;
2491 $opt->{extra} =~ s/\s+$//mg;
2492 push @headers, grep /^\w[-\w]*:/, split /\n/, $opt->{extra};
2495 $body ||= $setsub->('body');
2497 return error_opt($opt, "Refuse to send email message with no body.");
2500 $body = format_auto_transmission($body) if ref $body;
2502 push(@headers, '') if @headers;
2504 return error_opt("mv_email_enable not set, required.") if $abort;
2505 if($check and $found{to} ne $Scratch->{mv_email_enable}) {
2507 "mv_email_enable to address (%s) doesn't match enable (%s)",
2509 $Scratch->{mv_email_enable},
2514 $ok = send_mail(\@headers, $body);
2519 $body = substr($body, 0, 2000) if length($body) > 2000;
2521 "Unable to send mail using %s\n%s",
2522 $Vend::Cfg->{SendMailProgram},
2523 join("\n", @headers, $body),
2527 delete $Scratch->{mv_email_enable} if $check;
2528 return if $opt->{hide};
2529 return join("\n", @headers, $body) if $opt->{show};
2530 return ($opt->{success} || $ok);
2533 # Returns the text of a user entered field named VAR.
2536 #::logDebug("called value args=" . uneval(\@_));
2539 my $vspace = $opt->{values_space};
2541 if (defined $vspace) {
2542 if ($vspace eq '') {
2543 $vref = $Vend::Session->{values};
2546 $vref = $Vend::Session->{values_repository}{$vspace} ||= {};
2553 $vref->{$var} = $opt->{set} if defined $opt->{set};
2555 my $value = defined $vref->{$var} ? $vref->{$var} : '';
2556 $value =~ s/\[/[/g unless $opt->{enable_itl};
2557 if($opt->{filter}) {
2558 $value = filter_value($opt->{filter}, $value, $var);
2559 $vref->{$var} = $value unless $opt->{keep};
2561 $::Scratch->{$var} = $value if $opt->{scratch};
2562 return '' if $opt->{hide};
2563 return $opt->{default} if ! $value and defined $opt->{default};
2564 $value =~ s/</</g unless $opt->{enable_html};
2570 $string =~ s!(\W)!'%' . sprintf '%02x', ord($1)!eg;
2574 # Escapes a scan reliably in three different possible ways
2576 my ($scan, $ref) = @_;
2577 #::logDebug("escape_scan: scan=$scan");
2581 $_ = "se=$_" unless /[=\n]/;
2582 $add .= "\nos=0" unless m{^\s*os=}m;
2583 $add .= "\nne=0" unless m{^\s*ne=}m;
2584 $add .= "\nop=rm" unless m{^\s*op=}m;
2585 $add .= "\nbs=0" unless m{^\s*bs=}m;
2586 $add .= "\nsf=*" unless m{^\s*sf=}m;
2587 $add .= "\ncs=0" unless m{^\s*cs=}m;
2588 $add .= "\nsg=0" unless m{^\s*sg=}m;
2589 $add .= "\nnu=0" unless m{^\s*nu=}m;
2592 $scan = join "\n", @$scan;
2593 $scan .= "\nco=yes" unless m{^\s*co=}m;
2594 #::logDebug("escape_scan: scan=$scan");
2597 if($scan =~ /^\s*(?:sq\s*=\s*)?select\s+/im) {
2599 $scan = Vend::Scan::sql_statement($scan, $ref || \%CGI::values)
2602 my $msg = errmsg("SQL query failed: %s\nquery was: %s", $@, $scan);
2604 $scan = 'se=BAD_SQL';
2608 return join '/', 'scan', escape_mv('/', $scan);
2617 ## Already escaped, return
2618 return $val if $val =~ /^\S+=\S+=\S*$/;
2620 my @args = split /\n+/, $val;
2623 s/^(.*?=)(.+)/$1 . Vend::Util::unhexify($2)/ge;
2629 s!([^=]+)=(.*)!esc($1) . '=' . esc($2)!eg
2630 or (undef $_, next);
2632 return join $Global::UrlJoiner, grep length($_), @args;
2636 my ($joiner, $scan, $not_scan, $esc) = @_;
2640 if(index($scan, "\n") != -1) {
2641 $scan =~ s/^\s+//mg;
2642 $scan =~ s/\s+$//mg;
2643 @args = split /\n+/, $scan;
2645 elsif($scan =~ /&\w\w=/) {
2646 @args = split /&/, $scan;
2649 $scan =~ s!::!__SLASH__!g;
2650 @args = split m:/:, $scan;
2652 @args = grep $_, @args;
2654 s!/!__SLASH__!g unless defined $not_scan;
2657 or (undef $_, next);
2658 s!__SLASH__!::!g unless defined $not_scan;
2660 return join $joiner, grep(defined $_, @args);
2665 my ($urlroutine, $page, $arg, $opt);
2668 my ($page, $arg, $opt) = @_;
2670 my $url = tag_area(@_);
2673 if($extra = ($opt ||= {})->{extra} || '') {
2674 $extra =~ s/^(\w+)$/class=$1/;
2677 return qq{<a href="$url"$extra>};
2680 # Returns an href which will call up the specified PAGE.
2683 ($page, $arg, $opt) = @_;
2685 $page = '' if ! defined $page;
2687 if( $page and $opt->{alias}) {
2688 my $aloc = $opt->{once} ? 'one_time_path_alias' : 'path_alias';
2689 $Vend::Session->{$aloc}{$page} = {}
2690 if not defined $Vend::Session->{path_alias}{$page};
2691 $Vend::Session->{$aloc}{$page} = $opt->{alias};
2696 if ($opt->{search}) {
2697 $page = escape_scan($opt->{search});
2699 elsif ($page =~ /^[a-z][a-z]+:/) {
2700 ### Javascript or absolute link
2701 return $page unless $opt->{form};
2702 $page =~ s{(\w+://[^/]+)/}{}
2705 my @pieces = split m{/}, $page, 9999;
2706 $page = pop(@pieces);
2707 if(! length($page)) {
2708 $page = pop(@pieces);
2709 if(! length($page)) {
2711 $r =~ s{/([^/]+)}{};
2718 $r = join "/", $intro, @pieces unless $r;
2719 $opt->{add_dot_html} = 0;
2720 $opt->{no_session} = 1;
2722 $opt->{no_count} = 1;
2724 elsif ($page eq 'scan') {
2725 $page = escape_scan($arg);
2729 elsif ($subname = $Vend::Cfg->{SpecialSub}{areapage}) {
2730 my $sub = $Vend::Cfg->{Sub}{$subname} || $Global::GlobalSub->{$subname};
2731 my $newpage = $sub->($page, $opt);
2732 $page = $newpage if defined $newpage;
2736 $urlroutine = $opt->{secure} ? \&secure_vendUrl : \&vendUrl;
2738 return $urlroutine->($page, $arg, undef, $opt);
2743 *form_link = \&tag_area;
2745 # Sets the default shopping cart for display
2747 $Vend::CurrentCart = shift;
2751 # Sets the discount namespace.
2752 sub switch_discount_space {
2753 my $dspace = shift || 'main';
2755 if (! $Vend::Cfg->{DiscountSpacesOn}) {
2757 = $Vend::Session->{discount}
2759 return $Vend::DiscountSpaceName = 'main';
2762 my $oldspace = $Vend::DiscountSpaceName || 'main';
2763 #::logDebug("switch_discount_space: called for space '$dspace'; current space is $oldspace.");
2764 unless ($Vend::Session->{discount} and $Vend::Session->{discount_space}) {
2766 = $Vend::Session->{discount}
2767 = $Vend::Session->{discount_space}{main}
2768 ||= ($Vend::Session->{discount} || {});
2769 $Vend::DiscountSpaceName = 'main';
2770 #::logDebug('switch_discount_space: initialized discount space hash.');
2772 if ($dspace ne $oldspace) {
2774 = $Vend::Session->{discount}
2775 = $Vend::Session->{discount_space}{$Vend::DiscountSpaceName = $dspace}
2777 #::logDebug("switch_discount_space: changed discount space from '$oldspace' to '$Vend::DiscountSpaceName'");
2780 # Make certain the hash is set, in case app programmer manipulated the session directly.
2782 = $Vend::Session->{discount}
2783 = $Vend::Session->{discount_space}{$Vend::DiscountSpaceName}
2784 unless ref $::Discounts eq 'HASH';
2792 if($Vend::NoInterpolate) {
2793 logGlobal({ level => 'alert' },
2794 "Attempt to interpolate perl/ITL from RPC, no permissions."
2798 $Items = $Vend::Items;
2801 $result = eval($body);
2804 init_calc() if ! $Vend::Calc_initialized;
2805 $result = $ready_safe->reval($body);
2810 $Vend::Session->{try}{$Vend::Try} = $msg if $Vend::Try;
2811 logGlobal({ level => 'debug' }, "Safe: %s\n%s\n" , $msg, $body);
2812 logError("Safe: %s\n%s\n" , $msg, $body);
2813 return $MVSAFE::Safe ? '' : 0;
2819 return tag_self_contained_if(@_, 1) if defined $_[4];
2820 return tag_if(@_, 1);
2823 sub tag_self_contained_if {
2824 my($base, $term, $operator, $comp, $body, $negate) = @_;
2826 my ($else,$elsif,@addl);
2829 #::logDebug("self_if: base=$base term=$term op=$operator comp=$comp");
2830 if ($body =~ s#$QR{condition_begin}##) {
2833 #::logDebug("self_if: base=$base term=$term op=$operator comp=$comp");
2835 if ( $body =~ /\[[EeTtAaOo][hHLlNnRr][SsEeDd\s]/ ) {
2836 ($body, $elsif, $else, @addl) = split_if($body);
2839 #::logDebug("Additional ops found:\n" . join("\n", @addl) ) if @addl;
2841 unless(defined $operator || defined $comp) {
2847 ($base =~ s/^\W+// or $base = "!$base") if $negate;
2849 my $status = conditional ($base, $term, $operator, $comp, @addl);
2856 $else = '[else]' . $else . '[/else]' if length $else;
2857 $elsif =~ s#(.*?)$QR{'/elsif'}(.*)#$1${2}[/elsif]#s;
2858 $out = '[if ' . $elsif . $else . '[/if]';
2860 elsif (length $else) {
2871 my($string, $reverse, $cond, $lhs) = @_;
2872 #::logDebug("pull_cond string='$string' rev='$reverse' cond='$cond' lhs='$lhs'");
2873 my ($op, $rhs) = split /\s+/, $cond, 2;
2874 $rhs =~ s/^(["'])(.*)\1$/$2/;
2875 if(! defined $cond_op{$op} ) {
2876 logError("bad conditional operator %s in if-PREFIX-data", $op);
2877 return pull_else($string, $reverse);
2879 return $cond_op{$op}->($lhs, $rhs)
2880 ? pull_if($string, $reverse)
2881 : pull_else($string, $reverse);
2885 return pull_cond(@_) if $_[2];
2886 my($string, $reverse) = @_;
2887 return pull_else($string) if $reverse;
2888 find_matching_else(\$string) if $string =~ s:$QR{has_else}::;
2893 return pull_cond(@_) if $_[2];
2894 my($string, $reverse) = @_;
2895 return pull_if($string) if $reverse;
2896 return find_matching_else(\$string) if $string =~ s:$QR{has_else}::;
2906 '' => sub { $_[0] cmp $_[1] },
2907 none => sub { $_[0] cmp $_[1] },
2908 f => sub { (lc $_[0]) cmp (lc $_[1]) },
2909 fr => sub { (lc $_[1]) cmp (lc $_[0]) },
2911 my ($a1,$a2) = split /[,.]/, $_[0], 2;
2912 my ($b1,$b2) = split /[,.]/, $_[1], 2;
2913 return $a1 <=> $b1 || $a2 <=> $b2;
2916 my ($a1,$a2) = split /[,.]/, $_[0], 2;
2917 my ($b1,$b2) = split /[,.]/, $_[1], 2;
2918 return $b1 <=> $a1 || $b2 <=> $a2;
2920 n => sub { $_[0] <=> $_[1] },
2921 nr => sub { $_[1] <=> $_[0] },
2922 r => sub { $_[1] cmp $_[0] },
2925 @Sort{qw/rf rl rn/} = @Sort{qw/fr lr nr/};
2927 use vars qw/%Sort_field/;
2928 %Sort_field = %Sort;
2931 my($opts, $list) = (@_);
2934 #::logDebug("tag_sort_ary: opts=$opts list=" . uneval($list));
2938 my ($start, $end, $num);
2939 my $glob_opt = 'none';
2941 my @opts = split /\s+/, $opts;
2942 my @option; my @bases; my @fields;
2945 my ($base, $fld, $opt) = split /:/, $_;
2947 if($base =~ /^(\d+)$/) {
2949 $glob_opt = $fld || $opt || 'none';
2952 if($base =~ /^([-=+])(\d+)-?(\d*)/) {
2954 if ($op eq '-') { $start = $2 }
2955 elsif ($op eq '+') { $num = $2 }
2956 elsif ($op eq '=') {
2958 $end = ($3 || undef);
2965 push @option, (defined $Vend::Interpolate::Sort_field{$opt} ? $opt : 'none');
2969 $num = 1 + $end - $start;
2970 $num = undef if $num < 1;
2974 my $routine = 'sub { ';
2975 for( $i = 0; $i < @bases; $i++) {
2976 $routine .= '&{$Vend::Interpolate::Sort_field{"' .
2979 $routine .= "tag_data('$bases[$i]','$fields[$i]', \$_[0]->[$key]),\n";
2980 $routine .= "tag_data('$bases[$i]','$fields[$i]', \$_[1]->[$key]) ) or ";
2982 $routine .= qq!0 or &{\$Vend::Interpolate::Sort_field{"$glob_opt"}}!;
2983 $routine .= '($_[0]->[$key],$_[1]->[$key]); }';
2984 #::logDebug("tag_sort_ary routine: $routine\n");
2986 my $code = eval $routine;
2987 die "Bad sort routine\n" if $@;
2989 #Prime the sort? Prevent variable suicide??
2990 #&{$Vend::Interpolate::Sort_field{'n'}}('31', '30');
2993 if($::Scratch->{mv_locale}) {
2994 POSIX::setlocale(POSIX::LC_COLLATE(),
2995 $::Scratch->{mv_locale});
2998 @codes = sort {&$code($a, $b)} @$list;
3001 splice(@codes, 0, $start - 1);
3005 splice(@codes, $num);
3007 #::logDebug("tag_sort_ary routine returns: " . uneval(\@codes));
3012 my($opts, $list) = (@_);
3015 #::logDebug("tag_sort_hash: opts=$opts list=" . uneval($list));
3019 my ($start, $end, $num);
3020 my $glob_opt = 'none';
3022 my @opts = split /\s+/, $opts;
3023 my @option; my @bases; my @fields;
3027 if(/^(\w+)(:([flnr]+))?$/) {
3029 $glob_opt = $3 || 'none';
3032 if(/^([-=+])(\d+)-?(\d*)/) {
3034 if ($op eq '-') { $start = $2 }
3035 elsif ($op eq '+') { $num = $2 }
3036 elsif ($op eq '=') {
3038 $end = ($3 || undef);
3042 my ($base, $fld, $opt) = split /:/, $_;
3046 push @option, (defined $Vend::Interpolate::Sort_field{$opt} ? $opt : 'none');
3050 $num = 1 + $end - $start;
3051 $num = undef if $num < 1;
3054 if (! defined $list->[0]->{$key}) {
3055 logError("sort key '$key' not defined in list. Skipping sort.");
3060 my $routine = 'sub { ';
3061 for( $i = 0; $i < @bases; $i++) {
3062 $routine .= '&{$Vend::Interpolate::Sort_field{"' .
3065 $routine .= "tag_data('$bases[$i]','$fields[$i]', \$_[0]->{$key}),\n";
3066 $routine .= "tag_data('$bases[$i]','$fields[$i]', \$_[1]->{$key}) ) or ";
3068 $routine .= qq!0 or &{\$Vend::Interpolate::Sort_field{"$glob_opt"}}!;
3069 $routine .= '($a->{$key},$_[1]->{$key}); }';
3071 #::logDebug("tag_sort_hash routine: $routine\n");
3072 my $code = eval $routine;
3073 die "Bad sort routine\n" if $@;
3075 #Prime the sort? Prevent variable suicide??
3076 #&{$Vend::Interpolate::Sort_field{'n'}}('31', '30');
3079 if($::Scratch->{mv_locale}) {
3080 POSIX::setlocale(POSIX::LC_COLLATE(),
3081 $::Scratch->{mv_locale});
3084 @codes = sort {&$code($a,$b)} @$list;
3087 splice(@codes, 0, $start - 1);
3091 splice(@codes, $num);
3093 #::logDebug("tag_sort_hash routine returns: " . uneval(\@codes));
3100 my($name, $value, $text, $substr) = @_;
3101 # $value is case-sensitive flag if passed text;
3103 $text =~ s:$QR{condition}::;
3104 $value = $value ? lc $1 : $1;
3106 $value = substr($value, 0, $substr) if $substr;
3107 my $prev = $Prev{$name};
3108 $Prev{$name} = $value;
3110 return pull_if($text) if ! defined $prev or $value ne $prev;
3111 return pull_else($text);
3113 return 1 unless defined $prev;
3114 return $value eq $prev ? 0 : 1;
3119 my $textref = shift;
3121 $$textref =~ s:\[quantity[-_]name:[$prefix-quantity-name:gi;
3122 $$textref =~ s:\[modifier[-_]name\s:[$prefix-modifier-name :gi;
3124 $$textref =~ s:\[if[-_]data\s:[if-$prefix-data :gi
3125 and $$textref =~ s:\[/if[-_]data\]:[/if-$prefix-data]:gi;
3127 $$textref =~ s:\[if[-_]modifier\s:[if-$prefix-param :gi
3128 and $$textref =~ s:\[/if[-_]modifier\]:[/if-$prefix-param]:gi;
3130 $$textref =~ s:\[if[-_]field\s:[if-$prefix-field :gi
3131 and $$textref =~ s:\[/if[-_]field\]:[/if-$prefix-field]:gi;
3133 $$textref =~ s:\[on[-_]change\s:[$prefix-change :gi
3134 and $$textref =~ s:\[/on[-_]change\s:[/$prefix-change :gi;
3139 sub tag_search_region {
3140 my($params, $opt, $text) = @_;
3141 $opt->{search} = $params if $params;
3142 $opt->{prefix} ||= 'item';
3143 $opt->{list_prefix} ||= 'search[-_]list';
3145 list_compat($opt->{prefix}, \$text) if $text;
3147 return region($opt, $text);
3152 return undef unless defined $$text and $$text =~ s#\[sort(([\s\]])(?s:.)+)#$1#io;
3153 my $options = find_close_square($$text);
3154 $$text = substr( $$text,length($options) + 1 )
3155 if defined $options;
3156 $options = interpolate_html($options) if index($options, '[') != -1;
3157 return $options || '';
3160 # Artificial for better variable passing
3180 sub more_link_template {
3181 my ($anchor, $arg, $form_arg) = @_;
3183 my $url = tag_area(undef, undef, {
3184 search => "MM=$arg",
3186 match_security => 1,
3189 my $lt = $link_template;
3190 $lt =~ s/\$URL\$/$url/g;
3191 $lt =~ s/\$ANCHOR\$/$anchor/g;
3197 my ($next, $last, $arg);
3199 $pa =~ s/__PAGE__/$inc/g;
3200 my $form_arg = "mv_more_ip=1\nmv_nextpage=$page";
3201 $form_arg .= "\npf=$prefix" if $prefix;
3202 $form_arg .= "\n$opt->{form}" if $opt->{form};
3203 $form_arg .= "\nmi=$more_id" if $more_id;
3204 $next = ($inc-1) * $chunk;
3205 #::logDebug("more_link: inc=$inc current=$current");
3206 $last = $next + $chunk - 1;
3207 $last = ($last+1) < $total ? $last : ($total - 1);
3208 $pa =~ s/__PAGE__/$inc/g;
3209 $pa =~ s/__MINPAGE__/$next + 1/eg;
3210 $pa =~ s/__MAXPAGE__/$last + 1/eg;
3211 if($inc == $current) {
3212 $pa =~ s/__BORDER__/$border_selected || $border || ''/e;
3213 $list .= qq|<strong>$pa</strong> | ;
3216 $pa =~ s/__BORDER__/$border/e;
3217 $arg = "$session:$next:$last:$chunk$perm";
3218 $list .= more_link_template($pa, $arg, $form_arg) . ' ';
3234 if(my $name = $opt->{more_routine}) {
3235 my $sub = $Vend::Cfg->{Sub}{$name} || $Global::GlobalSub->{$name};
3236 return $sub->(@_) if $sub;
3238 #::logDebug("more_list: opt=$opt label=$opt->{label}");
3239 return undef if ! $opt;
3240 $q = $opt->{object} || $::Instance->{SearchObject}{$opt->{label}};
3241 return '' unless $q->{matches} > $q->{mv_matchlimit}
3242 and $q->{mv_matchlimit} > 0;
3243 my($arg,$inc,$last,$m);
3245 my($first_anchor,$last_anchor);
3249 $session = $q->{mv_cache_key};
3250 my $first = $q->{mv_first_match} || 0;
3251 $chunk = $q->{mv_matchlimit};
3252 $perm = $q->{mv_more_permanent} ? ':1' : '';
3253 $total = $q->{matches};
3254 my $next = defined $q->{mv_next_pointer}
3255 ? $q->{mv_next_pointer}
3257 $page = $q->{mv_search_page} || $Global::Variable->{MV_PAGE};
3258 $prefix = $q->{prefix} || '';
3259 my $form_arg = "mv_more_ip=1\nmv_nextpage=$page";
3260 $form_arg .= "\npf=$q->{prefix}" if $q->{prefix};
3261 $form_arg .= "\n$opt->{form}" if $opt->{form};
3262 if($q->{mv_more_id}) {
3263 $more_id = $q->{mv_more_id};
3264 $form_arg .= "\nmi=$more_id";
3270 my $more_joiner = $opt->{more_link_joiner} || ' ';
3272 if($r =~ s:\[border\]($All)\[/border\]::i) {
3276 if($r =~ s:\[border[-_]selected\]($All)\[/border[-_]selected\]::i) {
3281 undef $link_template;
3282 $r =~ s:\[link[-_]template\]($All)\[/link[-_]template\]::i
3283 and $link_template = $1;
3284 $link_template ||= q{<a href="$URL$">$ANCHOR$</a>};
3286 if(! $chunk or $chunk >= $total) {
3290 $border = qq{ border="$border"} if defined $border;
3291 $border_selected = qq{ border="$border_selected"}
3292 if defined $border_selected;
3294 $adder = ($total % $chunk) ? 1 : 0;
3295 $pages = int($total / $chunk) + $adder;
3296 $current = int($next / $chunk) || $pages;
3299 $first = 0 if $first < 0;
3301 # First link may appear when prev link is valid
3302 if($r =~ s:\[first[-_]anchor\]($All)\[/first[-_]anchor\]::i) {
3306 $first_anchor = errmsg('First');
3308 unless ($first_anchor eq 'none') {
3312 $arg .= ":$chunk$perm";
3313 $hash{first_link} = more_link_template($first_anchor, $arg, $form_arg);
3316 unless ($prev_anchor) {
3317 if($r =~ s:\[prev[-_]anchor\]($All)\[/prev[-_]anchor\]::i) {
3321 $prev_anchor = errmsg('Previous');
3324 elsif ($prev_anchor ne 'none') {
3325 $prev_anchor = qq%<img src="$prev_anchor"$border>%;
3327 unless ($prev_anchor eq 'none') {
3330 $arg .= $first - $chunk;
3333 $arg .= ":$chunk$perm";
3334 $hash{prev_link} = more_link_template($prev_anchor, $arg, $form_arg);
3339 $r =~ s:\[(prev|first)[-_]anchor\]$All\[/\1[-_]anchor\]::ig;
3344 unless ($next_anchor) {
3345 if($r =~ s:\[next[-_]anchor\]($All)\[/next[-_]anchor\]::i) {
3349 $next_anchor = errmsg('Next');
3353 $next_anchor = qq%<img src="$next_anchor"$border>%;
3355 $last = $next + $chunk - 1;
3356 $last = $last > ($total - 1) ? $total - 1 : $last;
3357 $arg = "$session:$next:$last:$chunk$perm";
3358 $hash{next_link} = more_link_template($next_anchor, $arg, $form_arg);
3360 # Last link can appear when next link is valid
3361 if($r =~ s:\[last[-_]anchor\]($All)\[/last[-_]anchor\]::i) {
3365 $last_anchor = errmsg('Last');
3367 unless ($last_anchor eq 'none') {
3369 my $last_beg_idx = $total - ($total % $chunk || $chunk);
3370 $arg = "$session:$last_beg_idx:$last:$chunk$perm";
3371 $hash{last_link} = more_link_template($last_anchor, $arg, $form_arg);
3375 $r =~ s:\[(last|next)[-_]anchor\]$All\[/\1[-_]anchor\]::gi;
3378 unless ($page_anchor) {
3379 if($r =~ s:\[page[-_]anchor\]($All)\[/page[-_]anchor\]::i) {
3383 $page_anchor = '__PAGE__';
3386 elsif ($page_anchor ne 'none') {
3387 $page_anchor = qq%<img src="$page_anchor?__PAGE__"__BORDER__>%;
3390 $page_anchor =~ s/\$(MIN|MAX)?PAGE\$/__${1}PAGE__/g;
3392 my $more_string = errmsg('more');
3393 my ($decade_next, $decade_prev, $decade_div);
3394 if( $q->{mv_more_decade} or $r =~ m:\[decade[-_]next\]:) {
3395 $r =~ s:\[decade[-_]next\]($All)\[/decade[-_]next\]::i
3396 and $decade_next = $1;
3397 $decade_next = "<small>[$more_string>>]</small>"
3399 $r =~ s:\[decade[-_]prev\]($All)\[/decade[-_]prev\]::i
3400 and $decade_prev = $1;
3401 $decade_prev = "<small>[<<$more_string]</small>"
3403 $decade_div = $q->{mv_more_decade} > 1 ? $q->{mv_more_decade} : 10;
3407 if(defined $decade_div and $pages > $decade_div) {
3408 if($current > $decade_div) {
3409 $begin = ( int ($current / $decade_div) * $decade_div ) + 1;
3410 $hash{decade_prev} = more_link($begin - $decade_div, $decade_prev);
3415 if($begin + $decade_div <= $pages) {
3416 $end = $begin + $decade_div;
3417 $hash{decade_next} = more_link($end, $decade_next);
3422 delete $hash{$decade_next};
3424 #::logDebug("more_list: decade found pages=$pages current=$current begin=$begin end=$end next=$next last=$last decade_div=$decade_div");
3427 ($begin, $end) = (1, $pages);
3428 delete $hash{$decade_next};
3430 #::logDebug("more_list: pages=$pages current=$current begin=$begin end=$end next=$next last=$last decade_div=$decade_div page_anchor=$page_anchor");
3433 if ($q->{mv_alpha_list}) {
3434 for my $record (@{$q->{mv_alpha_list}}) {
3435 $arg = "$session:$record->[2]:$record->[3]:" . ($record->[3] - $record->[2] + 1);
3436 my $letters = substr($record->[0], 0, $record->[1]);
3437 push @more_links, more_link_template($letters, $arg, $form_arg);
3439 $hash{more_alpha} = join $more_joiner, @more_links;
3442 foreach $inc ($begin .. $end) {
3443 last if $page_anchor eq 'none';
3444 push @more_links, more_link($inc, $page_anchor);
3446 $hash{more_numeric} = join $more_joiner, @more_links;
3449 $hash{more_list} = join $more_joiner, @more_links;
3451 $first = $first + 1;
3452 $last = $first + $chunk - 1;
3453 $last = $last > $total ? $total : $last;
3454 $m = $first . '-' . $last;
3455 $hash{matches} = $m;
3456 $hash{first_match} = $first;
3457 $hash{last_match} = $last;
3458 $hash{decade_first} = $begin;
3459 $hash{decade_last} = $end;
3460 $hash{last_page} = $hash{total_pages} = $pages;
3461 $hash{current_page} = $current;
3462 $hash{match_count} = $q->{matches};
3464 if($r =~ /{[A-Z][A-Z_]+[A-Z]}/ and $r !~ $QR{more}) {
3465 return tag_attr_list($r, \%hash, 1);
3468 my $tpl = qq({FIRST_LINK?}{FIRST_LINK} {/FIRST_LINK?}{PREV_LINK?}{PREV_LINK} {/PREV_LINK?}{DECADE_PREV?}{DECADE_PREV} {/DECADE_PREV?}{MORE_LIST}{DECADE_NEXT?} {DECADE_NEXT}{/DECADE_NEXT?}{NEXT_LINK?} {NEXT_LINK}{/NEXT_LINK?}{LAST_LINK?} {LAST_LINK}{/LAST_LINK?});
3470 my $list = tag_attr_list($opt->{more_template} || $tpl, \%hash, 1);
3471 $r =~ s,$QR{more},$list,g;
3472 $r =~ s,$QR{matches},$m,g;
3473 $r =~ s,$QR{match_count},$q->{matches},g;
3487 my $LdD = qr{\s+([-\w:#/.]+)\]};
3488 my $LdI = qr{\s+([-\w:#/.]+)$Optr\]($Some)};
3500 sub tag_labeled_data_row {
3501 my ($key, $text) = @_;
3502 my ($row, $table, $tabRE);
3506 if(defined $Prefix) {
3509 $LdB = qr(\[$prefix[-_]data$Spacef)i;
3510 $LdIB = qr(\[if[-_]$prefix[-_]data(\d*)$Spacef(!?)(?:%20|\s)*)i;
3511 $LdIE = qr(\[/if[-_]$prefix[-_]data)i;
3512 $LdExpr = qr{ \[(?:$prefix[-_]data|if[-_]$prefix[-_]data(\d*))
3513 \s+ !?\s* ($Codere) \s
3514 (?!$All\[(?:$prefix[-_]data|if[-_]$prefix[-_]data\1)) }xi;
3519 #tag_labeled_data_row:
3529 while($$text =~ $LdExpr) {
3531 $tabRE = qr/$table/;
3532 $row = $Data_cache{"$table.$key"}
3533 || ( $Data_cache{"$table.$key"}
3534 = Vend::Data::database_row($table, $key)
3538 $$text =~ s#$LdIB$tabRE$LdI$LdIE\1\]#
3539 $row->{$3} ? pull_if($5,$2,$4,$row->{$3})
3540 : pull_else($5,$2,$4,$row->{$3})#ge
3542 #::logDebug("after if: table=$table 1=$1 2=$2 3=$3 $$text =~ s#$LdIB $tabRE $LdI $LdIE#");
3544 $$text =~ s/$LdB$tabRE$LdD/ed($row->{$1})/eg
3551 sub random_elements {
3552 my($ary, $wanted) = @_;
3553 return (0 .. $#$ary) unless $wanted > 0;
3554 $wanted = 1 if $wanted =~ /\D/;
3555 return undef unless ref $ary;
3559 my $count = scalar @$ary;
3560 $wanted = $count if $wanted > $count;
3561 for($j = 0; $j < $wanted; $j++) {
3562 my $cand = int rand($count);
3563 redo if $seen{$cand}++;
3575 my($opt, $text, $obj) = @_;
3577 $obj = $opt->{object} if ! $obj;
3578 return '' if ! $obj;
3580 my $ary = $obj->{mv_results};
3581 return '' if (! $ary or ! ref $ary or ! defined $ary->[0]);
3583 my $save_unsafe = $MVSAFE::Unsafe || '';
3584 $MVSAFE::Unsafe = 1;
3586 # This allows left brackets to be output by the data tags
3588 $Safe_data = 1 if $opt->{safe_data};
3590 # if($opt->{prefix} eq 'item') {
3591 #::logDebug("labeled list: opt:\n" . uneval($opt) . "\nobj:" . uneval($obj) . "text:" . substr($text,0,100));
3593 $Orig_prefix = $Prefix = $opt->{prefix} || 'item';
3595 $B = qr(\[$Prefix)i;
3596 $E = qr(\[/$Prefix)i;
3597 $IB = qr(\[if[-_]$Prefix)i;
3598 $IE = qr(\[/if[-_]$Prefix)i;
3602 if ( defined $CGI::values{mv_more_matches}
3603 and $CGI::values{mv_more_matches} eq 'loop' )
3605 undef $CGI::values{mv_more_matches};
3606 $opt->{fm} = $CGI::values{mv_next_pointer} + 1;
3607 $end = $CGI::values{mv_last_pointer}
3608 if defined $CGI::values{mv_last_pointer};
3609 $opt->{ml} = $CGI::values{mv_matchlimit}
3610 if defined $CGI::values{mv_matchlimit};
3612 # get the number to start the increment from
3614 if (defined $obj->{more_in_progress} and $obj->{mv_first_match}) {
3615 $i = $obj->{mv_first_match};
3617 elsif (defined $opt->{random} && !is_no($opt->{random})) {
3618 $opt->{random} = scalar(@$ary) if $opt->{random} =~ /^[yYtT]/;
3619 @$ary = @$ary[random_elements($ary, $opt->{random})];
3620 $i = 0; $end = $#$ary;
3621 undef $obj->{mv_matchlimit};
3623 elsif (defined $opt->{fm}) {
3624 $i = $opt->{fm} - 1;
3627 $count = $obj->{mv_first_match} || $i;
3629 # Zero the on-change hash
3632 if(defined $opt->{option}) {
3633 $opt_value = $opt->{option};
3634 my $optref = $opt->{cgi} ? (\%CGI::values) : $::Values;
3636 if($opt_value =~ s/\s*($Codere)::($Codere)\s*//) {
3639 $opt_value = lc($optref->{$opt_value}) || undef;
3641 return lc(tag_data($opt_table, $opt_field, shift)) eq $opt_value;
3645 elsif(defined $optref->{$opt_value} and length $optref->{$opt_value} ) {
3646 $opt_value = lc($optref->{$opt_value});
3647 $opt_select = ! $opt->{multiple}
3648 ? sub { return "\L$_[0]" eq $opt_value }
3649 : sub { $opt_value =~ /^$_[0](?:\0|$)/i or
3650 $opt_value =~ /\0$_[0](?:\0|$)/i
3659 if($Vend::OnlyProducts) {
3660 $text =~ s#$B$QR{_field}#[$Prefix-data $Vend::OnlyProducts $1]#g
3661 and $text =~ s#$E$QR{'/_field'}#[/$Prefix-data]#g;
3662 $text =~ s,$IB$QR{_field_if_wo},[if-$Prefix-data $1$Vend::OnlyProducts $2],g
3663 and $text =~ s,$IE$QR{'/_field'},[/if-$Prefix-data],g;
3665 #::logDebug("Past only products.");
3666 $end = ($obj->{mv_matchlimit} and $obj->{mv_matchlimit} > 0)
3667 ? $i + ($opt->{ml} || $obj->{mv_matchlimit}) - 1
3669 $end = $#$ary if $#$ary < $end;
3672 $text =~ /^\s*\[sort\s+.*/si
3673 and $opt->{sort} = find_sort(\$text);
3677 if($ary->[0] =~ /HASH/) {
3678 $ary = tag_sort_hash($opt->{sort}, $ary) if $opt->{sort};
3679 $r = iterate_hash_list($i, $end, $count, $text, $ary, $opt_select, $opt);
3682 my $fa = $obj->{mv_return_fields} || undef;
3683 my $fh = $obj->{mv_field_hash} || undef;
3684 my $fn = $obj->{mv_field_names} || undef;
3685 my $row_fields = $fa;
3686 $ary = tag_sort_ary($opt->{sort}, $ary) if $opt->{sort};
3691 @$row_fields = @{$fn}[@$fa];
3693 $fh->{$fn->[$_]} = $idx++;
3696 elsif (! $fh and $fn) {
3704 $opt->{mv_return_fields} = $fa;
3705 #::logDebug("Missing mv_field_hash and/or mv_field_names in Vend::Interpolate::labeled_list") unless ref $fh eq 'HASH';
3706 # Pass the field arrayref ($row_fields) for support in iterate_array_list of new $Row object...
3707 $r = iterate_array_list($i, $end, $count, $text, $ary, $opt_select, $fh, $opt, $row_fields);
3709 $MVSAFE::Unsafe = $save_unsafe;
3714 my ($body, $hash, $ucase) = @_;
3717 $hash = string_to_ref($hash);
3719 logDebug("eval error: $@");
3721 return undef if ! ref $hash;
3724 my $Marker = '[A-Z_]\\w+';
3725 $body =~ s!\{($Marker)\}!$hash->{"\L$1"}!g;
3726 $body =~ s!\{($Marker)\?($Marker)\:($Marker)\}!
3727 length($hash->{lc $1}) ? $hash->{lc $2} : $hash->{lc $3}
3729 $body =~ s!\{($Marker)\|($Some)\}!$hash->{lc $1} || $2!eg;
3730 $body =~ s!\{($Marker)\s+($Some)\}! $hash->{lc $1} ? $2 : ''!eg;
3731 1 while $body =~ s!\{($Marker)\?\}($Some){/\1\?\}! $hash->{lc $1} ? $2 : ''!eg;
3732 1 while $body =~ s!\{($Marker)\:\}($Some){/\1\:\}! $hash->{lc $1} ? '' : $2!eg;
3733 $body =~ s!\{(\w+)\:+(\w+)\:+(.*?)\}! tag_data($1, $2, $3) !eg;
3736 $body =~ s!\{($Codere)\}!$hash->{$1}!g;
3737 $body =~ s!\{($Codere)\?($Codere)\:($Codere)\}!
3738 length($hash->{$1}) ? $hash->{$2} : $hash->{$3}
3740 $body =~ s!\{($Codere)\|($Some)\}!$hash->{$1} || $2!eg;
3741 $body =~ s!\{($Codere)\s+($Some)\}! $hash->{$1} ? $2 : ''!eg;
3742 1 while $body =~ s!\{($Codere)\?\}($Some){/\1\?\}! $hash->{$1} ? $2 : ''!eg;
3743 1 while $body =~ s!\{($Codere)\:\}($Some){/\1\:\}! $hash->{$1} ? '' : $2!eg;
3744 $body =~ s!\{(\w+)\:+(\w+)\:+(.*?)\}! tag_data($1, $2, $3) !eg;
3750 my ($count, $item, $hash, $opt, $body) = @_;
3751 #::logDebug("in ship_address");
3752 return pull_else($body) if defined $opt->{if} and ! $opt->{if};
3753 return pull_else($body) if ! $Vend::username || ! $Vend::Session->{logged_in};
3754 #::logDebug("logged in with usernam=$Vend::username");
3756 my $tag = 'address';
3758 my $nattr = 'mv_an';
3760 if($opt->{billing}) {
3767 # if($item->{$attr} and ! $opt->{set}) {
3768 # my $pre = $opt->{prefix};
3769 # $pre =~ s/[-_]/[-_]/g;
3770 # $body =~ s:\[$pre\]($Some)\[/$pre\]:$item->{$attr}:g;
3771 # return pull_if($body);
3774 my $nick = $opt->{nick} || $opt->{nickname} || $item->{$nattr};
3776 #::logDebug("nick=$nick");
3779 if(not $user = $Vend::user_object) {
3780 $user = new Vend::UserDB username => ($opt->{username} || $Vend::username);
3782 #::logDebug("user=$user");
3783 ! $user and return pull_else($body);
3785 my $blob = $user->get_hash('SHIPPING') or return pull_else($body);
3786 #::logDebug("blob=$blob");
3787 my $addr = $blob->{$nick};
3790 %$addr = %{ $::Values };
3793 #::logDebug("addr=" . uneval($addr));
3795 $addr->{mv_an} = $nick;
3796 my @nick = sort keys %$blob;
3798 if($label = $opt->{address_label}) {
3799 @nick = sort { $blob->{$a}{$label} cmp $blob->{$a}{$label} } @nick;
3800 @nick = map { "$_=" . ($blob->{$_}{$label} || $_) } @nick;
3805 $opt->{blank} = '--select--' unless $opt->{blank};
3806 unshift(@nick, "=$opt->{blank}");
3807 $opt->{address_book} = join ",", @nick
3808 unless $opt->{address_book};
3810 my $joiner = get_joiner($opt->{joiner}, "<br$Vend::Xtrailer>");
3811 if(! $opt->{no_address}) {
3812 my @vals = map { $addr->{$_} }
3813 grep /^address_?\d*$/ && length($addr->{$_}), keys %$addr;
3814 $addr->{address} = join $joiner, @vals;
3817 if($opt->{widget}) {
3818 $addr->{address_book} = tag_accessories(
3822 attribute => $nattr,
3823 type => $opt->{widget},
3824 passed => $opt->{address_book},
3825 form => $opt->{form},
3831 if($opt->{set} || ! $item->{$attr}) {
3833 if($::Variable->{MV_SHIP_ADDRESS_TEMPLATE}) {
3834 $template .= $::Variable->{MV_SHIP_ADDRESS_TEMPLATE};
3837 $template .= "{company}\n" if $addr->{"${pre}company"};
3840 {city}, {state} {zip}
3841 {country} -- {phone_day}
3844 $template =~ s/{(\w+.*?)}/{$pre$1}/g if $pre;
3845 $addr->{mv_ad} = $item->{$attr} = tag_attr_list($template, $addr);
3848 $addr->{mv_ad} = $item->{$attr};
3851 if($opt->{textarea}) {
3852 $addr->{textarea} = tag_accessories(
3858 rows => $opt->{rows} || '4',
3859 cols => $opt->{cols} || '40',
3865 $body =~ s:\[$tag\]($Some)\[/$tag\]:tag_attr_list($1, $addr):eg;
3866 return pull_if($body);
3870 my ($count, $item, $hash, $opt, $body) = @_;
3871 my $param = delete $hash->{param}
3876 if(not $method = delete $hash->{method}) {
3877 $out = $item->{$param}->();
3880 $out = $item->{$param}->$method();
3886 my %Dispatch_hash = (
3887 address => \&tag_address,
3888 object => \&tag_object,
3891 sub find_matching_else {
3896 my $open = '[else]';
3897 my $close = '[/else]';
3901 $$buf =~ s{\[else\]}{[else]}igo;
3902 $first = index($$buf, $open);
3903 #::logDebug("first=$first");
3904 return undef if $first < 0;
3907 $$buf =~ s{\[/else\]}{[/else]}igo
3912 $begin = index($$buf, $open, $pos);
3913 $int = index($$buf, $close, $int + 1);
3916 $first = $int = $begin;
3919 #::logDebug("pos=$pos int=$int first=$first begin=$begin");
3921 $first = $begin if $begin > -1;
3922 substr($$buf, $first) =~ s/(.*)//s;
3924 substr($out, 0, 6) = '';
3929 my($tag, $count, $item, $hash, $chunk) = @_;
3932 my $full = lc "$Orig_prefix-tag-$tag";
3934 #::logDebug("tag_dispatch: tag=$tag count=$count chunk=$chunk");
3940 $eaten = Vend::Parse::_find_tag(\$chunk, $attrhash, $attrseq);
3941 substr($chunk, 0, 1) = '';
3943 $this_tag = Vend::Parse::find_matching_end($full, \$chunk);
3945 $attrhash->{prefix} = $tag unless $attrhash->{prefix};
3948 if(defined $Dispatch_hash{$tag}) {
3949 $out = $Dispatch_hash{$tag}->($count, $item, $hash, $attrhash, $this_tag);
3952 $attrhash->{body} = $this_tag unless defined $attrhash->{body};
3953 #::logDebug("calling tag tag=$tag this_tag=$this_tag attrhash=" . uneval($attrhash));
3954 $Tag ||= new Vend::Tags;
3955 $out = $Tag->$tag($attrhash);
3957 return $out . $chunk;
3962 sub resolve_nested_if {
3963 my ($where, $what) = @_;
3964 $where =~ s~\[$what\s+(?!.*\[$what\s)(.*?)\[/$what\]~
3965 '[' . $what . $rit . " $1" . '[/' . $what . $rit++ . ']'~seg;
3966 #::logDebug("resolved?\n$where\n");
3970 use vars qw/%Ary_code/;
3972 accessories => \&tag_accessories,
3973 common => \&Vend::Data::product_common,
3974 description => \&Vend::Data::product_description,
3975 field => \&Vend::Data::product_field,
3976 last => \&interpolate_html,
3977 next => \&interpolate_html,
3978 options => \&Vend::Options::tag_options,
3981 use vars qw/%Hash_code/;
3983 accessories => \&tag_accessories,
3984 common => \&Vend::Data::item_common,
3985 description => \&Vend::Data::item_description,
3986 field => \&Vend::Data::item_field,
3987 last => \&interpolate_html,
3988 next => \&interpolate_html,
3989 options => \&tag_options,
3992 sub map_list_routines {
3993 my($type, $opt) = @_;
3995 ### This allows mapping of new routines to
3997 ## PREFIX-accessories
3998 ## PREFIX-description
4009 for $ac ($Global::CodeDef->{$type}, $Vend::Cfg->{CodeDef}{$type}) {
4010 next unless $ac and $ac->{Routine};
4012 for(keys %{$ac->{Routine}}) {
4013 $nc->{$_} = $ac->{Routine}{$_};
4017 if($ac = $opt->{maproutine}) {
4020 $ac =~ s/[\s'",=>\0]+$//;
4021 $ac =~ s/^[\s'",=>\0]+//;
4022 $ac = { split /[\s'",=>\0]+/, $ac };
4024 $ac = {} if ref($ac) ne 'HASH';
4025 while( my($k,$v) = each %$ac) {
4026 $nc->{$k} = $Vend::Cfg->{Sub}{$v} || $Global::GlobalSub->{$v}
4028 logError("%s: non-existent mapped routine %s.", $type, $_);
4037 my ($count, $inc, $end, $page_start, $array_last) = @_;
4039 if(! length($inc)) {
4040 $inc ||= $::Values->{mv_item_alternate} || 2;
4043 return $count % $inc if $inc >= 1;
4046 if($inc == -1 or $inc eq 'except_last') {
4047 $status = 1 unless $count - 1 == $end;
4049 elsif($inc eq '0' or $inc eq 'first_only') {
4050 $status = 1 if $count == 1 || $count == ($page_start + 1);
4052 elsif($inc eq 'except_first') {
4053 $status = 1 unless $count == 1 || $count == ($page_start + 1);
4055 elsif($inc eq 'last_only') {
4056 $status = 1 if $count - 1 == $end;
4058 elsif($inc eq 'absolute_last') {
4059 $status = 1 if $count == $array_last;
4061 elsif($inc eq 'absolute_first') {
4062 $status = 1 if $count == 1;
4067 sub iterate_array_list {
4068 my ($i, $end, $count, $text, $ary, $opt_select, $fh, $opt, $fa) = @_;
4069 #::logDebug("passed opt=" . ::uneval($opt));
4070 my $page_start = $i;
4071 my $array_last = scalar @{$ary || []};
4075 # The $Row object needs to be built per-row, so undef it initially.
4077 @$fa = sort { $fh->{$a} <=> $fh->{$b} } keys %$fh
4078 if ! @$fa and ref $fh eq 'HASH';
4082 if($lim = $::Limit->{list_text_size} and length($text) > $lim) {
4083 my $len = length($text);
4084 my $caller = join "|", caller();
4085 my $msg = "Large list text encountered, length=$len, caller=$caller";
4087 return undef if $::Limit->{list_text_overflow} eq 'abort';
4090 # Optimize for no-match, on-match, etc
4091 if(! $opt->{iterator} and $text !~ /\[(?:if-)?$Prefix-/) {
4092 for(; $i <= $end; $i++) {
4098 my $nc = map_list_routines('ArrayCode', $opt);
4100 $nc and local(@Ary_code{keys %$nc}) = values %$nc;
4102 my ($run, $row, $code, $return);
4104 #::logDebug("iterating array $i to $end. count=$count opt_select=$opt_select ary=" . uneval($ary));
4111 $Data_cache{"/$filename"} or do {
4112 my $content = Vend::Util::readfile($filename);
4113 vars_and_comments(\$content);
4114 $Data_cache{"/$filename"} = $content;
4118 if($text =~ m/^$B$QR{_line}\s*$/is) {
4120 my $fa = $opt->{mv_return_fields};
4121 $r .= join "\t", @$fa[$i .. $#$fa];
4124 1 while $text =~ s#$IB$QR{_header_param_if}$IE[-_]header[-_]param\1\]#
4125 (defined $opt->{$3} ? $opt->{$3} : '')
4126 ? pull_if($5,$2,$4,$opt->{$3})
4127 : pull_else($5,$2,$4,$opt->{$3})#ige;
4128 $text =~ s#$B$QR{_header_param}#defined $opt->{$1} ? ed($opt->{$1}) : ''#ige;
4129 while($text =~ s#$B$QR{_sub}$E$QR{'/_sub'}##i) {
4133 ## $Vend::Cfg->{Sub}{''} = sub { errmsg('undefined sub') }
4134 ## unless defined $Vend::Cfg->{Sub}{''};
4135 $routine = 'sub { ' . $routine . ' }' unless $routine =~ /^\s*sub\s*{/;
4138 $sub = $ready_safe->reval($routine);
4141 logError( errmsg("syntax error on %s-sub %s]: $@", $B, $name) );
4142 $sub = sub { errmsg('ERROR') };
4144 #::logDebug("sub $name: $sub --> $routine");
4145 $Vend::Cfg->{Sub}{$name} = $sub;
4148 my $oexec = { %$opt };
4150 if($opt->{iterator}) {
4152 $sub = $opt->{iterator} if ref($opt->{iterator}) eq 'CODE';
4153 $sub ||= $Vend::Cfg->{Sub}{$opt->{iterator}}
4154 || $Global::GlobalSub->{$opt->{iterator}};
4157 "list iterator subroutine '%s' called but not defined. Skipping.",
4162 for( ; $i <= $end ; $i++ ) {
4163 $r .= $sub->($text, $ary->[$i], $oexec);
4168 1 while $text =~ s{(\[(if[-_]$Prefix[-_][a-zA-Z]+)(?=.*\[\2)\s.*\[/\2\])}
4170 resolve_nested_if($1, $2)
4173 # log helpful errors if any unknown field names are
4174 # used in if-prefix-param or prefix-param tags
4175 my @field_msg = ('error', "Unknown field name '%s' used in tag %s");
4177 if(! $opt->{ignore_undefined}) {
4178 $run =~ s#$B$QR{_param}# defined $fh->{$1} ||
4179 logOnce(@field_msg, $1, "$Orig_prefix-param") #ige;
4180 $run =~ s#$IB$QR{_param_if}# defined $fh->{$3} ||
4181 logOnce(@field_msg, $3, "if-$Orig_prefix-param") #ige;
4184 for( ; $i <= $end ; $i++, $count++ ) {
4186 last unless defined $row;
4189 #::logDebug("Doing $code substitution, count $count++");
4190 #::logDebug("Doing '" . substr($code, 0, index($code, "\n") + 1) . "' substitution, count $count++");
4193 $run =~ s#$B$QR{_alternate}$E$QR{'/_alternate'}#
4194 alternate($count, $1, $end, $page_start, $array_last)
4197 1 while $run =~ s#$IB$QR{_param_if}$IE[-_](?:param|modifier)\1\]#
4198 (defined $fh->{$3} ? $row->[$fh->{$3}] : '')
4199 ? pull_if($5,$2,$4,$row->[$fh->{$3}])
4200 : pull_else($5,$2,$4,$row->[$fh->{$3}])#ige;
4201 $run =~ s#$B$QR{_param}#defined $fh->{$1} ? ed($row->[$fh->{$1}]) : ''#ige;
4202 1 while $run =~ s#$IB$QR{_pos_if}$IE[-_]pos\1\]#
4204 ? pull_if($5,$2,$4,$row->[$3])
4205 : pull_else($5,$2,$4,$row->[$3])#ige;
4206 $run =~ s#$B$QR{_pos}#ed($row->[$1])#ige;
4207 #::logDebug("fh: " . uneval($fh) . uneval($row)) unless $once++;
4208 1 while $run =~ s#$IB$QR{_field_if}$IE[-_]field\1\]#
4209 my $tmp = product_field($3, $code);
4210 $tmp ? pull_if($5,$2,$4,$tmp)
4211 : pull_else($5,$2,$4,$tmp)#ige;
4212 $run =~ s:$B$QR{_line}:join "\t", @{$row}[ ($1 || 0) .. $#$row]:ige;
4213 $run =~ s:$B$QR{_increment}:$count:ig;
4214 $run =~ s:$B$QR{_accessories}:
4215 $Ary_code{accessories}->($code,$1,{}):ige;
4216 $run =~ s:$B$QR{_options}:
4217 $Ary_code{options}->($code,$1):ige;
4218 $run =~ s:$B$QR{_code}:$code:ig;
4219 $run =~ s:$B$QR{_description}:ed($Ary_code{description}->($code)):ige;
4220 $run =~ s:$B$QR{_field}:ed($Ary_code{field}->($1, $code)):ige;
4221 $run =~ s:$B$QR{_common}:ed($Ary_code{common}->($1, $code)):ige;
4222 tag_labeled_data_row($code, \$run);
4223 $run =~ s!$B$QR{_price}!
4224 currency(product_price($code,$1), $2)!ige;
4226 1 while $run =~ s!$B$QR{_change}$E$QR{'/_change'}\1\]!
4227 check_change($1,$3,undef,$2)
4229 : pull_else($4)!ige;
4230 $run =~ s#$B$QR{_tag}($Some$E[-_]tag[-_]\1\])#
4231 tag_dispatch($1,$count, $row, $ary, $2)#ige;
4232 $run =~ s#$B$QR{_calc}$E$QR{'/_calc'}#
4235 @{$Row}{@$fa} = @$row;
4239 $run =~ s#$B$QR{_exec}$E$QR{'/_exec'}#
4240 init_calc() if ! $Vend::Calc_initialized;
4242 $Vend::Cfg->{Sub}{$1} ||
4243 $Global::GlobalSub->{$1} ||
4244 sub { logOnce('error', "subroutine $1 missing for PREFIX-exec"); errmsg('ERROR') }
4247 $run =~ s#$B$QR{_filter}$E$QR{'/_filter'}#filter_value($1,$2)#ige;
4248 $run =~ s#$B$QR{_last}$E$QR{'/_last'}#
4249 my $tmp = $Ary_code{last}->($1);
4252 if($tmp && $tmp < 0) {
4259 $run =~ s#$B$QR{_next}$E$QR{'/_next'}#
4260 $Ary_code{next}->($1) != 0 ? (undef $Row, next) : '' #ixge;
4261 $run =~ s/<option\s*/<option SELECTED /i
4262 if $opt_select and $opt_select->($code);
4270 sub iterate_hash_list {
4271 my($i, $end, $count, $text, $hash, $opt_select, $opt) = @_;
4276 # Optimize for no-match, on-match, etc
4277 if(! $opt->{iterator} and $text !~ /\[/) {
4278 for(; $i <= $end; $i++) {
4284 my $code_field = $opt->{code_field} || 'mv_sku';
4285 my ($run, $code, $return, $item);
4287 my $nc = map_list_routines('HashCode', $opt);
4289 $nc and local(@Hash_code{keys %$nc}) = values %$nc;
4291 #::logDebug("iterating hash $i to $end. count=$count opt_select=$opt_select hash=" . uneval($hash));
4298 $Data_cache{"/$filename"} or do {
4299 my $content = Vend::Util::readfile($filename);
4300 vars_and_comments(\$content);
4301 $Data_cache{"/$filename"} = $content;
4305 1 while $text =~ s#$IB$QR{_header_param_if}$IE[-_]header[-_]param\1\]#
4306 (defined $opt->{$3} ? $opt->{$3} : '')
4307 ? pull_if($5,$2,$4,$opt->{$3})
4308 : pull_else($5,$2,$4,$opt->{$3})#ige;
4309 $text =~ s#$B$QR{_header_param}#defined $opt->{$1} ? ed($opt->{$1}) : ''#ige;
4310 while($text =~ s#$B$QR{_sub}$E$QR{'/_sub'}##i) {
4314 ## $Vend::Cfg->{Sub}{''} = sub { errmsg('undefined sub') }
4315 ## unless defined $Vend::Cfg->{Sub}{''};
4316 $routine = 'sub { ' . $routine . ' }' unless $routine =~ /^\s*sub\s*{/;
4319 $sub = $ready_safe->reval($routine);
4322 logError( errmsg("syntax error on %s-sub %s]: $@", $B, $name) );
4323 $sub = sub { errmsg('ERROR') };
4325 $Vend::Cfg->{Sub}{$name} = $sub;
4327 #::logDebug("subhidden: $opt->{subhidden}");
4329 my $oexec = { %$opt };
4331 if($opt->{iterator}) {
4333 $sub = $opt->{iterator} if ref($opt->{iterator}) eq 'CODE';
4334 $sub ||= $Vend::Cfg->{Sub}{$opt->{iterator}}
4335 || $Global::GlobalSub->{$opt->{iterator}};
4338 "list iterator subroutine '%s' called but not defined. Skipping.",
4344 for( ; $i <= $end ; $i++ ) {
4345 $r .= $sub->($text, $hash->[$i], $oexec);
4350 1 while $text =~ s{(\[(if[-_]$Prefix[-_][a-zA-Z]+)(?=.*\[\2)\s.*\[/\2\])}
4352 resolve_nested_if($1, $2)
4355 # undef the $Row object, as it should only be set as needed by [PREFIX-calc]
4358 for ( ; $i <= $end; $i++, $count++) {
4359 $item = $hash->[$i];
4360 $item->{mv_ip} = $opt->{reverse} ? ($end - $i) : $i;
4361 if($opt->{modular}) {
4362 if($opt->{master}) {
4363 next unless $item->{mv_mi} eq $opt->{master};
4365 if($item->{mv_mp} and $item->{mv_si} and ! $opt->{subitems}) {
4366 # $r .= <<EOF if $opt->{subhidden};
4367 #<INPUT TYPE="hidden" NAME="quantity$item->{mv_ip}" VALUE="$item->{quantity}">
4372 $item->{mv_cache_price} = undef;
4373 $code = $item->{$code_field} || $item->{code};
4374 $code = '' unless defined $code;
4376 #::logDebug("Doing $code (variant $item->{code}) substitution, count $count++");
4379 $run =~ s#$B$QR{_alternate}$E$QR{'/_alternate'}#
4380 alternate($i + 1, $1, $end)
4383 tag_labeled_data_row($code,\$run);
4384 $run =~ s:$B$QR{_line}:join "\t", @{$hash}:ge;
4385 1 while $run =~ s#$IB$QR{_param_if}$IE[-_](?:param|modifier)\1\]#
4386 $item->{$3} ? pull_if($5,$2,$4,$item->{$3})
4387 : pull_else($5,$2,$4,$item->{$3})#ige;
4388 1 while $run =~ s#$IB$QR{_parent_if}$IE[-_]parent\1\]#
4389 $item->{$3} ? pull_if($5,$2,$4,$opt->{$3})
4390 : pull_else($5,$2,$4,$opt->{$3})#ige;
4391 1 while $run =~ s#$IB$QR{_field_if}$IE[-_]field\1\]#
4392 my $tmp = item_field($item, $3);
4393 $tmp ? pull_if($5,$2,$4,$tmp)
4394 : pull_else($5,$2,$4,$tmp)#ge;
4395 $run =~ s:$B$QR{_increment}:$i + 1:ge;
4397 $run =~ s:$B$QR{_accessories}:
4398 $Hash_code{accessories}->($code,$1,{},$item):ge;
4399 $run =~ s:$B$QR{_options}:
4400 $Hash_code{options}->($item,$1):ige;
4401 $run =~ s:$B$QR{_sku}:$code:ig;
4402 $run =~ s:$B$QR{_code}:$item->{code}:ig;
4403 $run =~ s:$B$QR{_quantity}:$item->{quantity}:g;
4404 $run =~ s:$B$QR{_param}:ed($item->{$1}):ge;
4405 $run =~ s:$B$QR{_parent}:ed($opt->{$1}):ge;
4406 $run =~ s:$B$QR{_quantity_name}:quantity$item->{mv_ip}:g;
4407 $run =~ s:$B$QR{_modifier_name}:$1$item->{mv_ip}:g;
4408 $run =~ s!$B$QR{_subtotal}!currency(item_subtotal($item),$1)!ge;
4409 $run =~ s!$B$QR{_discount_subtotal}!
4410 currency( discount_subtotal($item), $1 )!ge;
4411 $run =~ s:$B$QR{_code}:$code:g;
4412 $run =~ s:$B$QR{_field}:ed($Hash_code{field}->($item, $1) || $item->{$1}):ge;
4413 $run =~ s:$B$QR{_common}:ed($Hash_code{common}->($item, $1) || $item->{$1}):ge;
4414 $run =~ s:$B$QR{_description}:
4415 ed($Hash_code{description}->($item) || $item->{description})
4417 $run =~ s!$B$QR{_price}!currency(item_price($item,$1), $2)!ge;
4418 $run =~ s!$B$QR{_discount_price}!
4420 discount_price($item, item_price($item,$1), $1 || 1)
4424 $run =~ s!$QR{discount_price}!
4426 discount_price($item, item_price($item,$1), $1 || 1)
4429 $run =~ s!$B$QR{_difference}!
4433 item_price($item, $item->{quantity}),
4439 $run =~ s!$B$QR{_discount}!
4443 item_price($item, $item->{quantity}),
4448 1 while $run =~ s!$B$QR{_change}$E$QR{'/_change'}\1\]!
4449 check_change($1,$3,undef,$2)
4451 : pull_else($4)!ige;
4452 $run =~ s#$B$QR{_tag}($All$E[-_]tag[-_]\1\])#
4453 tag_dispatch($1,$count, $item, $hash, $2)#ige;
4455 $run =~ s#$B$QR{_calc}$E$QR{'/_calc'}#tag_calc($1)#ige;
4456 $run =~ s#$B$QR{_exec}$E$QR{'/_exec'}#
4457 init_calc() if ! $Vend::Calc_initialized;
4459 $Vend::Cfg->{Sub}{$1} ||
4460 $Global::GlobalSub->{$1} ||
4462 )->($2,$item,$oexec)
4464 $run =~ s#$B$QR{_filter}$E$QR{'/_filter'}#filter_value($1,$2)#ige;
4465 $run =~ s#$B$QR{_last}$E$QR{'/_last'}#
4466 my $tmp = interpolate_html($1);
4467 if($tmp && $tmp < 0) {
4474 $run =~ s#$B$QR{_next}$E$QR{'/_next'}#
4475 interpolate_html($1) != 0 ? next : '' #oge;
4476 $run =~ s/<option\s*/<option SELECTED /i
4477 if $opt_select and $opt_select->($code);
4481 #::logDebug("item $code mv_cache_price: $item->{mv_cache_price}");
4482 delete $item->{mv_cache_price};
4490 my ($opt, @args) = @_;
4491 return undef unless ref $opt;
4492 my $msg = errmsg(@args);
4493 $msg = "$opt->{error_id}: $msg" if $opt->{error_id};
4494 if($opt->{log_error}) {
4497 return $msg if $opt->{show_error};
4505 my ($query, $opt, $text) = @_;
4506 $opt = {} if ! $opt;
4507 $opt->{prefix} = 'sql' unless $opt->{prefix};
4508 if($opt->{more} and $Vend::More_in_progress) {
4509 undef $Vend::More_in_progress;
4510 return region($opt, $text);
4512 $opt->{table} = $Vend::Cfg->{ProductFiles}[0]
4513 unless $opt->{table};
4514 my $db = $Vend::Database{$opt->{table}} ;
4515 return $opt->{failure} if ! $db;
4517 $opt->{query} = $query
4521 \[\Q$opt->{prefix}\E[_-]quote\](.*?)\[/\Q$opt->{prefix}\E[_-]quote\]
4526 if (! $opt->{wantarray} and ! defined $MVSAFE::Safe) {
4527 my $result = $db->query($opt, $text);
4528 return (ref $result) ? '' : $result;
4530 $db->query($opt, $text);
4534 my($opt, $ary, $na) = @_;
4537 $na = [ split /\s+/, $opt->{columns} ];
4542 my $delimiter = quotemeta $opt->{delimiter} || "\t";
4543 my $splittor = quotemeta $opt->{record_delim} || "\n";
4544 my (@rows) = split /$splittor/, $ary;
4545 $na = [ split /$delimiter/, shift @rows ] if $opt->{th};
4547 my $count = scalar @$na || -1;
4549 push @$ary, [split /$delimiter/, $_, $count];
4553 my ($tr, $td, $th, $fc, $fr) = @{$opt}{qw/tr td th fc fr/};
4555 for($tr, $td, $th, $fc, $fr) {
4556 next unless defined $_;
4561 $tr = '' if ! defined $tr;
4562 $td = '' if ! defined $td;
4563 if(! defined $th || $th and scalar @$na ) {
4564 $th = '' if ! defined $th;
4567 $r .= "<th$th><b>$_</b></th>";
4577 $val = (shift @$row) || ' ';
4578 $r .= "<td$fc>$val</td>";
4581 $val = $_ || ' ';
4582 $r .= "<td$td>$val</td>";
4587 foreach $row (@$ary) {
4591 $val = (shift @$row) || ' ';
4592 $r .= "<td$fc>$val</td>";
4595 $val = $_ || ' ';
4596 $r .= "<td$td>$val</td>";
4604 # Tests of above routines
4606 #print html_table( {
4607 # td => "BGCOLOR=#FFFFFF",
4610 # [qw/ data1a data2a data3a/],
4611 # [qw/ data1b data2b data3b/],
4612 # [qw/ data1c data2c data3c/],
4614 #[ qw/cell1 cell2 cell3/ ],
4617 #print html_table( {
4618 # td => "BGCOLOR=#FFFFFF",
4619 # columns => "cell1 cell2 cell3",
4621 #data1a data2a data3a
4622 #data1b data2b data3b
4623 #data1c data2c data3c
4629 my($text,$ary,$nh,$opt,$na) = @_;
4630 $opt = {} unless defined $opt;
4631 $opt->{prefix} = 'sql' if ! defined $opt->{prefix};
4632 $opt->{list_prefix} = 'sql[-_]list' if ! defined $opt->{prefix};
4636 mv_field_hash => $nh,
4637 mv_return_fields => $na,
4638 mv_more_id => $opt->{mv_more_id},
4639 matches => scalar @$ary,
4642 # Scans the option hash for more search settings if mv_more_alpha
4643 # is set in [query ...] tag....
4645 # Find the sort field and alpha options....
4646 Vend::Scan::parse_profile_ref($object, $opt);
4647 # We need to turn the hash reference into a search object
4648 $object = new Vend::Search (%$object);
4649 # Delete this so it will meet conditions for creating a more
4650 delete $object->{mv_matchlimit};
4653 $opt->{object} = $object;
4654 return region($opt, $text);
4658 # Displays a search page with the special [search-list] tag evaluated.
4662 my $new = { %$opt };
4663 my $out = iterate_hash_list(@_,[$new]);
4664 $Prefix = $Orig_prefix;
4670 my($opt,$page) = @_;
4674 if($opt->{object}) {
4675 ### The caller supplies the object, no search to be done
4676 $obj = $opt->{object};
4679 ### We need to run a search to get an object
4681 if($CGI::values{mv_more_matches} || $CGI::values{MM}) {
4683 ### It is a more function, we need to get the parameters
4684 find_search_params(\%CGI::values);
4685 delete $CGI::values{mv_more_matches};
4687 elsif ($opt->{search}) {
4688 ### Explicit search in tag parameter, run just like any
4689 if($opt->{more} and $::Instance->{SearchObject}{''}) {
4690 $obj = $::Instance->{SearchObject}{''};
4691 #::logDebug("cached search");
4694 $c = { mv_search_immediate => 1,
4695 mv_search_label => $opt->{label} || 'current',
4697 my $params = escape_scan($opt->{search});
4698 Vend::Scan::find_search_params($c, $params);
4699 $c->{mv_no_more} = ! $opt->{more};
4700 $obj = perform_search($c);
4704 ### See if we have a search already done for this label
4705 $obj = $::Instance->{SearchObject}{$opt->{label}};
4708 # If none of the above happen, we need to perform a search
4709 # based on the passed CGI parameters
4711 $obj = perform_search();
4715 mv_search_error => [ errmsg('No search was found') ],
4718 finish_search($obj);
4720 # Label it for future reference
4721 $::Instance->{SearchObject}{$opt->{label}} = $opt->{object} = $obj;
4726 if($opt->{list_prefix}) {
4727 $lprefix = $opt->{list_prefix};
4728 $mprefix = "(?:$opt->{list_prefix}-)?";
4730 elsif ($opt->{prefix}) {
4731 $lprefix = "(?:$opt->{prefix}-)?list";
4732 $mprefix = "(?:$opt->{prefix}-)?";
4739 #::logDebug("region: opt:\n" . uneval($opt) . "\npage:" . substr($page,0,100));
4741 if($opt->{ml} and ! defined $obj->{mv_matchlimit} ) {
4742 $obj->{mv_matchlimit} = $opt->{ml};
4743 $obj->{mv_more_decade} = $opt->{md};
4744 $obj->{matches} = scalar @{$obj->{mv_results}};
4745 $obj->{mv_cache_key} = generate_key($opt->{query} || substr($page,0,100));
4746 $obj->{mv_more_permanent} = $opt->{pm};
4747 $obj->{mv_first_match} = $opt->{fm} if $opt->{fm};
4748 $obj->{mv_search_page} = $opt->{sp} if $opt->{sp};
4749 $obj->{prefix} = $opt->{prefix} if $opt->{prefix};
4750 my $out = delete $obj->{mv_results};
4751 Vend::Search::save_more($obj, $out);
4752 $obj->{mv_results} = $out;
4755 $opt->{prefix} = $obj->{prefix} if $obj->{prefix};
4757 $Orig_prefix = $Prefix = $opt->{prefix} || 'item';
4759 $B = qr(\[$Prefix)i;
4760 $E = qr(\[/$Prefix)i;
4761 $IB = qr(\[if[-_]$Prefix)i;
4762 $IE = qr(\[/if[-_]$Prefix)i;
4766 \[ ( $mprefix more[-_]list ) $Optx$Optx$Optx$Optx$Optx \]
4770 tag_more_list($2,$3,$4,$5,$6,$opt,$7)
4773 \[ ( $mprefix on[-_]match )\]
4777 $obj->{matches} > 0 ? opt_region(0,0,1,$2,$opt) : ''
4780 \[ ( $mprefix no[-_]match )\]
4784 $obj->{matches} > 0 ? '' : opt_region(0,0,1,$2,$opt)
4787 $page =~ s:\[($lprefix)\]($Some)\[/\1\]:labeled_list($opt,$2,$obj):ige
4788 or $page = labeled_list($opt,$page,$obj);
4789 #::logDebug("past labeled_list");
4795 my ($list, $opt, $text) = @_;
4800 $opt->{prefix} ||= 'loop';
4801 $opt->{label} ||= "loop" . ++$::Instance->{List_it} . $Global::Variable->{MV_PAGE};
4803 #::logDebug("list is: " . uneval($list) );
4805 ## Thanks to Kaare Rasmussen for this suggestion
4806 ## about passing embedded Perl objects to a list
4808 # Can pass object.mv_results=$ary object.mv_field_names=$ary
4809 if ($opt->{object}) {
4810 my $obj = $opt->{object};
4811 # ensure that number of matches is always set
4812 # so [on-match] / [no-match] works
4813 $obj->{matches} = scalar(@{$obj->{mv_results}});
4814 return region($opt, $text);
4817 # Here we can take the direct results of an op like
4818 # @set = $db->query() && return \@set;
4820 # [loop list=`$Scratch->{ary}`] [loop-code]
4823 #::logDebug("opt->list in: " . uneval($list) );
4824 unless (ref $list eq 'ARRAY' and ref $list->[0] eq 'ARRAY') {
4825 logError("loop was passed invalid list=`...` argument");
4828 my ($ary, $fh, $fa) = @$list;
4829 my $obj = $opt->{object} ||= {};
4830 $obj->{mv_results} = $ary;
4831 $obj->{matches} = scalar @$ary;
4832 $obj->{mv_field_names} = $fa if $fa;
4833 $obj->{mv_field_hash} = $fh if $fh;
4835 $obj->{mv_matchlimit} = $opt->{ml};
4836 $obj->{mv_no_more} = ! $opt->{more};
4837 $obj->{mv_first_match} = $opt->{mv_first_match} || 0;
4838 $obj->{mv_next_pointer} = $opt->{mv_first_match} + $opt->{ml};
4840 return region($opt, $text);
4845 if($opt->{search}) {
4846 #::logDebug("loop resolve search");
4847 if($opt->{more} and $Vend::More_in_progress) {
4848 undef $Vend::More_in_progress;
4849 return region($opt, $text);
4852 return region($opt, $text);
4855 elsif ($opt->{file}) {
4856 #::logDebug("loop resolve file");
4857 $list = Vend::Util::readfile($opt->{file});
4858 $opt->{lr} = 1 unless
4862 elsif ($opt->{extended}) {
4866 my ($view, $tab, $key) = split /:+/, $opt->{extended}, 3;
4873 $id .= "::$key" if $key;
4874 my $meta = Vend::Table::Editor::meta_record(
4878 $opt->{extended_only},
4884 mv_field_names => [],
4890 mv_results => [ $meta ],
4893 return region($opt, $text);
4896 if ($fn = $opt->{fn} || $opt->{mv_field_names}) {
4897 $fn = [ grep /\S/, split /[\s,]+/, $fn ];
4901 #::logDebug("loop resolve line");
4905 $delim = $opt->{delimiter} || "\t";
4906 my $splittor = $opt->{record_delim} || "\n";
4907 if ($splittor eq "\n") {
4908 $list =~ s/\r\n/\n/g;
4912 @rows = map { [ split /\Q$delim/, $_ ] } split /\Q$splittor/, $list;
4916 elsif($opt->{acclist}) {
4917 #::logDebug("loop resolve acclist");
4918 $fn = [ qw/option label/ ] unless $fn;
4920 my @items = split /\s*,\s*/, $list;
4922 my ($o, $l) = split /=/, $_;
4923 $l = $o unless defined $l && $l =~ /\S/;
4924 push @rows, [ $o, $l ];
4927 #::logDebug("rows:" . uneval(\@rows));
4929 elsif($opt->{quoted}) {
4930 #::logDebug("loop resolve quoted");
4931 my @l = Text::ParseWords::shellwords($list);
4932 produce_range(\@l) if $opt->{ranges};
4934 @rows = map { [$_] } @l;
4938 #::logDebug("loop resolve default");
4939 $delim = $opt->{delimiter} || '[,\s]+';
4940 my @l = split /$delim/, $list;
4941 produce_range(\@l) if $opt->{ranges};
4943 @rows = map { [$_] } @l;
4948 logError("bad split delimiter in loop list: $@");
4949 #::logDebug("loop resolve error $@");
4952 # head_skip pulls rows off the top, and uses the last row to
4953 # set the field names if mv_field_names/fn option was not set
4954 if ($opt->{head_skip}) {
4957 $last_row = shift(@rows) while $i++ < $opt->{head_skip};
4962 matches => scalar(@rows),
4963 mv_results => \@rows,
4964 mv_field_names => $fn,
4967 #::logDebug("loop object: " . uneval($opt));
4968 return region($opt, $text);
4971 # Tries to display the on-the-fly page if page is missing
4973 my($code, $opt, $page) = @_;
4975 my ($selector, $subname, $base, $listref);
4977 return $page if (! $code and $Vend::Flypart eq $Vend::FinalPath);
4979 $code = $Vend::FinalPath
4982 $Vend::Flypart = $code;
4984 if ($subname = $Vend::Cfg->{SpecialSub}{flypage}) {
4985 my $sub = $Vend::Cfg->{Sub}{$subname} || $Global::GlobalSub->{$subname};
4986 $listref = $sub->($code);
4988 return unless defined $listref;
4996 $listref = { mv_results => [[$listref]] };
4997 $base = product_code_exists_ref($code);
5001 $listref = {mv_results => [[$code]]};
5002 $base = product_code_exists_ref($code);
5005 #::logDebug("fly_page: code=$code base=$base page=" . substr($page, 0, 100));
5006 return undef unless $base || $opt->{onfly};
5008 $base = $Vend::Cfg->{ProductFiles}[0] unless $base;
5011 $selector = 'passed in tag';
5013 elsif( $Vend::ForceFlypage ) {
5014 $selector = $Vend::ForceFlypage;
5015 undef $Vend::ForceFlypage;
5017 elsif( $selector = $Vend::Cfg->{PageSelectField}
5018 and db_column_exists($base,$selector)
5021 $selector = database_field($base, $code, $selector)
5024 $selector = find_special_page('flypage')
5026 #::logDebug("fly_page: selector=$selector");
5028 unless (defined $page) {
5029 unless( allowed_file($selector) ) {
5030 log_file_violation($selector, 'fly_page');
5033 $page = readin($selector);
5034 if (defined $page) {
5035 vars_and_comments(\$page);
5037 logError("attempt to display code=$code with bad flypage '$selector'");
5042 # This allows access from embedded Perl
5043 $Tmp->{flycode} = $code;
5045 $Vend::Track->view_product($code) if $Vend::Track;
5048 $opt->{prefix} ||= 'item';
5050 list_compat($opt->{prefix}, \$page) if $page;
5053 return labeled_list( $opt, $page, $listref);
5056 sub item_difference {
5057 my($code,$price,$q,$item) = @_;
5058 return $price - discount_price($item || $code,$price,$q);
5062 my($code,$price,$q) = @_;
5063 return ($price * $q) - discount_price($code,$price,$q) * $q;
5066 sub discount_subtotal {
5067 my ($item, $price) = @_;
5069 unless (ref $item) {
5070 ::logError("Bad call to discount price, item is not reference: %s", $item);
5074 my $quantity = $item->{quantity} || 1;
5076 $price ||= item_price($item);
5077 my $new_price = discount_price($item, $price);
5079 return $new_price * $quantity;
5082 sub discount_price {
5083 my ($item, $price, $quantity) = @_;
5087 unless (ref $item) {
5089 $item = { code => $code, quantity => ($quantity || 1) };
5093 ($code, $extra) = ($item->{code}, $item->{mv_discount});
5095 if ($extra and ! $::Discounts) {
5096 my $dspace = $Vend::DiscountSpaceName ||= 'main';
5097 $Vend::Session->{discount_space}{main}
5098 = $Vend::Session->{discount}
5099 ||= {} unless $Vend::Session->{discount_space}{main};
5101 = $Vend::Session->{discount}
5102 = $Vend::Session->{discount_space}{$dspace}
5103 ||= {} if $Vend::Cfg->{DiscountSpacesOn};
5106 return $price unless $extra or $::Discounts && %$::Discounts;
5108 $quantity = $item->{quantity};
5110 $Vend::Interpolate::item = $item;
5111 $Vend::Interpolate::q = $quantity || 1;
5112 $Vend::Interpolate::s = $price;
5114 my $subtotal = $price * $quantity;
5116 #::logDebug("quantity=$q code=$item->{code} price=$s");
5118 my ($discount, $return);
5120 for($code, 'ALL_ITEMS') {
5121 next unless $discount = $::Discounts->{$_};
5122 $Vend::Interpolate::s = $return ||= $subtotal;
5123 $return = $ready_safe->reval($discount);
5125 ::logError("Bad discount code for %s: %s", $discount, $@);
5126 $return = $subtotal;
5129 $price = $return / $q;
5134 $return = $ready_safe->reval($extra);
5142 sub apply_discount {
5145 my($formula, $cost);
5148 # Check for individual item discount
5149 push(@formulae, $::Discounts->{$item->{code}})
5150 if defined $::Discounts->{$item->{code}};
5151 # Check for all item discount
5152 push(@formulae, $::Discounts->{ALL_ITEMS})
5153 if defined $::Discounts->{ALL_ITEMS};
5154 push(@formulae, $item->{mv_discount})
5155 if defined $item->{mv_discount};
5157 my $subtotal = item_subtotal($item);
5159 init_calc() unless $Vend::Calc_initialized;
5160 # Calculate any formalas found
5161 foreach $formula (@formulae) {
5162 next unless $formula;
5163 $Vend::Interpolate::q = $item->{quantity};
5164 $Vend::Interpolate::s = $subtotal;
5165 $Vend::Interpolate::item = $item;
5166 # $formula =~ s/\$q\b/$item->{quantity}/g;
5167 # $formula =~ s/\$s\b/$subtotal/g;
5168 $cost = $ready_safe->reval($formula);
5171 "Discount for $item->{code} has bad formula. Not applied.\n$@";
5179 # Stubs for relocated shipping stuff in case of legacy code
5180 *read_shipping = \&Vend::Ship::read_shipping;
5181 *custom_shipping = \&Vend::Ship::shipping;
5182 *tag_shipping_desc = \&Vend::Ship::tag_shipping_desc;
5183 *shipping = \&Vend::Ship::shipping;
5184 *tag_handling = \&Vend::Ship::tag_handling;
5185 *tag_shipping = \&Vend::Ship::tag_shipping;
5186 *tag_ups = \&Vend::Ship::tag_ups;
5188 # Sets the value of a scratchpad field
5191 $::Scratch->{$var} = $val;
5195 # Sets the value of a temporary scratchpad field
5198 push @Vend::TmpScratch, $var;
5199 $::Scratch->{$var} = $val;
5208 if ($Vend::LockedOut) {
5212 elsif (defined $opt->{if}) {
5213 $abort = 1 if ! $opt->{if};
5218 $saved_file = $Vend::ScanPassed;
5219 $abort = 1 if ! $saved_file || $file =~ m:MM=:;
5222 $opt->{login} = 1 if $opt->{auto};
5225 if($opt->{new} and $Vend::new_session and !$Vend::Session->{logged_in}) {
5226 #::logDebug("we are new");
5227 $save_scratch = $::Scratch;
5229 $Vend::Session->{scratch} = { %{$Vend::Cfg->{ScratchDefault}}, mv_no_session_id => 1, mv_no_count => 1, mv_force_cache => 1 };
5233 return Vend::Interpolate::interpolate_html($_[0])
5235 or ( ! $opt->{force}
5238 or ! $opt->{login} && $Vend::Session->{logged_in}
5243 local ($Scratch->{mv_no_session_id});
5244 $Scratch->{mv_no_session_id} = 1;
5247 $opt->{minutes} = 60 unless defined $opt->{minutes};
5248 my $dir = "$Vend::Cfg->{ScratchDir}/auto-timed";
5249 unless (allowed_file($dir)) {
5250 log_file_violation($dir, 'timed_build');
5255 File::Path::mkpath($dir);
5257 $file = "$dir/" . generate_key(@_);
5262 last CHECKDIR if Vend::File::file_name_is_absolute($file);
5263 last CHECKDIR if $file and $file !~ m:/:;
5270 $file = $saved_file || $Vend::Flypart || $Global::Variable->{MV_PAGE};
5271 #::logDebug("static=$file");
5273 $file = $saved_file;
5274 $file =~ s:^scan/::;
5275 $file = generate_key($file);
5276 $file = "scan/$file";
5279 $saved_file = $file = ($Vend::Flypart || $Global::Variable->{MV_PAGE});
5281 $file .= $Vend::Cfg->{HTMLsuffix};
5284 if $file =~ s:(.*)/::;
5285 unless (allowed_file($dir)) {
5286 log_file_violation($dir, 'timed_build');
5291 File::Path::mkpath($dir);
5293 $file = Vend::Util::catfile($dir, $file);
5296 #::logDebug("saved=$saved_file");
5297 #::logDebug("file=$file exists=" . -f $file);
5298 if($opt->{minutes}) {
5299 $secs = int($opt->{minutes} * 60);
5301 elsif ($opt->{period}) {
5302 $secs = Vend::Config::time_to_seconds($opt->{period});
5305 $file = Vend::Util::escape_chars($file);
5306 if(! $opt->{auto} and ! allowed_file($file)) {
5307 log_file_violation($file, 'timed_build');
5311 if( ! -f $file or $secs && (stat(_))[9] < (time() - $secs) ) {
5312 my $out = Vend::Interpolate::interpolate_html(shift);
5313 $opt->{umask} = '22' unless defined $opt->{umask};
5314 Vend::Util::writefile(">$file", $out, $opt );
5315 $Vend::Session->{scratch} = $save_scratch if $save_scratch;
5318 $Vend::Session->{scratch} = $save_scratch if $save_scratch;
5319 return Vend::Util::readfile($file);
5323 my ($func, $opt) = @_;
5324 if($func eq 'quantity') {
5325 Vend::Order::update_quantity();
5327 elsif($func eq 'cart') {
5330 $cart = $::Carts->{$opt->{name}};
5333 $cart = $Vend::Items;
5335 return if ! ref $cart;
5336 Vend::Cart::toss_cart($cart, $opt->{name});
5338 elsif ($func eq 'process') {
5339 Vend::Dispatch::do_process();
5341 elsif ($func eq 'values') {
5342 Vend::Dispatch::update_user();
5344 elsif ($func eq 'data') {
5345 Vend::Data::update_data();
5353 $Vend::Session->{warnings} = [$Vend::Session->{warnings}]
5354 if ! ref $Vend::Session->{warnings};
5355 push @{$Vend::Session->{warnings}}, errmsg(@_);
5360 sub taxable_amount {
5361 my($cart, $dspace) = @_;
5363 return subtotal($cart || undef, $dspace || undef) unless $Vend::Cfg->{NonTaxableField};
5365 my ($taxable, $i, $code, $item, $quantity, $save, $oldspace);
5368 $save = $Vend::Items;
5372 # Support for discount namespaces.
5373 $oldspace = switch_discount_space($dspace) if $dspace;
5377 foreach $i (0 .. $#$Vend::Items) {
5378 $item = $Vend::Items->[$i];
5379 next if is_yes( $item->{mv_nontaxable} );
5380 next if is_yes( item_field($item, $Vend::Cfg->{NonTaxableField}) );
5381 if (%$::Discounts or $item->{mv_discount}) {
5382 $taxable += apply_discount($item);
5385 $taxable += item_subtotal($item);
5389 if (defined $::Discounts->{ENTIRE_ORDER}) {
5390 $Vend::Interpolate::q = tag_nitems();
5391 $Vend::Interpolate::s = $taxable;
5392 my $cost = $Vend::Interpolate::ready_safe->reval(
5393 $::Discounts->{ENTIRE_ORDER},
5397 "Discount ENTIRE_ORDER has bad formula. Returning normal subtotal.";
5403 $Vend::Items = $save if defined $save;
5405 # Restore initial discount namespace if appropriate.
5406 switch_discount_space($oldspace) if defined $oldspace;
5414 my ($area, $opt) = @_;
5416 if(my $country_check = $::Variable->{TAXCOUNTRY}) {
5417 $country_check =~ /\b$::Values->{country}\b/
5422 my $zone = $Vend::Cfg->{SalesTax};
5423 while($zone =~ m/(\w+)/g) {
5424 last if $area = $::Values->{$1};
5427 #::logDebug("flytax area=$area");
5428 return 0 unless $area;
5429 my $rates = $::Variable->{TAXRATE};
5430 my $taxable_shipping = $::Variable->{TAXSHIPPING} || '';
5431 my $taxable_handling = $::Variable->{TAXHANDLING} || '';
5436 my (@rates) = split /\s*,\s*/, $rates;
5439 my ($k,$v) = split /\s*=\s*/, $_, 2;
5440 next unless "\U$k" eq "\U$area";
5442 $rate = $rate / 100 if $rate > 1;
5445 #::logDebug("flytax rate=$rate");
5446 return 0 unless $rate;
5448 my ($oldcart, $oldspace);
5450 $oldcart = $Vend::Items;
5451 tag_cart($opt->{cart});
5453 if ($opt->{discount_space}) {
5454 $oldspace = switch_discount_space($opt->{discount_space});
5457 my $amount = taxable_amount();
5458 #::logDebug("flytax before shipping amount=$amount");
5459 $amount += tag_shipping()
5460 if $taxable_shipping =~ m{(^|[\s,])$area([\s,]|$)}i;
5461 $amount += tag_handling()
5462 if $taxable_handling =~ m{(^|[\s,])$area([\s,]|$)}i;
5464 $Vend::Items = $oldcart if defined $oldcart;
5465 switch_discount_space($oldspace) if defined $oldspace;
5467 #::logDebug("flytax amount=$amount return=" . $amount*$rate);
5468 return $amount * $rate;
5473 $rate =~ s/\s*%\s*$// and $rate /= 100;
5478 my($type, $opt) = @_;
5479 #::logDebug("entering VAT, opts=" . uneval($opt));
5480 my $cfield = $::Variable->{MV_COUNTRY_TAX_VAR} || 'country';
5481 my $country = $opt->{country} || $::Values->{$cfield};
5483 return 0 if ! $country;
5484 my $ctable = $opt->{country_table}
5485 || $::Variable->{MV_COUNTRY_TABLE}
5487 my $c_taxfield = $opt->{country_tax_field}
5488 || $::Variable->{MV_COUNTRY_TAX_FIELD}
5490 #::logDebug("ctable=$ctable c_taxfield=$c_taxfield country=$country");
5491 $type ||= tag_data($ctable, $c_taxfield, $country)
5493 #::logDebug("tax type=$type");
5499 if($type =~ /^(\w+)$/) {
5501 my $state = $opt->{state} || $::Values->{$sfield};
5502 return 0 if ! $state;
5503 my $stable = $opt->{state_table}
5504 || $::Variable->{MV_STATE_TABLE}
5506 my $s_taxfield = $opt->{state_tax_field}
5507 || $::Variable->{MV_STATE_TAX_FIELD}
5509 my $s_taxtype = $opt->{tax_type_field}
5510 || $::Variable->{MV_TAX_TYPE_FIELD}
5512 my $db = database_exists_ref($stable)
5515 if($opt->{tax_type}) {
5516 $addl = " AND $s_taxtype = " .
5517 $db->quote($opt->{tax_type}, $s_taxtype);
5520 SELECT $s_taxfield FROM $stable
5521 WHERE $cfield = '$country'
5522 AND $sfield = '$state'
5525 #::logDebug("tax state query=$q");
5528 $ary = $db->query($q);
5531 logError("error on state tax query %s", $q);
5533 #::logDebug("query returns " . uneval($ary));
5534 return 0 unless ref $ary;
5536 next unless $_->[0];
5537 push @taxes, $_->[0];
5545 foreach my $t (@taxes) {
5548 if ($t =~ /simple:(.*)/) {
5549 $total += fly_tax($::Values->{$1});
5552 elsif ($t =~ /handling:(.*)/) {
5553 my @modes = grep /\S/, split /[\s,]+/, $1;
5556 $cost += tag_handling($_) for @modes;
5561 #::logDebug("tax type=$t");
5562 if($t =~ /^(\d+(?:\.\d+)?)\s*(\%)$/) {
5565 $rate = $rate / (1 + $rate) if $Vend::Cfg->{TaxInclusive};
5566 my $amount = Vend::Interpolate::taxable_amount();
5567 $total += ($rate * $amount);
5570 $tax = Vend::Util::get_option_hash($t);
5572 #::logDebug("tax hash=" . uneval($tax));
5573 my $pfield = $opt->{tax_category_field}
5574 || $::Variable->{MV_TAX_CATEGORY_FIELD}
5576 my @pfield = split /:+/, $pfield;
5578 for my $item (@$Vend::Items) {
5581 ($tab, $col) = @pfield;
5584 $tab = $item->{mv_ib};
5587 my $cat = tag_data($tab, $col, $item->{code});
5588 my $rate = defined $tax->{$cat} ? $tax->{$cat} : $tax->{default};
5589 #::logDebug("item $item->{code} cat=$cat rate=$rate");
5590 $rate = percent_rate($rate);
5592 $rate = $rate / (1 + $rate) if $Vend::Cfg->{TaxInclusive};
5593 my $sub = discount_subtotal($item);
5594 #::logDebug("item $item->{code} subtotal=$sub");
5595 $total += $sub * $rate;
5596 #::logDebug("tax total=$total");
5599 my $tax_shipping_rate = 0;
5601 ## Add some tax on shipping ONLY IF TAXABLE ITEMS
5602 ## if rate for mv_shipping_when_taxable category is set
5603 if ($tax->{mv_shipping_when_taxable} and $total > 0) {
5604 $tax_shipping_rate += percent_rate($tax->{mv_shipping_when_taxable});
5607 ## Add some tax on shipping if rate for mv_shipping category is set
5608 if ($tax->{mv_shipping} > 0) {
5609 $tax_shipping_rate += percent_rate($tax->{mv_shipping});
5612 if($tax_shipping_rate > 0) {
5613 my $rate = $tax_shipping_rate;
5614 $rate =~ s/\s*%\s*$// and $rate /= 100;
5615 my $sub = tag_shipping() * $rate;
5616 #::logDebug("applying shipping tax rate of $rate, tax of $sub");
5620 ## Add some tax on handling if rate for mv_handling category is set
5621 if ($tax->{mv_handling} > 0) {
5622 my $rate = $tax->{mv_handling};
5623 $rate =~ s/\s*%\s*$// and $rate /= 100;
5624 $rate = $rate / (1 + $rate) if $Vend::Cfg->{TaxInclusive};
5625 my $sub = tag_handling() * $rate;
5626 #::logDebug("applying handling tax rate of $rate, tax of $sub");
5635 # Calculate the sales tax
5637 my($cart, $opt) = @_;
5641 my($save, $oldspace);
5642 ### If the user has assigned to salestax,
5643 ### we use their value come what may, no rounding
5644 if($Vend::Session->{assigned}) {
5645 return $Vend::Session->{assigned}{salestax}
5646 if defined $Vend::Session->{assigned}{salestax}
5647 && length( $Vend::Session->{assigned}{salestax});
5651 $save = $Vend::Items;
5655 $oldspace = switch_discount_space( $opt->{discount_space} ) if $opt->{discount_space};
5657 #::logDebug("salestax entered, cart=$cart");
5660 if($Vend::Cfg->{SalesTax} eq 'multi') {
5661 $cost = tax_vat($opt->{type}, $opt);
5663 elsif($Vend::Cfg->{SalesTax} =~ /\[/) {
5664 $cost = interpolate_html($Vend::Cfg->{SalesTax});
5666 elsif($Vend::Cfg->{SalesTaxFunction}) {
5667 $tax_hash = tag_calc($Vend::Cfg->{SalesTaxFunction});
5668 #::logDebug("found custom tax function: " . uneval($tax_hash));
5671 $tax_hash = $Vend::Cfg->{SalesTaxTable};
5672 #::logDebug("looking for tax function: " . uneval($tax_hash));
5675 # if we have a cost from previous routines, return it
5677 $Vend::Items = $save if $save;
5678 switch_discount_space($oldspace) if defined $oldspace;
5679 if($cost < 0 and $::Pragma->{no_negative_tax}) {
5682 return Vend::Util::round_to_frac_digits($cost);
5685 #::logDebug("got to tax function: " . uneval($tax_hash));
5686 my $amount = taxable_amount();
5687 # Restore the original discount namespace if appropriate; no other routines need the discount info.
5688 switch_discount_space($oldspace) if defined $oldspace;
5691 # Make it upper case for state and overseas postal
5692 # codes, zips don't matter
5693 my(@code) = map { (uc $::Values->{$_}) || '' }
5694 split /[,\s]+/, $Vend::Cfg->{SalesTax};
5695 push(@code, 'DEFAULT');
5697 $tax_hash = { DEFAULT => } if ! ref($tax_hash) =~ /HASH/;
5699 if(! defined $tax_hash->{DEFAULT}) {
5700 #::logDebug("Sales tax failed, no tax source, returning 0");
5705 last CHECKSHIPPING unless $Vend::Cfg->{TaxShipping};
5706 foreach $code (@code) {
5707 next unless $Vend::Cfg->{TaxShipping} =~ /\b\Q$code\E\b/i;
5708 $amount += tag_shipping();
5713 foreach $code (@code) {
5716 #::logDebug("salestax: check code '$code'");
5717 $code =~ s/(\d{5})-\d{4}/$1/;
5718 next unless defined $tax_hash->{$code};
5719 my $tax = $tax_hash->{$code};
5720 #::logDebug("salestax: found tax='$tax' for code='$code'");
5721 if($tax =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) {
5722 $r = $amount * $tax;
5725 $r = Vend::Data::chain_cost(
5726 { mv_price => $amount,
5728 quantity => $amount, }, $tax);
5730 #::logDebug("salestax: final tax='$r' for code='$code'");
5734 $Vend::Items = $save if defined $save;
5736 if($r < 0 and ! $::Pragma->{no_negative_tax}) {
5740 return Vend::Util::round_to_frac_digits($r);
5743 # Returns just subtotal of items ordered, with discounts
5746 my($cart, $dspace, $nodiscount) = @_;
5748 ### If the user has assigned to salestax,
5749 ### we use their value come what may, no rounding
5750 if($Vend::Session->{assigned}) {
5751 return $Vend::Session->{assigned}{subtotal}
5752 if defined $Vend::Session->{assigned}{subtotal}
5753 && length( $Vend::Session->{assigned}{subtotal});
5756 my ($save, $subtotal, $i, $item, $cost, $formula, $oldspace);
5758 $save = $Vend::Items;
5762 levies() unless $Vend::Levying;
5767 foreach $i (0 .. $#$Vend::Items) {
5768 $item = $Vend::Items->[$i];
5769 $subtotal += Vend::Data::item_subtotal($item);
5773 # Use switch_discount_space unconditionally to guarantee existance of proper discount structures.
5774 $oldspace = switch_discount_space($dspace || $Vend::DiscountSpaceName);
5776 my $discount = (ref($::Discounts) eq 'HASH' and %$::Discounts);
5778 foreach $i (0 .. $#$Vend::Items) {
5779 $item = $Vend::Items->[$i];
5780 if ($discount || $item->{mv_discount}) {
5781 $subtotal += apply_discount($item);
5783 $subtotal += Vend::Data::item_subtotal($item);
5787 if (defined $::Discounts->{ENTIRE_ORDER}) {
5788 $formula = $::Discounts->{ENTIRE_ORDER};
5789 $formula =~ s/\$q\b/tag_nitems()/eg;
5790 $formula =~ s/\$s\b/$subtotal/g;
5791 $cost = $Vend::Interpolate::ready_safe->reval($formula);
5794 "Discount ENTIRE_ORDER has bad formula. Returning normal subtotal.\n$@";
5800 $Vend::Session->{latest_subtotal} = $subtotal;
5802 # Switch to original discount space if an actual switch occured.
5803 switch_discount_space($oldspace) if $dspace and defined $oldspace;
5806 $Vend::Items = $save if defined $save;
5813 # Returns the total cost of items ordered.
5816 my ($cart, $dspace) = @_;
5817 my ($total, $i, $save, $oldspace);
5819 $oldspace = switch_discount_space($dspace) if $dspace;
5822 $save = $Vend::Items;
5828 if($Vend::Cfg->{Levies}) {
5829 $total = subtotal();
5834 $shipping += tag_shipping()
5835 if $::Values->{mv_shipmode};
5836 $shipping += tag_handling()
5837 if $::Values->{mv_handling};
5838 $total += subtotal();
5839 $total += $shipping;
5840 $total += salestax()
5841 unless $Vend::Cfg->{TaxInclusive};
5843 $Vend::Items = $save if defined $save;
5844 $Vend::Session->{latest_total} = $total;
5845 switch_discount_space($oldspace) if defined $oldspace;
5851 my ($set, $levies, $repos) = @_;
5853 $set ||= $Vend::CurrentCart || 'main';
5854 $levies ||= $Vend::Cfg->{Levies};
5855 $repos ||= $Vend::Cfg->{Levy_repository};
5857 my $icart = $Vend::Session->{carts}{$set} || [];
5861 push @sums, @{$_}{sort keys %$_};
5865 next unless $items = $repos->{$_}{check_status};
5866 push @sums, @{$::Values}{ split /[\s,\0]/, $items };
5868 return generate_key(@sums);
5872 my($recalc, $set, $opt) = @_;
5875 return unless $levies = $Vend::Cfg->{Levies};
5879 my $repos = $Vend::Cfg->{Levy_repository};
5880 #::logDebug("Calling levies, recalc=$recalc group=$opt->{group}");
5883 logOnce('error', "Levies set but no levies defined! No tax or shipping.");
5887 $set ||= $Vend::CurrentCart;
5890 $Vend::Session->{levies} ||= {};
5892 my $lcheck = $Vend::Session->{latest_levy} ||= {};
5893 $lcheck = $lcheck->{$set} ||= {};
5895 if($Vend::LeviedOnce and ! $recalc and ! $opt->{group} and $lcheck->{sum}) {
5896 my $newsum = levy_sum($set, $levies, $repos);
5897 #::logDebug("did levy check, new=$newsum old=$lcheck->{sum}");
5898 if($newsum eq $lcheck->{sum}) {
5899 undef $Vend::Levying;
5900 #::logDebug("levy returning cached value");
5901 return $lcheck->{total};
5905 my $lcart = $Vend::Session->{levies}{$set} = [];
5908 for my $name (@$levies) {
5909 my $l = $repos->{$name};
5910 #::logDebug("Levying $name, repos => " . uneval($l));
5912 logOnce('error', "Levy '%s' called but not defined. Skipping.", $name);
5915 if(my $if = $l->{include_if}) {
5916 if($if =~ /^\w+$/) {
5917 next unless $::Values->{$if};
5919 elsif($if =~ /__[A-Z]\w+__|[[a-zA-Z]/) {
5920 my $val = interpolate_html($if);
5926 next unless tag_calc($if);
5929 if(my $if = $l->{exclude_if}) {
5930 if($if =~ /^\w+$/) {
5931 next if $::Values->{$if};
5933 elsif($if =~ /__[A-Z]\w+__|[[a-zA-Z]/) {
5934 my $val = interpolate_html($if);
5940 next if tag_calc($if);
5943 my $type = $l->{type} || ($name eq 'salestax' ? 'salestax' : 'shipping');
5946 if($l->{mode_from_values}) {
5947 $mode = $::Values->{$l->{mode_from_values}};
5949 elsif($l->{mode_from_scratch}) {
5950 $mode = $::Scratch->{$l->{mode_from_scratch}};
5953 $mode ||= ($l->{mode} || $name);
5954 my $group = $l->{group} || $type;
5958 my $lab_field = $l->{label_value};
5959 if($type eq 'salestax') {
5961 $sort = $l->{sort} || '010';
5962 $lab_field ||= $Vend::Cfg->{SalesTax};
5963 if($l->{tax_fields}) {
5964 $save = $Vend::Cfg->{SalesTax};
5965 $Vend::Cfg->{SalesTax} = $l->{tax_fields};
5967 elsif ($l->{multi}) {
5968 $save = $Vend::Cfg->{SalesTax};
5969 $Vend::Cfg->{SalesTax} = 'multi';
5971 $cost = salestax(undef, { tax_type => $l->{tax_type} } );
5972 $l->{description} ||= 'Sales Tax';
5973 $Vend::Cfg->{SalesTax} = $save if defined $save;
5975 elsif ($type eq 'shipping' or $type eq 'handling') {
5976 if(not $sort = $l->{sort}) {
5977 $sort = $type eq 'handling' ? 100 : 500;
5981 my @modes = split /\0/, $mode;
5982 for my $m (@modes) {
5983 $cost += shipping($m);
5984 if($l->{description}) {
5985 if($l->{multi_description}) {
5986 $l->{description} = $l->{multi_description};
5989 $l->{description} .= ', ' if $l->{description};
5990 $l->{description} .= tag_shipping_desc($m);
5994 $l->{description} = tag_shipping_desc($m);
5998 elsif($type eq 'custom') {
6001 $sub = $Vend::Cfg->{Sub}{$mode} || $Global::GlobalSub->{$mode}
6004 $sub = $Vend::Cfg->{UserTag}{Routine}{$mode};
6006 last SUBFIND if ! $@ and $sub;
6008 $sub = $Global::UserTag->{Routine}{$mode};
6011 if( ref($sub) eq 'CODE') {
6012 ($cost, $desc, $sort) = $sub->($l);
6015 logError("No subroutine found for custom levy '%s'", $name);
6021 $::Values->{$lab_field},
6030 sort => $sort || $l->{sort},
6031 cost => round_to_frac_digits($cost),
6032 currency => currency($cost),
6034 inclusive => $l->{inclusive},
6035 label => $l->{label} || $desc,
6036 part_number => $l->{part_number},
6037 description => $desc,
6040 next unless $l->{keep_if_zero};
6042 $item->{free_message} = $l->{free_message} || $cost;
6045 if(my $target = $l->{add_to}) {
6047 foreach my $lev (@$lcart) {
6048 next unless $lev->{code} eq $target;
6049 $lev->{cost} += $item->{cost};
6050 $lev->{cost} = round_to_frac_digits($lev->{cost});
6051 $lev->{currency} = currency($lev->{cost});
6056 push @$lcart, $item;
6060 push @$lcart, $item;
6064 @$lcart = sort { $a->{sort} cmp $b->{sort} } @$lcart;
6067 next if $opt->{group} and $opt->{group} ne $_->{group};
6068 next if $_->{inclusive};
6069 next if $_->{type} eq 'salestax' and $Vend::Cfg->{TaxInclusive};
6073 $run = round_to_frac_digits($run);
6074 if(! $opt->{group}) {
6075 $lcheck = $Vend::Session->{latest_levy}{$set} = {};
6076 $lcheck->{sum} = levy_sum($set, $levies, $repos);
6077 $lcheck->{total} = $run;
6078 $Vend::LeviedOnce = 1;
6081 undef $Vend::Levying;