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)) {