MiniVend 3.15-beta3 minivend-3.15-beta3
Mike Heins [Tue, 10 Aug 1999 05:00:00 +0000 (00:00 -0500)]
27 files changed:
MANIFEST
dist/WHATSNEW
dist/bin/localize
dist/bin/minivend
dist/form_mail.cfg
dist/minivend.cfg.dist
dist/simple/catalog.cfg
dist/simple/etc/receipt.html
dist/simple/pages/about.html
dist/simple/pages/flypage.html
dist/simple/pages/privacy.html
dist/userdb.cfg [deleted file]
dist/usertag/bar_button [new file with mode: 0644]
dist/usertag/db_count [new file with mode: 0644]
dist/usertag/db_date [new file with mode: 0644]
dist/usertag/email [new file with mode: 0644]
dist/usertag/loc [new file with mode: 0644]
dist/usertag/summary [new file with mode: 0644]
dist/usertag/var [new file with mode: 0644]
doc/Tagref.pm [new file with mode: 0644]
lib/Vend/Config.pm
lib/Vend/ECML.pm
lib/Vend/Interpolate.pm
lib/Vend/Parse.pm
lib/Vend/Scan.pm
lib/Vend/Server.pm
lib/Vend/Util.pm

index 54e4e82..a29c43c 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -278,7 +278,14 @@ dist/src/tlink.c
 dist/src/tlink.pl
 dist/src/vlink.c
 dist/src/vlink.pl
-dist/userdb.cfg
+dist/usertag/bar_button
+dist/usertag/db_count
+dist/usertag/db_date
+dist/usertag/email
+dist/usertag/loc
+dist/usertag/summary
+dist/usertag/var
+doc/Tagref.pm
 doc/bullet.gif
 doc/frtoc.html
 doc/intro.html
index c7f1d2f..c7bf186 100644 (file)
@@ -142,6 +142,22 @@ MiniVend 3.15 incorporates some new features -- I couldn't resist. 8-)
 
       in the demo, this will demonstrate it.
 
+    * The idiom:
+        
+         #include directory/*
+
+      now is supported (and used in the demo minivend.cfg file). This
+      includes all files in that directory (but not subdirectories)
+      and allows you to include the whole directory. It should make
+      maintaining multiple servers a bit easier.
+
+    * Made some minor changes in the demo:
+
+        - updated flypage to show attribute-based price tag
+        - added [email to=addr from=addr subject=subj] message [/email]
+          UserTag which is better then the form_mail GlobalSub. It will
+          allow $Tag->email() to be used, among other things.
+
 MiniVend 3.14 is a bug fix release with a couple of new features:
 
     * You can now specify a database in minivend.cfg. It becomes
index 2725576..b5c8355 100644 (file)
@@ -2,7 +2,7 @@
 #
 # MiniVend localizer
 #
-# $Id: localize,v 1.3 1999/08/09 02:32:21 mike Exp mike $
+# $Id: localize,v 1.3 1999/08/09 02:32:21 mike Exp $
 #
 # Copyright 1996-1998 by Michael J. Heins <mikeh@minivend.com>
 #
index 27979f6..1260e37 100644 (file)
@@ -2,7 +2,7 @@
 #
 # MiniVend version 3.15
 #
-# $Id: minivend,v 1.46 1999/08/09 02:32:31 mike Exp mike $
+# $Id: minivend,v 1.48 1999/08/10 10:24:35 mike Exp mike $
 #
 # This program is largely based on Vend 0.2
 # Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
@@ -43,7 +43,7 @@ BEGIN {
 ($Global::VendRoot = $ENV{MINIVEND_ROOT})
        if defined $ENV{MINIVEND_ROOT};
        
-$Global::VendRoot = $Global::VendRoot || '/home/minivend';
+$Global::VendRoot = $Global::VendRoot || '/home/mike/mvend';
 $Global::ConfigFile = 'minivend.cfg';
 
 die "MiniVend not configured, no $Global::ConfigFile.\n"
@@ -151,7 +151,7 @@ sub debug {
 }
 
 BEGIN {
-       $VERSION = '3.15beta2';
+       $VERSION = '3.15beta3';
 }
 use strict;
 use Fcntl;
@@ -669,12 +669,11 @@ sub do_scan {
 }
 
 sub fake_scan {
-
-       my($argument,$path) = @_;
+       my($path) = @_;
        my ($key,$page);
        my $c = {};
        find_search_params($c,$path);
-       return perform_search($c,$argument);
+       return perform_search($c);
 }
 
 # Returns undef if interaction error
@@ -1562,9 +1561,10 @@ $Global::DEBUG = $Global::DebugMode || 0;
 # STATICPAGE
        READSTATIC: {
                        my $basedir = $c->{PageDir};
-                       last READSTATIC unless -f "$basedir/.build";
+                       last READSTATIC if ! -f "$basedir/.static";
                        if ($c->{Static}) {
                                print "loading static page names...";
+                               last READSTATIC if $c->{StaticDBM};
                                open STATICPAGE, "$basedir/.static"
                                        or warn <<EOF;
 Couldn't read static page status file $basedir/.static: $!
@@ -1730,7 +1730,6 @@ sub build_page {
        my $status = 1;
 
        Vend::Interpolate::reset_calc();
-
   eval {
        unless($scan) {
                $page = readin($name);
@@ -1768,7 +1767,7 @@ sub build_page {
                        my $count = 0;
                        my($search, $file, $newpage);
                        my $string = $Vend::Cfg->{VendURL} . "/scan/" ;
-                       while($page =~ s!$string([^?]+)[^"]+!"__POST_" . $count++ . "__"!e) {
+                       while($page =~ s!$string([^?"]+)[^"]*"!"__POST_" . $count++ . "__" . '"'!e) {
                                undef $Vend::CachePage;
                                undef $Vend::ForceBuild;
                                $search = $1;
@@ -1779,7 +1778,7 @@ sub build_page {
                                        push @post, $Vend::Found_scan{$search};
                                        print "cached.";
                                }
-                               elsif ($newpage = fake_scan('', $search) ) {
+                               elsif ($newpage = fake_scan($search) ) {
                                        $file = "scan" . ++$Vend::ScanCount .
                                                                        $Vend::Cfg->{StaticSuffix};
                                        pop @post;
@@ -1834,11 +1833,41 @@ sub build_page {
                $status = 0;
        }
   };
-       $status = 0 if $@;
+       if($@) {
+               $status = 0;
+               ::logError("build_page died: $@");
+       }
        return $status;
 
 }
 
+sub tie_static_dbm {
+       if($Global::GDBM) {
+               tie(%Vend::StaticDBM,
+                       'GDBM_File',
+                       "$Vend::Cfg->{StaticDBM}.gdbm",
+                       &GDBM_WRCREAT,
+                       $Vend::Cfg->{'FileCreationMask'},
+               )
+               or undef $Vend::Cfg->{StaticDBM};
+       }
+       elsif ($Global::DB_File) {
+               tie(%Vend::StaticDBM,
+                       'DB_File',
+                       "$Vend::Cfg->{StaticDBM}.db",
+                       &O_RDWR | &O_CREAT,
+                       $Vend::Cfg->{'FileCreationMask'},
+                       )
+               or undef $Vend::Cfg->{StaticDBM};
+       }
+       else {
+               undef $Vend::Cfg->{StaticDBM};
+       }
+       ::logError("Failed to create StaticDBM $Vend::Cfg->{StaticDBM}")
+               unless $Vend::Cfg->{StaticDBM} || shift;
+       return $Vend::Cfg->{StaticDBM} ? '1' : undef;
+}
+
 
 # Build a static page tree from the database
 # The session is faked, but all other operations
@@ -1961,6 +1990,12 @@ sub build_all {
        $Vend::SessionID = '';
        $Vend::SessionName = '';
        init_session();
+       if($Vend::Cfg->{StaticDBM}) {
+               for(glob("$Vend::Cfg->{StaticDBM}.gdbm $Vend::Cfg->{StaticDBM}.db")) {
+                       unlink $_;
+               }
+       }
+       unlink "$basedir/.static" if $Vend::Cfg->{StaticDBM};
     $CGI::cookie = $Vend::Cookie = "MV_SESSION_ID=building:local.host";
        $Vend::Session->{'frames'} = $Vend::Cfg->{FramesDefault};
        require File::Find or die "No standard Perl library File::Find!\n";
@@ -2032,8 +2067,8 @@ sub build_all {
                last FLYCHECK unless $Vend::Cfg->{StaticFly};
                last FLYCHECK if @build_file;
                my $save = $Vend::Session->{frames};
-         for (@Vend::Productbase) {
-               $p = database_ref($_);
+         foreach $p (@Vend::Productbase) {
+               $p = database_ref($p);
                while( ($key,$val) = $p->each_record() ) {
                        next if $build_list && ! defined $build{$key};
                        next unless $key =~ m{^$spec}o;
@@ -2071,8 +2106,8 @@ sub build_all {
          last FLY unless $Vend::Cfg->{StaticFly};
          last FLY if @build_file;
          my $save = $Vend::Session->{frames};
-         for (@Vend::Productbase) {
-               $p = database_ref($_);
+         foreach $p (@Vend::Productbase) {
+               $p = database_ref($p);
                while( ($key,$val) = $p->each_record() ) {
                        FRAMES:
             if($Vend::Cfg->{FrameFlyPage}) {
@@ -2088,7 +2123,7 @@ sub build_all {
                        next unless defined $Vend::Cfg->{StaticPage}->{$key};
                        print do_msg("Building part number $key");
                        build_page($key,$outdir)
-                               or (delete($Vend::Cfg->{StaticPage}->{$key}), next);
+                               or ( print "skipped.\n" and delete($Vend::Cfg->{StaticPage}->{$key}), next);
                        $Vend::Session->{'pageCount'} = -1;
                        print "done.\n";
                }
@@ -2097,8 +2132,11 @@ sub build_all {
        }
        open STATICPAGE, ">$basedir/.static"
                or die "Couldn't write static page file: $!\n";
+       tie_static_dbm if $Vend::Cfg->{StaticDBM};
        for(sort keys %{$Vend::Cfg->{StaticPage}}) {
                print STATICPAGE "$_\t$Vend::Cfg->{StaticPage}{$_}\n";
+               $Vend::StaticDBM{$_} = $Vend::Cfg->{StaticPage}{$_}
+                        if defined %Vend::StaticDBM;
        }
        close STATICPAGE;
 
@@ -2109,6 +2147,7 @@ sub build_all {
        }
        close UNBUILT;
        unlink "$basedir/.build" if @build_file;
+       unlink "$basedir/.static" if $Vend::Cfg->{StaticDBM};
 
        # Second level of search, no more than 1 is recommended, but
        # can be set by StaticDepth
@@ -2311,13 +2350,19 @@ sub dispatch {
                $Global::DEBUG |= $Vend::Cfg->{DebugMode};
        }
        if ($Vend::Cfg->{SetGroup}) {
-               eval {$) = "$Vend::Cfg->{SetGroup} $Vend::Cfg->{SetGroup}"};
+               eval {
+                       $) = "$Vend::Cfg->{SetGroup} $Vend::Cfg->{SetGroup}";
+               };
                if ($@) {
                        my $msg = $@;
-#                      logGlobal("Can't set group to GID $Vend::Cfg->{SetGroup}: $msg");
-                       logGlobal( errmsg('bin/minivend:16', "Can't set group to GID %s: %s" , $Vend::Cfg->{SetGroup}, $msg) );
-#                      logError("Can't set group to GID $Vend::Cfg->{SetGroup}: $msg");
-                       logError( errmsg('bin/minivend:17', "Can't set group to GID %s: %s" , $Vend::Cfg->{SetGroup}, $msg) );
+                       logGlobal( errmsg('bin/minivend:16',
+                                                               "Can't set group to GID %s: %s" ,
+                                                               $Vend::Cfg->{SetGroup},
+                                                               $msg ) );
+                       logError( errmsg('bin/minivend:17',
+                                                               "Can't set group to GID %s: %s",
+                                                               $Vend::Cfg->{SetGroup},
+                                                               $msg ) );
                }
        }
 
@@ -2349,6 +2394,7 @@ EOF
        chdir $Vend::Cfg->{'VendRoot'} 
                or die "Couldn't change to $Vend::Cfg{'VendRoot'}: $!\n";
        set_file_permissions();
+       tie_static_dbm('nolog') if $Vend::Cfg->{StaticDBM};
        umask $Vend::Cfg->{'Umask'};
        open_database();
 
index 2cd053b..beded41 100644 (file)
@@ -7,6 +7,11 @@ sub form_mail {
 
     $reply = '' unless defined $reply;
     $reply = "Reply-to: $reply\n" if $reply;
+       if ($reply !~ /^from:/mi) {
+               my $from = $Vend::Cfg->{MailOrderTo};
+               $from =~ s/,.*//;
+               $reply = "From: $from\n$reply";
+       }
 
     $ok = 0;
     SEND: {
index b35e13f..18fd61b 100644 (file)
@@ -287,146 +287,12 @@ Variable TEST_VARIABLE Test of global variable OK.
 # Custom tag for every catalog served by this MiniVend.
 #
 # UserTag
-
-# [loc locale*] message [/loc]
-#
-# This tag is the equivalent of [L] ... [/L] localization, except
-# it works with contained tags
-#
-UserTag loc hasEndTag   1
-UserTag loc Interpolate 1
-UserTag loc Order locale
-UserTag loc Routine <<EOF
-sub {
-    my ($locale, $message) = @_;
-    return $message unless $Vend::Cfg->{Locale};
-    my $ref;
-    if($locale) {
-        return $message
-            unless defined $Vend::Cfg->{Locale_repository}{$locale};
-        $ref = $Vend::Cfg->{Locale_repository}{$locale}
-    }
-    else {
-        $ref = $Vend::Cfg->{Locale};
-    }
-    return defined $ref->{$message} ? $ref->{$message} : $message;
-}
-EOF
-
-# [var name=variablename global=1]
-#
-# This tag is the equivalent of __VARIABLE__ except that it will
-# works in other variables
-#
-UserTag var Interpolate 1
-UserTag var PosNumber 2
-UserTag var Order name global
-UserTag var Routine <<EOF
-sub {
-    $_[1] and return $Global::Variable->{shift @_};
-    return $Vend::Cfg->{Variable}{shift @_};
-}
-EOF
-
-# [summary  amount=n.nn
-#           name=label*
-#           hide=1*
-#           total=1*
-#           reset=1*
-#           format="%.2f"*
-#           currency=1* ]
-#
-# Calculates column totals (if used properly. 8-\)
 # 
-#
-UserTag summary Order amount name currency format total reset hide
-UserTag summary PosNumber 6
-UserTag summary Routine <<EOF
-use vars qw/%summary_hash/;
-sub {
-    my ($amount, $name, $currency, $format, $total, $reset, $hide) = @_;
-       unless ($name) {
-               $name = 'ONLY0000';
-               %summary_hash = () if Vend::Util::is_yes($reset);
-       }
-       else {
-               $summary_hash{$name} = 0 if Vend::Util::is_yes($reset);
-       }
-       $summary_hash{$name} += $amount if length $amount;
-       $amount = $summary_hash{$name} if Vend::Util::is_yes($total);
-       return '' if defined $hide && Vend::Util::is_yes($hide);
-       return sprintf $format, $amount if $format;
-    return Vend::Util::currency($amount) if $currency;
-    return $amount;
-}
-EOF
-
-
-
-
-# [db-date table format]
-#
-# This tag returns the last-modified time of a database table,
-# 'products' by default. Accepts a POSIX strftime value for
-# date format; uses '%A %d %b %Y' by default.
-#
-UserTag  db-date  Order table format
-UserTag  db-date  PosNumber 2
-UserTag  db-date  Routine <<EOF
-sub {
-    my ($format, $db) = @_;
-    $format = '%A %d %b %Y';
-
-       $db = 'products' unless $db;
-
-    my $mtime = (stat($Vend::Cfg->{Database}{'file'}))[9];
-    return POSIX::strftime($format, localtime($mtime));
-
-}
-EOF
-
-# [db-count table]
-#
-# This tag returns the number of records in a database table,
-# 'products' by default.
-#
-UserTag  db-count  Order table
-UserTag  db-count  PosNumber 1
-UserTag  db-count  Routine <<EOF
-sub {
-    my ($db) = @_;
-
-    $db = 'products' unless $db;
-
-    my $ref = Vend::Data::database_exists_ref($db)
-        or return "Bad table $db";
-    $ref = $ref->ref();
-    my $count;
-    while ($ref->each_record()) {
-        $count++;
-    }
-    return $count;
-}
-EOF
-
-UserTag bar-button Order page current
-UserTag bar-button PosNumber 2
-UserTag bar-button HasEndTag 1
-UserTag bar-button Routine   <<EOR
-sub {
-       use strict;
-       my ($page, $current, $html) = @_;
-       $current = $Global::Variable->{MV_PAGE}
-               if ! $current;
-       $html =~ s:\[selected\]([\000-\377]*)\[/selected]::i;
-       my $alt = $1;
-       return $html if $page ne $current;
-       return $alt;
-}
-EOR
-
-### The old user database stuff
-### DON'T ### include userdb.cfg
+# See the documentation or look in the directory "usertag"
+
+#### Now including individual usertag files
+
+#include usertag/*
 
 ### The minivend administration stuff
 #include admin/mv_admin.cfg
@@ -436,5 +302,3 @@ EOR
 
 #include flycat.cfg
 #include form_mail.cfg
-
-
index 06a5887..58a139b 100644 (file)
@@ -864,7 +864,7 @@ NewTags    Yes
 # the name is a directory, then no pages in that directory (or any below it) be
 # cached or built statically.
 #
-NoCache    ord special results new_account flypage
+NoCache    ord special results new_account flypage query reconfig rand rotate
 
 
 ########### NoImport
@@ -1332,7 +1332,7 @@ StaticAll   Yes
 # static pages. The user ID executing MiniVend must have write permission on
 # the directory (and all files within) if this is to work.
 #
-StaticDir   __CATDOCROOT__/pages
+StaticDir   __SAMPLEHTML__/pages
 
 ########### StaticFly
 ##
@@ -1363,7 +1363,7 @@ StaticFly   Yes
 # The path (relative to HTTP document root) which should be used in references
 # built by and referred to by the page-building capability of MiniVend.
 #
-StaticPath __CATDOCURL__/pages
+StaticPath __SAMPLEURL__/pages
 
 
 ########### StaticPattern
index c37a65c..5554e35 100644 (file)
@@ -105,14 +105,11 @@ Quan  Item No.    Description                             Price        Extension
 [include pages/buttonbar.html]
 </CENTER>
 </body> </html>
+<!-- Send a copy to [value email]:
 [if value email_copy]
-[set name=email_copy_sent interpolate=1][perl arg=sub interpolate=1]
-       form_mail(
-            q{[value email]},
-            q{Thank you for your order [value mv_order_number]!},
-            q{__COMPANY__ Customer Service <__ORDERS_TO__>},
-            <<'EndOfMail')
-
+[email to="[value email]"
+               subject="Thank you for your order [value mv_order_number]!"
+        from=|__COMPANY__ Customer Service <__ORDERS_TO__>| ]
 Dear __COMPANY__ customer,
 
 Thank you for your order #[value mv_order_number], it is being electronically
@@ -206,6 +203,5 @@ __CITY__
 __PHONE__
 __TOLLFREE__
 FAX: __FAX__
-
-EndOfMail
-[/perl][/set][/if]
+[/email]
+[/if] -->
index 82015de..4ca370a 100644 (file)
@@ -42,7 +42,7 @@ __LEFTSIDE__
        <P>
        Phone: __PHONE__<BR>
        Fax: __FAX__<BR>
-       [if type=explicit compare="__TOLLFREE__"]
+       [if type=explicit compare="q{__TOLLFREE__}"]
        Tollfree: __TOLLFREE__<BR>
        [/if]
        <P>
index 4a840d0..7e85028 100644 (file)
@@ -93,7 +93,7 @@ __LEFTSIDE__
                                 __HEADERBG__
                                 TEXT="__HEADERTEXT__">
                                 <FONT SIZE="+1"><B>Quick-order</B></FONT><BR>
-                                                               [loop arg="1 2 5 10 25"]
+                                                               [loop arg="1 5 10 25 100"]
                                                                        [if cgi prospective_price == [loop-code]]
                                                                        <B>[loop-code]</B>
                                                                        [elsif !value prospective_price]
index 66ed948..df9ebaf 100644 (file)
@@ -1,3 +1,4 @@
+[tag flag build][/tag]
 <HTML>
 <HEAD>
 <TITLE>[L]About Us[/L]</TITLE>
diff --git a/dist/userdb.cfg b/dist/userdb.cfg
deleted file mode 100644 (file)
index b4727df..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-GlobalSub <<EndOfSub
-sub userdb {
-       my($function, %options) = @_;
-print("userdb function $function\n") if $Global::DEBUG;
-       use Vend::UserDB;
-
-       my $status = 1;
-       my $user = new Vend::UserDB %options;
-       unless (defined $user) {
-               $Vend::Session->{failure} = "Unable to access user database.";
-               return undef;
-       }
-       elsif (! $function) {
-               $Vend::Session->{failure} = "No function called for userdb.";
-               return undef;
-       }
-
-print("Called userdb user=$user function=$function\n" .
-                               do {
-                                       my $out;
-                                       for(keys %options) {
-                                               $out .= "option $_=$options{$_}\n";
-                                       }
-                                       for(keys %{$user}) {
-                                               $out .= "user $_=$user->{$_}\n";
-                                       }
-       } ) if $Global::DEBUG;
-
-       if($function eq 'login') {
-               $Vend::Session->{logged_in} = 0;
-               $status = $user->login(%options) and $Vend::Session->{logged_in} = 1;
-       }
-       elsif($function eq 'new_account') {
-               $Vend::Session->{logged_in} = 0;
-               $status = $user->new_account(%options) and $Vend::Session->{logged_in} = 1;
-       }
-       elsif($function eq 'logout') {
-               $Vend::Session->{logged_in} = 0;
-               $Vend::Session->{'values'}{mv_password} = '';
-               $user->{MESSAGE} = 'Logged out.';
-       }
-       elsif (! $Vend::Session->{logged_in}) {
-               $Vend::Session->{failure} = "Not logged in.";
-               return undef;
-       }
-       elsif($function eq 'save') {
-               $status = $user->set_values();
-       }
-       else {
-               $status = $user->$function(%options);
-       }
-       
-       if($status) {
-               delete $Vend::Session->{failure};
-print("userdb success=$user->{ERROR}\n") if $Global::DEBUG;
-               $Vend::Session->{success} = $user->{MESSAGE} || '';
-       }
-       else {
-print("userdb failure=$user->{ERROR}\n") if $Global::DEBUG;
-               $Vend::Session->{failure} = $user->{ERROR};
-       }
-       return $status;
-}
-EndOfSub
-
diff --git a/dist/usertag/bar_button b/dist/usertag/bar_button
new file mode 100644 (file)
index 0000000..3686cb1
--- /dev/null
@@ -0,0 +1,16 @@
+UserTag bar-button Order page current
+UserTag bar-button PosNumber 2
+UserTag bar-button HasEndTag 1
+UserTag bar-button Routine   <<EOR
+sub {
+       use strict;
+       my ($page, $current, $html) = @_;
+       $current = $Global::Variable->{MV_PAGE}
+               if ! $current;
+       $html =~ s:\[selected\]([\000-\377]*)\[/selected]::i;
+       my $alt = $1;
+       return $html if $page ne $current;
+       return $alt;
+}
+EOR
+
diff --git a/dist/usertag/db_count b/dist/usertag/db_count
new file mode 100644 (file)
index 0000000..a6bcdac
--- /dev/null
@@ -0,0 +1,24 @@
+# [db-count table]
+#
+# This tag returns the number of records in a database table,
+# 'products' by default.
+#
+UserTag  db-count  Order table
+UserTag  db-count  PosNumber 1
+UserTag  db-count  Routine <<EOF
+sub {
+    my ($db) = @_;
+
+    $db = 'products' unless $db;
+
+    my $ref = Vend::Data::database_exists_ref($db)
+        or return "Bad table $db";
+    $ref = $ref->ref();
+    my $count;
+    while ($ref->each_record()) {
+        $count++;
+    }
+    return $count;
+}
+EOF
+
diff --git a/dist/usertag/db_date b/dist/usertag/db_date
new file mode 100644 (file)
index 0000000..748783c
--- /dev/null
@@ -0,0 +1,21 @@
+# [db-date table format]
+#
+# This tag returns the last-modified time of a database table,
+# 'products' by default. Accepts a POSIX strftime value for
+# date format; uses '%A %d %b %Y' by default.
+#
+UserTag  db-date  Order table format
+UserTag  db-date  PosNumber 2
+UserTag  db-date  Routine <<EOF
+sub {
+    my ($format, $db) = @_;
+    $format = '%A %d %b %Y';
+
+       $db = 'products' unless $db;
+
+    my $mtime = (stat($Vend::Cfg->{Database}{'file'}))[9];
+    return POSIX::strftime($format, localtime($mtime));
+
+}
+EOF
+
diff --git a/dist/usertag/email b/dist/usertag/email
new file mode 100644 (file)
index 0000000..325b84d
--- /dev/null
@@ -0,0 +1,45 @@
+UserTag email Order to subject reply from extra
+UserTag email hasEndTag
+UserTag email Interpolate
+UserTag email Routine <<EOR
+sub {
+    my($to, $subject, $reply, $from, $extra, $body) = @_;
+    my($ok);
+
+    $subject = '<no subject>' unless defined $subject && $subject;
+
+    $reply = '' unless defined $reply;
+    $reply = "Reply-to: $reply\n" if $reply;
+       if (! $from) {
+               $from = $Vend::Cfg->{MailOrderTo};
+               $from =~ s/,.*//;
+       }
+
+       $extra =~ s/\s*$/\n/ if $extra;
+    $ok = 0;
+    SEND: {
+        open(Vend::MAIL,"|$Vend::Cfg->{'SendMailProgram'} -t") or last SEND;
+        print Vend::MAIL
+                       "To: $to\n",
+                       "From: $from\n",
+                       $reply,
+                       $extra || '',
+                       "Subject: $subject\n\n",
+                       $body
+            or last SEND;
+        close Vend::MAIL or last SEND;
+        $ok = ($? == 0);
+    }
+
+    if (!$ok) {
+        logError("Unable to send mail using $Vend::Cfg->{'SendMailProgram'}\n" .
+            "To '$to'\n" .
+            "From '$from'\n" .
+            "With extra headers '$extra'\n" .
+            "With reply-to '$reply'\n" .
+            "With subject '$subject'\n" .
+            "And body:\n$body");
+    }
+    $ok;
+}
+EOR
diff --git a/dist/usertag/loc b/dist/usertag/loc
new file mode 100644 (file)
index 0000000..dac02f9
--- /dev/null
@@ -0,0 +1,25 @@
+# [loc locale*] message [/loc]
+#
+# This tag is the equivalent of [L] ... [/L] localization, except
+# it works with contained tags
+#
+UserTag loc hasEndTag   1
+UserTag loc Interpolate 1
+UserTag loc Order locale
+UserTag loc Routine <<EOF
+sub {
+    my ($locale, $message) = @_;
+    return $message unless $Vend::Cfg->{Locale};
+    my $ref;
+    if($locale) {
+        return $message
+            unless defined $Vend::Cfg->{Locale_repository}{$locale};
+        $ref = $Vend::Cfg->{Locale_repository}{$locale}
+    }
+    else {
+        $ref = $Vend::Cfg->{Locale};
+    }
+    return defined $ref->{$message} ? $ref->{$message} : $message;
+}
+EOF
+
diff --git a/dist/usertag/summary b/dist/usertag/summary
new file mode 100644 (file)
index 0000000..5c3e2d9
--- /dev/null
@@ -0,0 +1,33 @@
+# [summary  amount=n.nn
+#           name=label*
+#           hide=1*
+#           total=1*
+#           reset=1*
+#           format="%.2f"*
+#           currency=1* ]
+#
+# Calculates column totals (if used properly. 8-\)
+# 
+#
+UserTag summary Order amount name currency format total reset hide
+UserTag summary PosNumber 6
+UserTag summary Routine <<EOF
+use vars qw/%summary_hash/;
+sub {
+    my ($amount, $name, $currency, $format, $total, $reset, $hide) = @_;
+       unless ($name) {
+               $name = 'ONLY0000';
+               %summary_hash = () if Vend::Util::is_yes($reset);
+       }
+       else {
+               $summary_hash{$name} = 0 if Vend::Util::is_yes($reset);
+       }
+       $summary_hash{$name} += $amount if length $amount;
+       $amount = $summary_hash{$name} if Vend::Util::is_yes($total);
+       return '' if defined $hide && Vend::Util::is_yes($hide);
+       return sprintf $format, $amount if $format;
+    return Vend::Util::currency($amount) if $currency;
+    return $amount;
+}
+EOF
+
diff --git a/dist/usertag/var b/dist/usertag/var
new file mode 100644 (file)
index 0000000..552359c
--- /dev/null
@@ -0,0 +1,18 @@
+# [var name=variablename global=1]
+#
+# This tag is the equivalent of __VARIABLE__ except that it will
+# works in other variables
+#
+UserTag var Interpolate 1
+UserTag var PosNumber 2
+UserTag var Order name global
+UserTag var Routine <<EOR
+sub {
+    $_[1] and return $Global::Variable->{shift @_};
+    my $key = shift;
+       return $Vend::Cfg->{Member}{$key}
+               if      $Vend::Session->{logged_in}
+                       && defined $Vend::Cfg->{Member}{$key};
+       return $Vend::Cfg->{Variable}{$key};
+}
+EOR
diff --git a/doc/Tagref.pm b/doc/Tagref.pm
new file mode 100644 (file)
index 0000000..ed70188
--- /dev/null
@@ -0,0 +1,4812 @@
+# Parse.pm - Parse MiniVend tags
+# 
+# $Id: Parse.pm,v 1.48 1999/02/15 08:51:10 mike Exp mike $
+#
+# Copyright 1997-1999 by Michael J. Heins <mikeh@iac.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+package Vend::Parse;
+
+# $Id: Parse.pm,v 1.48 1999/02/15 08:51:10 mike Exp mike $
+
+require Vend::Parser;
+
+
+$VERSION = sprintf("%d.%02d", q$Revision: 1.48 $ =~ /(\d+)\.(\d+)/);
+
+use Safe;
+use Vend::Util;
+use Vend::Interpolate;
+use Text::ParseWords;
+# STATICPAGE
+use Vend::PageBuild;
+# END STATICPAGE
+use Vend::Data qw/product_field/;
+
+#require Exporter;
+
+@ISA = qw(Exporter Vend::Parser);
+
+$VERSION = substr(q$Revision: 1.48 $, 10);
+@EXPORT = ();
+@EXPORT_OK = qw(find_matching_end);
+
+use strict;
+
+use vars qw($VERSION);
+
+my($CurrentSearch, $CurrentCode, $CurrentDB, $CurrentWith, $CurrentItem);
+my(@SavedSearch, @SavedCode, @SavedDB, @SavedWith, @SavedItem);
+
+my %PosNumber =        ( qw!
+                    
+                accessories      2
+                area             2
+                areatarget       3
+                body             2
+                bounce           2
+                buttonbar        1
+                cart             1
+                cgi              1
+                checked          3
+                currency         1
+                data             6
+                default          2
+                discount         1
+                description      2
+                               ecml                     2
+                field            2
+                file             2
+                finish_order     1
+                fly_list         2
+                framebase        1
+                goto             2
+                help             1
+                if               1
+                import           2
+                include          1
+                input_filter     1
+                index            1
+                label            1
+                last_page        2
+                lookup           1
+                loop             1
+                mvasp            1
+                nitems           1
+                order            4
+                page             2
+                pagetarget       3
+                perl             1
+                price            4
+                process_order    2
+                process_search   1
+                process_target   2
+                rotate           2
+                row              1
+                salestax         2
+                scratch          1
+                search           1
+                search_region    1
+                selected         3
+                set              1
+                setlocale        3
+                shipping         3
+                shipping_desc    1
+                sql              2
+                subtotal         2
+                tag              1
+                total_cost       2
+                userdb           1
+                value            4
+                value_extended   1
+
+                       ! );
+
+my %Optional = (
+
+               
+       import          => [qw(continue separator)],
+               area                    => [qw(form)],
+
+               );
+
+my %Order =    (
+
+                               accessories             => [qw( code arg )],
+                               area                    => [qw( href arg secure)],
+                               areatarget              => [qw( href target arg secure)],
+                               body                    => [qw( type extra )],
+                               bounce                  => [qw( href if )],
+                               buttonbar               => [qw( type  )],
+                               calc                    => [],
+                               cart                    => [qw( name  )],
+                               cgi                             => [qw( name  )],
+                               compat                  => [],
+                               'currency'              => [qw( convert )],
+                               checked                 => [qw( name value multiple default)],
+                               data                    => [qw( table field key value increment append )],
+                               default                 => [qw( name default set)],
+                               description             => [qw( code base )],
+                               discount                => [qw( code  )],
+                               ecml                    => [qw( name function )],
+                               field                   => [qw( name code )],
+                               file                    => [qw( name type )],
+                               finish_order    => [qw( href )],
+                               fly_list                => [qw( code base )],
+                               framebase               => [qw( target  )],
+                               frames_off              => [],
+                               frames_on               => [],
+                               'goto'                  => [qw( name if)],
+                               help                    => [qw( name  )],
+                               'if'                    => [qw( type term op compare )],
+                               'or'                    => [qw( type term op compare )],
+                               'and'                   => [qw( type term op compare )],
+                               index                   => [qw( table )],
+                               import                  => [qw( table type )],
+                               input_filter    => [qw( name )],
+                               include                 => [qw( file )],
+                               item_list               => [qw( name )],
+                               label                   => [qw( name )],
+                               last_page               => [qw( target arg )],
+                               lookup                  => [qw( table field key value )],
+                               loop                    => [qw( with arg search option)],
+                               loop_change             => [qw( with arg )],
+                               nitems                  => [qw( name  )],
+                               order                   => [qw( code href base quantity )],
+                               page                    => [qw( href arg secure)],
+                               pagetarget              => [qw( href target arg secure)],
+                               perl                    => [qw( arg )],
+                               mvasp                   => [qw( tables )],
+                               post                    => [],
+                               price                   => [qw( code quantity base noformat)],
+                               process_order   => [qw( target secure )],
+                               process_search  => [qw( target )],
+                               process_target  => [qw( target secure )],
+                               random                  => [],
+                               read_cookie             => [qw( name )],
+                               rotate                  => [qw( ceiling floor )],
+                               row                             => [qw( width )],
+                               'salestax'              => [qw( name noformat)],
+                               scratch                 => [qw( name  )],
+                               search                  => [qw( arg   )],
+                               search_region   => [qw( arg   )],
+                               selected                => [qw( name value multiple )],
+                               set_cookie              => [qw( name value expire )],
+                               setlocale               => [qw( locale currency persist )],
+                               set                             => [qw( name  )],
+                               'shipping'              => [qw( name cart noformat )],
+                               shipping_desc   => [qw( name  )],
+                               sql                             => [qw( type query list false base)],
+                               strip                   => [],
+                               'subtotal'              => [qw( name noformat )],
+                               tag                             => [qw( op base file type )],
+                               total_cost              => [qw( name noformat )],
+                               userdb          => [qw( function ) ],
+                               value                   => [qw( name escaped set hide)],
+                               value_extended  => [qw( name )],
+
+                       );
+
+my %InvalidateCache = (
+
+                       qw(
+                               cgi                     1
+                               cart            1
+                               checked         1
+                               default         1
+                               discount        1
+                               frames_off      1
+                               frames_on       1
+                               item_list       1
+                               import          1
+                               index           1
+                               input_filter            1
+                               if          1
+                               last_page       1
+                               lookup          1
+                               mvasp           1
+                               nitems          1
+                               perl            1
+                               'salestax'      1
+                               scratch         1
+                               selected        1
+                               read_cookie 1
+                               set_cookie  1
+                               set                     1
+                               'shipping'      1
+                               sql                     1
+                               subtotal        1
+                               total_cost      1
+                               userdb          1
+                               value           1
+                               value_extended 1
+
+                          )
+                       );
+
+my %Implicit = (
+
+                       'data' =>               { qw( increment increment ) },
+                       'value' =>              { qw( escaped   escaped hide hide ) },
+                       'checked' =>    { qw( multiple  multiple default        default ) },
+                       'area'    =>    { qw( secure    secure ) },
+                       'page'    =>    { qw( secure    secure ) },
+                       'areatarget'    =>      { qw( secure    secure ) },
+                       'process_order' =>      { qw( secure    secure ) },
+                       'process_target' =>     { qw( secure    secure ) },
+                       'pagetarget'    =>      { qw( secure    secure ) },
+
+                       'if' =>         { qw(
+                                                               !=              op
+                                                               !~              op
+                                                               <=              op
+                                                               ==              op
+                                                               =~              op
+                                                               >=              op
+                                                               eq              op
+                                                               gt              op
+                                                               lt              op
+                                                               ne              op
+                                          )},
+
+                       'and' =>                { qw(
+                                                               !=              op
+                                                               !~              op
+                                                               <=              op
+                                                               ==              op
+                                                               =~              op
+                                                               >=              op
+                                                               eq              op
+                                                               gt              op
+                                                               lt              op
+                                                               ne              op
+                                          )},
+
+                       'or' =>         { qw(
+                                                               !=              op
+                                                               !~              op
+                                                               <=              op
+                                                               ==              op
+                                                               =~              op
+                                                               >=              op
+                                                               eq              op
+                                                               gt              op
+                                                               lt              op
+                                                               ne              op
+                                          )},
+
+                       );
+
+my %PosRoutineName = (
+                               'or'                    => q{sub { return &Vend::Interpolate::tag_if(@_, 1) }},
+                               'and'                   => q{sub { return &Vend::Interpolate::tag_if(@_, 1) }},
+                               'if'                    => q{\&Vend::Interpolate::tag_if},
+                               'tag'                   => q{\&Vend::Interpolate::do_tag},
+                               'sql'                   => q{\&Vend::Data::sql_query},
+                       );
+
+my %RoutineName = (
+
+                               accessories             => q{sub {
+                                                                       &Vend::Interpolate::tag_accessories
+                                                                               ($_[0], '', $_[1])
+                                                                       }},
+                               area                    => q{\&Vend::Interpolate::tag_area},
+                               areatarget              => q{\&Vend::Interpolate::tag_areatarget},
+                               body                    => q{\&Vend::Interpolate::tag_body},
+                               bounce          => q{sub { return '' }},
+                               buttonbar               => q{\&Vend::Interpolate::tag_buttonbar},
+                               calc                    => q{\&Vend::Interpolate::tag_calc},
+                               cart                    => q{\&Vend::Interpolate::tag_cart},
+                               cgi                             => q{\&Vend::Interpolate::tag_cgi},
+                               checked                 => q{\&Vend::Interpolate::tag_checked},
+                               'currency'              => q{sub {
+                                                                               my($convert,$amount) = @_;
+                                                                               return &Vend::Util::currency(
+                                                                                                               $amount,
+                                                                                                               undef,
+                                                                                                               $convert);
+                                                                       }},
+                               compat                  => q{sub {
+                                                                               &Vend::Interpolate::interpolate_html('[old]' . $_[0]);
+                                                                       }},
+                               data                    => q{\&Vend::Interpolate::tag_data},
+                               default                 => q{\&Vend::Interpolate::tag_default},
+                               description             => q{\&Vend::Data::product_description},
+                               discount                => q{\&Vend::Interpolate::tag_discount},
+                               ecml                    => q{\&Vend::ECML::ecml},
+                               field                   => q{\&Vend::Data::product_field},
+                               file                    => q{\&Vend::Interpolate::tag_file},
+                               finish_order    => q{\&Vend::Interpolate::tag_finish_order},
+                               fly_list                => q{sub {
+                                                                       $_[0] = $Vend::Session->{'arg'} unless $_[0];
+                                                                       return &Vend::Interpolate::fly_page(@_);
+                                                                       }},
+                               framebase               => q{\&Vend::Interpolate::tag_frame_base},
+                               frames_off              => q{\&Vend::Interpolate::tag_frames_off},
+                               frames_on               => q{\&Vend::Interpolate::tag_frames_on},
+                               help                    => q{\&Vend::Interpolate::tag_help},
+                               index                   => q{\&Vend::Data::index_database},
+                               import                  => q{\&Vend::Data::import_text},
+                               include                 => q{sub {
+                                                                       &Vend::Interpolate::interpolate_html(
+                                                                               &Vend::Util::readfile
+                                                                                       ($_[0], $Global::NoAbsolute)
+                                                                                 );
+                                                                       }},
+                               input_filter    => q{\&Vend::Interpolate::input_filter},
+                               item_list               => q{\&Vend::Interpolate::tag_item_list},
+                               'if'                    => q{\&Vend::Interpolate::tag_self_contained_if},
+                               'or'                    => q{sub { return &Vend::Interpolate::tag_self_contained_if(@_, 1) }},
+                               'and'                   => q{sub { return &Vend::Interpolate::tag_self_contained_if(@_, 1) }},
+                               'goto'                  => q{sub { return '' }},
+                               label                   => q{sub { return '' }},
+                               last_page               => q{\&Vend::Interpolate::tag_last_page},
+                               lookup                  => q{\&Vend::Interpolate::tag_lookup},
+                               loop                    => q{sub {
+                                                                       # Munge the args, UGHH. Fix this.
+                                                                       my $option = splice(@_,3,1);
+                                                                       return &Vend::Interpolate::tag_loop_list
+                                                                               (@_, $option);
+                                                                       }},
+                               nitems                  => q{\&Vend::Util::tag_nitems},
+                               order                   => q{\&Vend::Interpolate::tag_order},
+                               page                    => q{\&Vend::Interpolate::tag_page},
+                               pagetarget              => q{\&Vend::Interpolate::tag_pagetarget},
+                               perl                    => q{\&Vend::Interpolate::tag_perl},
+                               mvasp                   => q{\&Vend::Interpolate::mvasp},
+                               post                    => q{sub { return $_[0] }},
+                               price           => q{\&Vend::Interpolate::tag_price},
+                               process_order   => q{\&Vend::Interpolate::tag_process_order},
+                               process_search  => q{\&Vend::Interpolate::tag_process_search},
+                               process_target  => q{\&Vend::Interpolate::tag_process_target},
+                               random                  => q{\&Vend::Interpolate::tag_random},
+                               read_cookie     => q{\&Vend::Util::read_cookie},
+                               rotate                  => q{\&Vend::Interpolate::tag_rotate},
+                               row                             => q{\&Vend::Interpolate::tag_row},
+                               'salestax'              => q{\&Vend::Interpolate::tag_salestax},
+                               scratch                 => q{\&Vend::Interpolate::tag_scratch},
+                               search                  => q{\&Vend::Interpolate::tag_search},
+                               search_region   => q{\&Vend::Interpolate::tag_search_region},
+                               selected                => q{\&Vend::Interpolate::tag_selected},
+                               setlocale               => q{\&Vend::Util::setlocale},
+                               set_cookie              => q{\&Vend::Util::set_cookie},
+                               rotate                  => q{\&Vend::Interpolate::tag_rotate},
+                               set                             => q{\&Vend::Interpolate::set_scratch},
+                               'shipping'              => q{\&Vend::Interpolate::tag_shipping},
+                               shipping_desc   => q{\&Vend::Interpolate::tag_shipping_desc},
+                               sql                             => q{\&Vend::Data::sql_query},
+                               'subtotal'              => q{\&Vend::Interpolate::tag_subtotal},
+                               strip                   => q{sub {
+                                                                               local($_) = shift;
+                                                                               s/^\s+//;
+                                                                               s/\s+$//;
+                                                                               return $_;
+                                                                       }},
+                               tag                             => q{\&Vend::Interpolate::do_parse_tag},
+                               total_cost              => q{\&Vend::Interpolate::tag_total_cost},
+                               userdb                  => q{\&Vend::UserDB::userdb},
+                               value                   => q{\&Vend::Interpolate::tag_value},
+                               value_extended  => q{\&Vend::Interpolate::tag_value_extended},
+
+                       );
+
+
+
+my %PosRoutine = (
+                               'or'                    => sub { return &Vend::Interpolate::tag_if(@_, 1) },
+                               'and'                   => sub { return &Vend::Interpolate::tag_if(@_, 1) },
+                               'if'                    => \&Vend::Interpolate::tag_if,
+                               'tag'                   => \&Vend::Interpolate::do_tag,
+                               'sql'                   => \&Vend::Data::sql_query,
+                       );
+
+my %Routine = (
+
+                               accessories             => sub {
+                                                                       &Vend::Interpolate::tag_accessories
+                                                                               ($_[0], '', $_[1])
+                                                                       },
+                               area                    => \&Vend::Interpolate::tag_area,
+                               areatarget              => \&Vend::Interpolate::tag_areatarget,
+                               body                    => \&Vend::Interpolate::tag_body,
+                               bounce          => sub { return '' },
+                               buttonbar               => \&Vend::Interpolate::tag_buttonbar,
+                               calc                    => \&Vend::Interpolate::tag_calc,
+                               cart                    => \&Vend::Interpolate::tag_cart,
+                               cgi                             => \&Vend::Interpolate::tag_cgi,
+                               checked                 => \&Vend::Interpolate::tag_checked,
+                               'currency'              => sub {
+                                                                               my($convert,$amount) = @_;
+                                                                               return &Vend::Util::currency(
+                                                                                                               $amount,
+                                                                                                               undef,
+                                                                                                               $convert);
+                                                                       },
+                               compat                  => sub {
+                                                                               &Vend::Interpolate::interpolate_html('[old]' . $_[0]);
+                                                                       },
+                               data                    => \&Vend::Interpolate::tag_data,
+                               default                 => \&Vend::Interpolate::tag_default,
+                               description             => \&Vend::Data::product_description,
+                               discount                => \&Vend::Interpolate::tag_discount,
+                               ecml                    => \&Vend::ECML::ecml,
+                               field                   => \&Vend::Data::product_field,
+                               file                    => \&Vend::Interpolate::tag_file,
+                               finish_order    => \&Vend::Interpolate::tag_finish_order,
+                               fly_list                => sub {
+                                                                       $_[0] = $Vend::Session->{'arg'} unless $_[0];
+                                                                       return &Vend::Interpolate::fly_page(@_);
+                                                                       },
+                               framebase               => \&Vend::Interpolate::tag_frame_base,
+                               frames_off              => \&Vend::Interpolate::tag_frames_off,
+                               frames_on               => \&Vend::Interpolate::tag_frames_on,
+                               help                    => \&Vend::Interpolate::tag_help,
+                               index                   => \&Vend::Data::index_database,
+                               import                  => \&Vend::Data::import_text,
+                               include                 => sub {
+                                                                       &Vend::Interpolate::interpolate_html(
+                                                                               &Vend::Util::readfile
+                                                                                       ($_[0], $Global::NoAbsolute)
+                                                                                 );
+                                                                       },
+                               input_filter    => \&Vend::Interpolate::input_filter,
+                               item_list               => \&Vend::Interpolate::tag_item_list,
+                               'if'                    => \&Vend::Interpolate::tag_self_contained_if,
+                               'or'                    => sub { return &Vend::Interpolate::tag_self_contained_if(@_, 1) },
+                               'and'                   => sub { return &Vend::Interpolate::tag_self_contained_if(@_, 1) },
+                               'goto'                  => sub { return '' },
+                               label                   => sub { return '' },
+                               last_page               => \&Vend::Interpolate::tag_last_page,
+                               lookup                  => \&Vend::Interpolate::tag_lookup,
+                               loop                    => sub {
+                                                                       # Munge the args, UGHH. Fix this.
+                                                                       my $option = splice(@_,3,1);
+                                                                       return &Vend::Interpolate::tag_loop_list
+                                                                               (@_, $option);
+                                                                       },
+                               nitems                  => \&Vend::Util::tag_nitems,
+                               order                   => \&Vend::Interpolate::tag_order,
+                               page                    => \&Vend::Interpolate::tag_page,
+                               pagetarget              => \&Vend::Interpolate::tag_pagetarget,
+                               perl                    => \&Vend::Interpolate::tag_perl,
+                               mvasp                   => \&Vend::Interpolate::mvasp,
+                               post                    => sub { return $_[0] },
+                               price           => \&Vend::Interpolate::tag_price,
+                               process_order   => \&Vend::Interpolate::tag_process_order,
+                               process_search  => \&Vend::Interpolate::tag_process_search,
+                               process_target  => \&Vend::Interpolate::tag_process_target,
+                               random                  => \&Vend::Interpolate::tag_random,
+                               read_cookie     => \&Vend::Util::read_cookie,
+                               rotate                  => \&Vend::Interpolate::tag_rotate,
+                               row                             => \&Vend::Interpolate::tag_row,
+                               'salestax'              => \&Vend::Interpolate::tag_salestax,
+                               scratch                 => \&Vend::Interpolate::tag_scratch,
+                               search                  => \&Vend::Interpolate::tag_search,
+                               search_region   => \&Vend::Interpolate::tag_search_region,
+                               selected                => \&Vend::Interpolate::tag_selected,
+                               setlocale               => \&Vend::Util::setlocale,
+                               set_cookie              => \&Vend::Util::set_cookie,
+                               rotate                  => \&Vend::Interpolate::tag_rotate,
+                               set                             => \&Vend::Interpolate::set_scratch,
+                               'shipping'              => \&Vend::Interpolate::tag_shipping,
+                               shipping_desc   => \&Vend::Interpolate::tag_shipping_desc,
+                               sql                             => \&Vend::Data::sql_query,
+                               'subtotal'              => \&Vend::Interpolate::tag_subtotal,
+                               strip                   => sub {
+                                                                               local($_) = shift;
+                                                                               s/^\s+//;
+                                                                               s/\s+$//;
+                                                                               return $_;
+                                                                       },
+                               tag                             => \&Vend::Interpolate::do_parse_tag,
+                               total_cost              => \&Vend::Interpolate::tag_total_cost,
+                               userdb                  => \&Vend::UserDB::userdb,
+                               value                   => \&Vend::Interpolate::tag_value,
+                               value_extended  => \&Vend::Interpolate::tag_value_extended,
+
+                       );
+
+
+my %attrAlias = (
+        page           => { 'base' => 'arg' },
+        field                  => { 
+                                                       'field' => 'name',
+                                                       'column' => 'name',
+                                                       'col' => 'name',
+                                                       'key' => 'code',
+                                                       'row' => 'code',
+                                               },
+        index                  => { 
+                                                       'database' => 'table',
+                                                       'base' => 'table',
+                                               },
+        import                 => { 
+                                                       'database' => 'table',
+                                                       'base' => 'table',
+                                               },
+        input_filter           => { 
+                                                       'ops' => 'op',
+                                                       'var' => 'name',
+                                                       'variable' => 'name',
+                                               },
+        data           => { 
+                                                       'database' => 'table',
+                                                       'base' => 'table',
+                                                       'name' => 'field',
+                                                       'column' => 'field',
+                                                       'col' => 'field',
+                                                       'code' => 'key',
+                                                       'row' => 'key',
+                                               },
+        'or'                   => { 
+                                                       'comp' => 'compare',
+                                                       'operator' => 'op',
+                                                       'base' => 'type',
+                                               },
+        'and'                  => { 
+                                                       'comp' => 'compare',
+                                                       'operator' => 'op',
+                                                       'base' => 'type',
+                                               },
+        'userdb'               => {
+                                                       'table' => 'db',
+                                                       'name' => 'nickname',
+                                               },
+        'shipping'                     => { 'cart' => 'name', },
+        'salestax'                     => { 'cart' => 'name', },
+        'subtotal'                     => { 'cart' => 'name', },
+        'total_cost'           => { 'cart' => 'name', },
+        'if'                   => { 
+                                                       'comp' => 'compare',
+                                                       'condition' => 'compare',
+                                                       'operator' => 'op',
+                                                       'base' => 'type',
+                                               },
+        search_region          => { params => 'arg',
+                                                        args => 'arg', },
+        loop                   => { args => 'arg',
+                                                        list => 'arg', },
+        item_list              => { cart => 'name', },
+        lookup                 => { 
+                                                       'database' => 'table',
+                                                       'base' => 'table',
+                                                       'name' => 'field',
+                                                       'code' => 'key',
+                                               },
+);
+
+my %Alias = (
+
+                               qw(
+                                               url                     urldecode
+                                               urld            urldecode
+                                               href            area
+                                               shipping_description shipping_desc
+                                               a                       pagetarget
+                               )
+                       );
+
+my %canNest = (
+
+                               qw(
+                                               if                      1
+                                               loop            1
+                               )
+                       );
+
+
+my %addAttr = (
+                               qw(
+                                       ecml      1
+                                       userdb    1
+                                       import    1
+                                       input_filter  1
+                                       index     1
+                                       page      1
+                                       price     1
+                                       area      1
+                                       value_extended    1
+                               )
+                       );
+
+
+my %replaceHTML = (
+                               qw(
+                                       del .*
+                                       pre .*
+                                       xmp .*
+                                       script .*
+                               )
+                       );
+
+my %replaceAttr = (
+                                       area                    => { qw/ a      href                    /},
+                                       areatarget              => { qw/ a      href                    /},
+                                       process_target  => { qw/ form action            /},
+                                       process_order   => { qw/ form action            /},
+                                       process_search  => { qw/ form action            /},
+                                       checked                 => { qw/ input checked          /},
+                                       selected                => { qw/ option selected        /},
+                       );
+
+my %insertHTML = (
+                               qw(
+
+                               form    process_target|process_order|process_search|area
+                               a               area|areatarget
+                               input   checked
+                               option  selected
+                               )
+                       );
+
+my %lookaheadHTML = (
+                               qw(
+
+                               if              then|elsif|else
+                               )
+                       );
+
+my %rowfixHTML = (     qw/
+                                               td      item_list|loop|sql_list
+                                       /        );
+# Only for containers
+my %insideHTML = (
+                               qw(
+                                       select  loop|item_list|tag
+                               )
+
+                               );
+
+# Only for containers
+my %endHTML = (
+                               qw(
+
+                               tr              .*
+                               td              .*
+                               th              .*
+                               del     .*
+                               script  .*
+                               table   if
+                               object  perl
+                               param   perl
+                               font    if
+                               a               if
+                               )
+                       );
+
+my %hasEndTag = (
+
+                               qw(
+                                               calc            1
+                                               compat          1
+                                               currency        1
+                                               discount        1
+                                               fly_list        1
+                                               if                      1
+                                               import          1
+                                               item_list       1
+                                               input_filter  1
+                                               loop            1
+                                               sql                     1
+                                               perl            1
+                                               mvasp           1
+                                               post            1
+                                               row                     1
+                                               set                     1
+                                               search_region                   1
+                                               strip           1
+                                               tag                     1
+
+                               )
+                       );
+
+my %Interpolate = (
+
+                               qw(
+                                               buttonbar       1
+                                               calc            1
+                                               currency        1
+                                               import          1
+                                               random          1
+                                               rotate          1
+                                               row                     1
+                               )
+                       );
+
+my %Gobble = ( qw/ mvasp 1/ );
+
+my %isEndAnchor = (
+
+                               qw(
+                                               areatarget      1
+                                               area            1
+                                               pagetarget      1
+                                               page            1
+                                               order           1
+                                               last_page       1
+                               )
+                       );
+
+my $Tags_added = 0;
+
+my $Initialized = 0;
+
+
+sub global_init {
+               add_tags($Global::UserTag);
+}
+
+sub new
+{
+    my $class = shift;
+    my $self = new Vend::Parser;
+       $self->{INVALID} = 0;
+       $self->{INTERPOLATE} = shift || 0;
+
+       add_tags($Vend::Cfg->{UserTag})
+               unless $Tags_added;
+
+       $self->{TOPLEVEL} = 1 if ! $Initialized;
+
+       $self->{OUT} = '';
+    bless $self, $class;
+       $Initialized = $self;
+}
+
+my %myRefs = (
+     Alias           => \%Alias,
+     addAttr         => \%addAttr,
+     attrAlias       => \%attrAlias,
+        canNest         => \%canNest,
+        endHTML         => \%endHTML,
+        hasEndTag       => \%hasEndTag,
+        Implicit        => \%Implicit,
+        insertHTML          => \%insertHTML,
+        insideHTML          => \%insideHTML,
+        Interpolate     => \%Interpolate,
+        InvalidateCache => \%InvalidateCache,
+        isEndAnchor     => \%isEndAnchor,
+        lookaheadHTML   => \%lookaheadHTML,
+        Order           => \%Order,
+        PosNumber       => \%PosNumber,
+        PosRoutine      => \%PosRoutine,
+        replaceAttr     => \%replaceAttr,
+        replaceHTML     => \%replaceHTML,
+        Routine         => \%Routine,
+);
+
+my %Documentation;
+
+sub tag_reference {
+       LOCAL: {
+               local($/);
+               my $text = <DATA>;
+               my (@items) = grep /\S/, split /\n%%%\n/, $text;
+               for(@items) {
+                       my ($k, $v) = split /\n%%\n/, $_, 2;
+                       $Documentation{$k} = $v;
+               }
+       }
+
+       print $Documentation{BEGIN};
+
+       for(sort keys %Routine) {
+               my $tag = $_;
+               print "\n\n=head2 $tag\n\n=over 4\n\n";
+               print "=item CALL INFORMATION\n\n";
+               my $val;
+               my @alias = %Alias;
+               my @val = ();
+               for (my $i = 1; $i < @alias; $i += 2) {
+                       push @val, $alias[$i - 1] if $alias[$i] eq $tag;
+               }
+
+
+               if(@val) {
+                       print "Aliases for tag\n\n";
+                       print join "\n", @val;
+                       print "\n\n";
+               }
+               @val = ();
+
+               my @parms = ();
+               if(defined $Order{$tag} and @{$Order{$tag}}) {
+                       @parms = @{$Order{$tag}};
+                       print "Parameters: B<";
+                       print join " ", @parms;
+                       print ">\n\n";
+                       if($PosNumber{$tag} >= @parms) {
+                               print "Positional parameters in same order.\n";
+                       }
+                       elsif ($tag eq 'loop' || $PosRoutine{$tag}) {
+                               print "THIS TAG HAS SPECIAL POSITIONAL PARAMETER HANDLING.\n\n";
+                       }
+                       else {
+                               print "ONLY THE B<";
+                               print join " ", @parms[0 .. $PosNumber{$tag} - 1];
+                               print "> PARAMETERS ARE POSITIONAL.\n";
+                       }
+                       print "\n\n";
+               }
+               else {
+                       printf "No parameters.\n\n";
+               }
+
+               if(defined $addAttr{$tag}) {
+                       print <<EOF if defined $hasEndTag{$tag};
+B<The attribute hash reference is passed> after the parameters but before
+the container text argument.
+B<This may mean that there are parameters not shown here.>
+
+EOF
+                       print <<EOF if ! defined $hasEndTag{$tag};
+B<The attribute hash reference is passed> to the subroutine after
+the parameters as the last argument.
+B<This may mean that there are parameters not shown here.>
+
+EOF
+               }
+               else {
+                       print "Pass attribute hash as last to subroutine: B<no>\n\n";
+               }
+
+               if(! defined $Interpolate{$tag}) {
+                       print "Must pass named parameter interpolate=1 to cause interpolation.";
+               }
+               elsif($hasEndTag{$tag}) {
+                       print "Interpolates B<container text> by default>.";
+               }
+               elsif(!$Gobble{$tag}) {
+                       print "Interpolates B<its own output> by default.";
+               }
+
+               print "\n\n";
+
+               if (defined $hasEndTag{$tag}) {
+                       my $nest = defined $canNest{$tag} ? 'YES' : 'NO';
+                       print "This is a container tag, i.e. [$tag] FOO [/$tag].\nNesting: $nest\n\n";
+               }
+
+               print "Invalidates cache: B<"                                                   .
+                               (defined $InvalidateCache{$tag} ? 'YES' : 'no') .
+                               ">\n\n";
+               print "This tag B<gobbles> all remaining page text if no end tag is passed.\n\n"
+                       if $Gobble{$tag};
+                      
+
+               print "Called Routine: $RoutineName{$tag}\n\n";
+               print "Called Routine for positonal: $PosRoutineName{$tag}\n\n" if $PosRoutine{$tag};
+
+               print "ASP/perl tag calls:\n\n";
+               print '    $Tag->' . $tag . '(' ."\n        {\n";
+               for (@parms) {
+                       print "         $_ => VALUE,\n";
+               }
+               print "        }";
+               print ",\n        BODY" if defined $hasEndTag{$tag};
+               print "\n    )\n  \n OR\n \n";
+               push @parms, 'ATTRHASH'         if defined $addAttr{$tag};
+               push @parms, 'BODY'                     if defined $hasEndTag{$tag};
+               print '    $Tag->' . $tag . '($' . join(', $', @parms) . ');' . "\n\n";
+
+               if (defined $attrAlias{$tag}) {
+                       printf "Attribute aliases\n\n";
+                       for( sort keys %{$attrAlias{$tag}}) {
+                               print "            $_ ==> $attrAlias{$tag}{$_}\n";
+                       }
+                       print "\n\n";
+               }
+               print " \n\n";
+               print "=item DESCRIPTION\n\n";
+               print $Documentation{$tag} if defined $Documentation{$tag};
+               print "B<NO DESCRIPTION>" if ! defined $Documentation{$tag};
+               print "\n\n";
+               print "=back\n\n";
+
+       }
+
+       print $Documentation{END};
+}
+
+sub do_tag {
+       my $tag = shift;
+       if (! defined $Routine{tag} and (not $tag = $Alias{$tag}) ) {
+               ::logError("Tag '$tag' not defined.");
+               return undef;
+       };
+       if(ref $_[-1] && scalar @{$Order{$tag}}) {
+               my $text;
+               my $ref = pop(@_);
+               $text = shift if $hasEndTag{$tag};
+               my @args = @$ref{ @{$Order{$tag}} };
+               push @args, $ref if $addAttr{$tag};
+               return &{$Routine{$tag}}(@args, $text || undef);
+       }
+       else {
+               return &{$Routine{$tag}}(@_);
+       }
+}
+
+sub resolve_args {
+       my $tag = shift;
+       return @_ unless defined $Routine{$tag};
+       my $ref = shift;
+       my @list;
+       if(defined $attrAlias{$tag}) {
+               my ($k, $v);
+               while (($k, $v) = each %{$attrAlias{$tag}} ) {
+                       next unless defined $ref->{$k};
+                       $ref->{$v} = $ref->{$k};
+               }
+       }
+       @list = @{$ref}{@{$Order{$tag}}};
+       push @list, $ref if defined $addAttr{$tag};
+       push @list, (shift || $ref->{body} || '') if $hasEndTag{$tag};
+       return @list;
+}
+
+sub add_tags {
+       return unless @_;
+       my $ref = shift;
+       my $area;
+       no strict 'refs';
+       foreach $area (keys %myRefs) {
+               next unless $ref->{$area};
+# DEBUG
+# Vend::Util::logDebug
+# ("Adding $area = " . Vend::Util::uneval($ref->{$area}) . "\n")
+#      if ::debug(0x2);
+# END DEBUG
+               if($area eq 'Routine') {
+                       for (keys %{$ref->{$area}}) {
+                               $myRefs{$area}->{$_} = $ref->{$area}->{$_};
+                       }
+                       next;
+               }
+               elsif ($area =~ /HTML$/) {
+                       for (keys %{$ref->{$area}}) {
+                               $myRefs{$area}->{$_} =
+                                       defined $myRefs{$area}->{$_}
+                                       ? $ref->{$area}->{$_} .'|'. $myRefs{$area}->{$_}
+                                       : $ref->{$area}->{$_};
+                       }
+               }
+               else {
+                       Vend::Util::copyref $ref->{$area}, $myRefs{$area};
+               }
+       }
+}
+
+sub eof
+{
+    shift->parse(undef);
+}
+
+sub text
+{
+    my($self, $text) = @_;
+       $Vend::PageCacheCopy .= $text
+               if defined $Vend::PageCacheCopy and $self->{TOPLEVEL};
+       $self->{OUT} .= $text;
+}
+
+sub comment
+{
+    # my($self, $comment) = @_;
+}
+
+my %Monitor = ( qw( ) );
+
+sub build_html_tag {
+       my ($orig, $attr, $attrseq) = @_;
+       $orig =~ s/\s+.*//s;
+       for (@$attrseq) {
+               $orig .= qq{ \U$_="} ;
+               $attr->{$_} =~ s/"/\\"/g;
+               $orig .= $attr->{$_};
+               $orig .= '"';
+       }
+       $orig .= ">";
+}
+
+my %implicitHTML = (qw/checked CHECKED selected SELECTED/);
+
+sub format_html_attribute {
+       my($attr, $val) = @_;
+       if(defined $implicitHTML{$attr}) {
+               return $implicitHTML{$attr};
+       }
+       $val =~ s/"/&quot;/g;
+       return qq{$attr="$val"};
+}
+
+sub goto_buf {
+       my ($name, $buf) = @_;
+       if(! $name) {
+               $$buf = '';
+               return;
+       }
+       while($$buf =~ s!  .+?
+                                                       (
+                                                               (?:
+                                                               \[ label \s+ (?:name \s* = \s* ["']?)?  |
+                                                               <[^>]+? \s+ mv.label \s*=\s*["']?               |
+                                                               <[^>]+? \s+
+                                                                       mv \s*=\s*["']? label
+                                                                       [^>]*? \s+ mv.name\s*=\s*["']?          |
+                                                               <[^>]+? \s+ mv \s*=\s*["']? label  \s+  |
+                                                               )
+                                                               (\w+)
+                                                       |
+                                                               </body\s*>
+                                                       )
+                                       !$1!ixs )
+       {
+                       last if $name eq $2;
+       }
+       return;
+}
+
+sub html_start {
+    my($self, $tag, $attr, $attrseq, $origtext, $end_tag) = @_;
+       $tag =~ tr/-/_/;   # canonical
+       $end_tag = lc $end_tag;
+       my $buf = \$self->{_buf};
+#::logGlobal("tag=$tag end_tag=$end_tag buf length " . length($$buf)) if $Monitor{$tag};
+#::logGlobal("attributes: ", %{$attr}) if $Monitor{$tag};
+       my($tmpbuf);
+    # $attr is reference to a HASH, $attrseq is reference to an ARRAY
+       my($return_html);
+
+       unless (defined $Routine{$tag}) {
+               if(defined $Alias{$tag}) {
+                       my ($rest, $text);
+                       ($tag, $rest) = split /\s+/, $Alias{$tag}, 2;
+                       _find_tag (\$rest, $attr, $attrseq);
+               }
+               elsif ($tag eq 'urldecode') {
+                       $attr->{urldecode} = 1;
+                       $return_html = $origtext;
+                       $return_html =~ s/\s+.*//s;
+               }
+               else {
+                       $self->{OUT} .= $origtext;
+                       return 1;
+               }
+       }
+
+       if(defined $InvalidateCache{$tag} and !$attr->{cache}) {
+               $self->{INVALID} = 1;
+       }
+
+       $attr->{interpolate} = $self->{INTERPOLATE}
+               unless defined $attr->{interpolate};
+
+       my $trib;
+       foreach $trib (@$attrseq) {
+               # Attribute aliases
+               if(defined $attrAlias{$tag} and $attrAlias{$tag}{$trib}) {
+                       my $new = $attrAlias{$tag}{$trib} ;
+                       $attr->{$new} = delete $attr->{$trib};
+                       $trib = $new;
+               }
+               elsif (0 and defined $Alias{$trib}) {
+                       my $new = $Alias{$trib} ;
+                       $attr->{$new} = delete $attr->{$trib};
+                       $trib = $new;
+               }
+               # Parse tags within tags, only works if the [ is the
+               # first character.
+               $attr->{$trib} =~ s/%([A-Fa-f0-9]{2})/chr(hex($1))/eg if $attr->{urldecode};
+               next unless $attr->{$trib} =~ /\[\w+[-\w]*\s*[\000-\377]*\]/;
+
+               my $p = new Vend::Parse;
+               $p->parse($attr->{$trib});
+               $attr->{$trib} = $p->{OUT};
+               $self->{INVALID} += $p->{INVALID};
+       }
+
+       if($tag eq 'urldecode') {
+               $self->{OUT} .= build_html_tag($return_html, $attr, $attrseq);
+               return 1;
+       }
+
+       $attr->{'decode'} = 1 unless defined $attr->{'decode'};
+       $attr->{'reparse'} = 1 unless defined $attr->{'reparse'};
+       $attr->{'true'} = 1;
+       $attr->{'false'} = 0;
+       $attr->{'undef'} = undef;
+
+       my ($routine,@args);
+
+       if ($attr->{OLD}) {
+       # HTML old-style tag
+               $attr->{interpolate} = 0 if $hasEndTag{$tag} and $canNest{$tag};
+               $attr->{interpolate} = 1 if defined $Interpolate{$tag};
+               @args = $attr->{OLD};
+               if(defined $PosNumber{$tag} and $PosNumber{$tag} > 1) {
+                       @args = split /\s+/, $attr->{OLD}, $PosNumber{$tag};
+                       push(@args, undef) while @args < $PosNumber{$tag};
+               }
+               $routine =  $PosRoutine{$tag} || $Routine{$tag};
+       }
+       else {
+       # New style tag, HTML or otherwise
+               $routine = $Routine{$tag};
+               $attr->{interpolate} = 1
+                       if defined $Interpolate{$tag} and ! defined $attr->{interpolate};
+               @args = @{$attr}{ @{ $Order{$tag} } };
+               push(@args, $attr) if $addAttr{$tag};
+       }
+
+       if($tag =~ /^[gb]o/) {
+               if($tag eq 'goto') {
+                       return 1 if defined $attr->{'if'} and
+                                               (! $attr->{'if'} or $attr->{'if'} =~ /^\s*[\s0]\s*$/); 
+                       if(! $args[0]) {
+                               $$buf = '';
+                               $$Initialized->{_buf} = '';
+                               $Initialized->{_buf} = '';
+                               $self->{ABORT} = 1
+                                       if $attr->{abort};
+                               return ($self->{SEND} = 1);
+                       }
+                       goto_buf($args[0], \$Initialized->{_buf});
+                       $self->{ABORT} = 1;
+                       return 1;
+               }
+               elsif($tag eq 'bounce') {
+                       return 1 if defined $attr->{'if'} and
+                                               (! $attr->{'if'} or $attr->{'if'} =~ /^\s*[\s0]\s*$/); 
+                       $Vend::StatusLine = '' if ! $Vend::StatusLine;
+                       $Vend::StatusLine .= <<EOF;
+Status: 302 moved
+Location: $attr->{href}
+EOF
+                       $$buf = '';
+                       $Initialized->{_buf} = '';
+                       return ($self->{SEND} = 1);
+               }
+       }
+
+#::logGlobal("tag=$tag end_tag=$end_tag attributes:\n" . Vend::Util::uneval($attr)) if$Monitor{$tag};
+
+       my $prefix = '';
+       my $midfix = '';
+       my $postfix = '';
+       my @out;
+
+       if($insertHTML{$end_tag}
+               and ! $attr->{noinsert}
+               and $tag =~ /^($insertHTML{$end_tag})$/) {
+               $origtext =~ s/>\s*$//;
+               @out = Text::ParseWords::shellwords($origtext);
+               shift @out;
+               @out = grep $_ !~ /^[Mm][Vv][=.]/, @out
+                       unless $attr->{showmv};
+               if (defined $replaceAttr{$tag}
+                       and $replaceAttr{$tag}->{$end_tag}
+                       and     ! $attr->{noreplace})
+               {
+                       my $t = $replaceAttr{$tag}->{$end_tag};
+                       @out = grep $_ !~ /^($t)\b/i, @out;
+                       unless(defined $implicitHTML{$t}) {
+                               $out[0] .= qq{ \U$t="};
+                               $out[1] = defined $out[1] ? qq{" } . $out[1] : '"';
+                       }
+                       else { $midfix = ' ' }
+               }
+               else {
+                       $out[0] = " " . $out[0] . " "
+                               if $out[0];
+               }
+               if (@out) {
+                       $out[$#out] .= '>';
+               }
+               else {
+                       @out = '>';
+               }
+#::logGlobal("inserted " . join "|", @out);
+       }
+
+       if($hasEndTag{$tag}) {
+               my $rowfix;
+               # Handle embedded tags, but only if interpolate is 
+               # defined (always if using old tags)
+               if (defined $replaceHTML{$end_tag}
+                       and $tag =~ /^($replaceHTML{$end_tag})$/
+                       and ! $attr->{noreplace} )
+               {
+                       $origtext = '';
+                       $tmpbuf = find_html_end($end_tag, $buf);
+                       $tmpbuf =~ s:</$end_tag\s*>::;
+                       HTML::Entities::decode($tmpbuf) if $attr->{decode};
+                       $tmpbuf =~ tr/\240/ /;
+               }
+               else {
+                       @out = Text::ParseWords::shellwords($origtext);
+                       ($attr->{showmv} and
+                                       @out = map {s/^[Mm][Vv]\./mv-/} @out)
+                               or @out = grep ! /^[Mm][Vv][=.]/, @out;
+                       $out[$#out] =~ s/([^>\s])\s*$/$1>/;
+                       $origtext = join " ", @out;
+
+                       if (defined $lookaheadHTML{$tag} and ! $attr->{nolook}) {
+                               $tmpbuf = $origtext . find_html_end($end_tag, $buf);
+                               while($$buf =~ s~^\s*(<([A-Za-z][-A-Z.a-z0-9]*)[^>]*)\s+
+                                                               [Mm][Vv]\s*=\s*
+                                                               (['"]) \[?
+                                                                       ($lookaheadHTML{$tag})\b(.*?)
+                                                               \]?\3~~ix ) 
+                               {
+                                       my $orig = $1;
+                                       my $enclose = $4;
+                                       my $adder = $5;
+                                       my $end = lc $2;
+                                       $tmpbuf .= "[$enclose$adder]"   .  $orig        .
+                                                               find_html_end($end, $buf)       .
+                                                               "[/$enclose]";
+                               }
+                       }
+                       # GACK!!! No table row attributes in some editors????
+                       elsif (defined $rowfixHTML{$end_tag}
+                               and $tag =~ /^($rowfixHTML{$end_tag})$/
+                               and $attr->{rowfix} )
+                       {
+                               $rowfix = 1;
+                               $tmpbuf = '<tr>' . $origtext . find_html_end('tr', $buf);
+#::logGlobal("Tmpbuf: $tmpbuf");
+                       }
+                       elsif (defined $insideHTML{$end_tag}
+                                       and ! $attr->{noinside}
+                                       and $tag =~ /^($insideHTML{$end_tag})$/i) {
+                               $prefix = $origtext;
+                               $tmpbuf = find_html_end($end_tag, $buf);
+                               $tmpbuf =~ s:</$end_tag\s*>::;
+                               $postfix = "</$end_tag>";
+                               HTML::Entities::decode($tmpbuf) if $attr->{'decode'};
+                               $tmpbuf =~ tr/\240/ / if $attr->{'decode'};
+                       }
+                       else {
+                               $tmpbuf = $origtext . find_html_end($end_tag, $buf);
+                       }
+               }
+
+               $tmpbuf =~ s/%([A-Fa-f0-9]{2})/chr(hex($1))/eg if $attr->{urldecode};
+
+               if ($attr->{interpolate}) {
+                       my $p = new Vend::Parse;
+                       $p->parse($tmpbuf);
+                       $tmpbuf =  $p->{OUT};
+               }
+
+               $tmpbuf =  $attr->{prepend} . $tmpbuf if defined $attr->{prepend};
+               $tmpbuf .= $attr->{append}            if defined $attr->{append};
+
+               if (! $attr->{reparse}) {
+                       $self->{OUT} .= $prefix . &{$routine}(@args,$tmpbuf) . $postfix;
+               }
+               elsif (! defined $rowfix) {
+                       $$buf = $prefix . &{$routine}(@args,$tmpbuf) . $postfix . $$buf
+               }
+               else {
+                       $tmpbuf = &{$routine}(@args,$tmpbuf);
+                       $tmpbuf =~ s|<tr>||i;
+                       $$buf = $prefix . $tmpbuf . $postfix . $$buf;
+               }
+
+
+       }
+       else {
+               if(! @out and $attr->{prepend} or $attr->{append}) {
+                       my @tmp;
+                       @tmp = Text::ParseWords::shellwords($origtext);
+                       shift @tmp;
+                       @tmp = grep $_ !~ /^[Mm][Vv][=.]/, @tmp
+                               unless $attr->{showmv};
+                       $postfix = $attr->{prepend} ? "<\U$end_tag " . join(" ", @tmp) : '';
+                       $prefix = $attr->{append} ? "<\U$end_tag " . join(" ", @tmp) : '';
+               }
+               if(! $attr->{interpolate}) {
+                       if(@out) {
+                               $self->{OUT} .= "<\U$end_tag ";
+                               if              ($out[0] =~ / > \s*$ /x ) { }   # End of tag, do nothing
+                               elsif   ($out[0] =~ / ^[^"]*"$/x ) {     # End of tag
+                                       $self->{OUT} .= shift(@out);
+                               }
+                               else {
+                                       unshift(@out, '');
+                               }
+                       }
+                       $self->{OUT} .= $prefix . &$routine( @args ) . $midfix;
+                       $self->{OUT} .= join(" ", @out) . $postfix;
+               }
+               else {
+                       if(@out) {
+                               $$buf = "<\U$end_tag " . &$routine( @args ) . $midfix . join(" ", @out) . $$buf;
+                       }
+                       else {
+                               $$buf = $prefix . &$routine( @args ) . $postfix . $$buf;
+                       }
+               }
+       }
+
+       $self->{SEND} = $attr->{'send'} || undef;
+#::logGlobal("Returning from $tag");
+       return 1;
+
+}
+
+sub start {
+       return html_start(@_) if $_[0]->{HTML};
+    my($self, $tag, $attr, $attrseq, $origtext) = @_;
+       $tag =~ tr/-/_/;   # canonical
+       my $buf = \$self->{_buf};
+#::logGlobal("tag=$tag buf length " . length($$buf));
+#::logGlobal("tag=$tag Interp='$Interpolate{$tag}' attributes:\n" . Vend::Util::uneval($attr)) if$Monitor{$tag};
+       my($tmpbuf);
+    # $attr is reference to a HASH, $attrseq is reference to an ARRAY
+       unless (defined $Routine{$tag}) {
+               if(defined $Alias{$tag}) {
+                       my ($rest, $text);
+                       ($tag, $rest) = split /\s+/, $Alias{$tag}, 2;
+                       $text = _find_tag (\$rest, $attr, $attrseq);
+                       $text = " $text" if $text;
+                       $origtext =~ s:^(\[\S+):[$tag$text:;
+               }
+               else {
+                       $self->{OUT} .= $origtext;
+                       return 1;
+               }
+       }
+
+       if(defined $InvalidateCache{$tag} and !$attr->{cache}) {
+               $self->{INVALID} = 1;
+       }
+
+       $attr->{interpolate} = $self->{INTERPOLATE}
+               unless $Interpolate{$tag} or defined $attr->{interpolate};
+
+       my $trib;
+       foreach $trib (@$attrseq) {
+               # Attribute aliases
+               if(defined $attrAlias{$tag} and $attrAlias{$tag}{$trib}) {
+                       my $new = $attrAlias{$tag}{$trib} ;
+                       $attr->{$new} = delete $attr->{$trib};
+                       $trib = $new;
+               }
+               # Parse tags within tags, only works if the [ is the
+               # first character.
+               next unless $attr->{$trib} =~ /\[\w+[-\w]*\s*[\000-\377]*\]/;
+
+               my $p = new Vend::Parse;
+               $p->parse($attr->{$trib});
+               $attr->{$trib} = $p->{OUT};
+               $self->{INVALID} += $p->{INVALID};
+       }
+
+       $attr->{'reparse'} = 1 unless defined $Gobble{$tag} || defined $attr->{'reparse'};
+       $attr->{'true'} = 1;
+       $attr->{'false'} = 0;
+       $attr->{'undef'} = undef;
+
+       my ($routine,@args);
+
+       # Check for old-style positional tag
+       if(!@$attrseq and $origtext =~ s/\[[-\w]+\s+//i) {
+                       $origtext =~ s/\]$//;
+                       $attr->{interpolate} = 0 if $hasEndTag{$tag} and $canNest{$tag};
+                       $attr->{interpolate} = 1 if defined $Interpolate{$tag};
+                       @args = ($origtext);
+                       if(defined $PosNumber{$tag} and $PosNumber{$tag} > 1) {
+                               @args = split /\s+/, $origtext, $PosNumber{$tag};
+                               push(@args, undef) while @args < $PosNumber{$tag};
+                       }
+                       $routine =  $PosRoutine{$tag} || $Routine{$tag};
+       }
+       else {
+               $routine = $Routine{$tag};
+               $attr->{interpolate} = 1
+                       if  defined $Interpolate{$tag} and ! defined $attr->{interpolate};
+               @args = @{$attr}{ @{ $Order{$tag} } };
+               push(@args, $attr) if $addAttr{$tag};
+       }
+
+#::logGlobal("Interpolate value now='$attr->{interpolate}'") if$Monitor{$tag};
+
+
+#::logGlobal(<<EOF) if $Monitor{$tag};
+#tag=$tag
+#routine=$routine
+#has_end=$hasEndTag{$tag}
+#attributes=@args
+#interpolate=$attr->{interpolate}
+#EOF
+
+       if($tag =~ /^[gb]o/) {
+               if($tag eq 'goto') {
+                       return 1 if defined $attr->{'if'} and
+                                               (! $attr->{'if'} or $attr->{'if'} =~ /^\s*[\s0]\s*$/); 
+                       if(! $args[0]) {
+                               $$buf = '';
+                               $Initialized->{_buf} = '';
+                               $self->{ABORT} = 1
+                                       if $attr->{abort};
+                               return ($self->{SEND} = 1);
+                       }
+                       goto_buf($args[0], \$Initialized->{_buf});
+                       $self->{ABORT} = 1;
+                       $self->{SEND} = 1 if ! $Initialized->{_buf};
+                       return 1;
+               }
+               elsif($tag eq 'bounce') {
+                       return 1 if defined $attr->{'if'} and
+                                               (! $attr->{'if'} or $attr->{'if'} =~ /^\s*[\s0]\s*$/); 
+                       $Vend::StatusLine = '' if ! $Vend::StatusLine;
+                       $Vend::StatusLine .= <<EOF;
+Status: 302 moved
+Location: $attr->{href}
+EOF
+                       $$buf = '';
+                       $Initialized->{_buf} = '';
+                       $self->{SEND} = 1;
+                       return 1;
+               }
+       }
+
+       if($hasEndTag{$tag}) {
+               # Handle embedded tags, but only if interpolate is 
+               # defined (always if using old tags)
+#::logGlobal("look end for $tag, buf=" . length($$buf) );
+               $tmpbuf = find_matching_end($tag, $buf);
+#::logGlobal("FOUND end for $tag\nBuf " . length($$buf) . ":\n" . $$buf . "\nTmpbuf:\n$tmpbuf\n");
+               if ($attr->{interpolate}) {
+                       my $p = new Vend::Parse;
+                       $p->parse($tmpbuf);
+                       $tmpbuf = $p->{ABORT} ? '' : $p->{OUT};
+               }
+               if($attr->{reparse} ) {
+                       my $intermediate = &$routine(@args,$tmpbuf);
+                       $$buf = $intermediate . $$buf;
+               }
+               else {
+                       $self->{OUT} .= &{$routine}(@args,$tmpbuf);
+               }
+       }
+       elsif(! $attr->{interpolate}) {
+               $self->{OUT} .= &$routine( @args );
+       }
+       else {
+               $$buf = &$routine( @args ) . $$buf;
+       }
+
+       $self->{SEND} = $attr->{'send'} || undef;
+#::logGlobal("Returning from $tag");
+       return 1;
+}
+
+sub end
+{
+    my($self, $tag) = @_;
+       my $save = $tag;
+       $tag =~ tr/-/_/;   # canonical
+       
+# DEBUG
+#Vend::Util::logDebug
+#("called Vend::Parse::end with $tag\n")
+#      if ::debug(0x2);
+# END DEBUG
+
+       $self->{OUT} .= $isEndAnchor{$tag} ? '</a>' : "[/$save]";
+
+}
+
+sub find_html_end {
+    my($tag, $buf) = @_;
+    my $out;
+       my $canon;
+
+    my $open  = "<$tag ";
+    my $close = "</$tag>";
+       ($canon = $tag) =~ s/_/[-_]/g;
+
+    $$buf =~ s!<$canon\s!<$tag !ig;
+    $$buf =~ s!</$canon\s*>!</$tag>!ig;
+    my $first = index($$buf, $close);
+    return undef if $first < 0;
+    my $int = index($$buf, $open);
+    my $pos = 0;
+#::logGlobal("find_html_end: tag=$tag open=$open close=$close $first=$first pos=$pos int=$int");
+    while( $int > -1 and $int < $first) {
+        $pos   = $int + 1;
+        $first = index($$buf, $close, $first + 1);
+        $int   = index($$buf, $open, $pos);
+#::logGlobal("find_html_end: tag=$tag open=$open close=$close $first=$first pos=$pos int=$int");
+    }
+#::logGlobal("find_html_end: tag=$tag open=$open close=$close $first=$first pos=$pos int=$int");
+       return undef if $first < 0;
+    $first += length($close);
+#::logGlobal("find_html_end (add close): tag=$tag open=$open close=$close $first=$first pos=$pos int=$int");
+    $out = substr($$buf, 0, $first);
+    substr($$buf, 0, $first) = '';
+    return $out;
+}
+
+sub find_matching_end {
+    my($tag, $buf) = @_;
+    my $out;
+       my $canon;
+
+    my $open  = "[$tag ";
+    my $close = "[/$tag]";
+       ($canon = $tag) =~ s/_/[-_]/g;
+
+    $$buf =~ s!\[$canon\s![$tag !ig;
+    $$buf =~ s!\[/$canon\]![/$tag]!ig;
+    my $first = index($$buf, $close);
+    if ($first < 0) {
+               if($Gobble{$tag}) {
+                       $out = $$buf;
+                       $$buf = '';
+                       return $out;
+               }
+               return undef;
+       }
+    my $int = index($$buf, $open);
+    my $pos = 0;
+    while( $int > -1 and $int < $first) {
+        $pos   = $int + 1;
+        $first = index($$buf, $close, $first + 1);
+        $int   = index($$buf, $open, $pos);
+    }
+    $out = substr($$buf, 0, $first);
+    $first = $first < 0 ? $first : $first + length($close);
+    substr($$buf, 0, $first) = '';
+    return $out;
+}
+
+# Passed some string that might be HTML-style attributes
+# or might be positional parameters, does the right thing
+sub _find_tag {
+       my ($buf, $attrhash, $attrseq) = (@_);
+       my $old = 0;
+       my $eaten = '';
+       my %attr;
+       my @attrseq;
+       while ($$buf =~ s|^(([a-zA-Z][-a-zA-Z0-9._]*)\s*)||) {
+               $eaten .= $1;
+               my $attr = lc $2;
+               my $val;
+               $old = 0;
+               # The attribute might take an optional value (first we
+               # check for an unquoted value)
+               if ($$buf =~ s|(^=\s*([^\"\'\]\s][^\]\s]*)\s*)||) {
+                       $eaten .= $1;
+                       $val = $2;
+                       HTML::Entities::decode($val);
+               # or quoted by " or '
+               } elsif ($$buf =~ s|(^=\s*([\"\'])(.*?)\2\s*)||s) {
+                       $eaten .= $1;
+                       $val = $3;
+                       HTML::Entities::decode($val);
+               # truncated just after the '=' or inside the attribute
+               } elsif ($$buf =~ m|^(=\s*)$| or
+                                $$buf =~ m|^(=\s*[\"\'].*)|s) {
+                       $eaten = "$eaten$1";
+                       last;
+               } else {
+                       # assume attribute with implicit value, which 
+                       # means in MiniVend no value is set and the
+                       # eaten value is grown. Note that you should
+                       # never use an implicit tag when setting up an Alias.
+                       $old = 1;
+               }
+               next if $old;
+               $attrhash->{$attr} = $val;
+               push(@attrseq, $attr);
+       }
+       unshift(@$attrseq, @attrseq);
+       return ($eaten);
+}
+
+# checks for implicit tags
+# INT is special in that it doesn't get pushed on @attrseq
+sub implicit {
+       my($self, $tag, $attr) = @_;
+# DEBUG
+Vend::Util::logDebug
+("check tag='$tag' attr='$attr'...")
+       if ::debug(0x2);
+# END DEBUG
+       return ('interpolate', 1, 1) if $attr eq 'int';
+       return ($attr, undef) unless defined $Implicit{$tag} and $Implicit{$tag}{$attr};
+       my $imp = $Implicit{$tag}{$attr};
+       return ($attr, $imp) if $imp =~ s/^$attr=//i;
+       return ( $Implicit{$tag}{$attr}, $attr );
+}
+
+if (! $Global::VendRoot) {
+       tag_reference();
+}
+
+1;
+__END__
+accessories
+%%
+
+The C<[accessories ...]> tag allows you to access MiniVend's option
+attribute facility in any of several ways.
+
+If passed any of the optional arguments, initiates special processing
+of item attributes based on entries in the product database.
+
+MiniVend allows item attributes to be set for each ordered item. This
+allows a size, color, or other modifier to be attached to a line
+item in the shopping cart. Previous attribute values can be resubmitted
+by means of a hidden field on a form.
+
+The C<catalog.cfg> file directive I<UseModifier> is used to set
+the name of the modifier or modifiers. For example
+
+    UseModifier        size color
+
+will attach both a size and color attribute to each item code that
+is ordered.
+
+B<IMPORTANT NOTE:> You may not use the following names for attributes:
+
+    item  group  quantity  code  mv_ib  mv_mi  mv_si
+
+You can also set it in scratch with the mv_UseModifier
+scratch variable -- C<[set mv_UseModifier]size color[/set]> has the
+same effect as above. This allows multiple options to be set for
+products. Whichever one is in effect at order time will be used.
+Be careful, you cannot set it more than once on the same page.
+Setting the C<mv_separate_items> or global directive I<SeparateItems>
+places each ordered item on a separate line, simplifying attribute
+handling. The scratch setting for C<mv_separate_items> has the same
+effect.
+
+The modifier value is accessed in the C<[item-list]> loop with the
+C<[item-modifier attribute]> tag, and form input fields are placed with the
+C<[modifier-name attribute]> tag. This is similar to the way that quantity
+is handled.
+
+NOTE: You must be sure that no fields in your forms have digits appended to
+their names if the variable is the same name as the attribute name you
+select, as the C<[modifier-name size]> variables will be placed in the
+user session as the form variables size0, size1, size2, etc.
+
+You can use the C<[loop arg="attribute attribute"]> list to reference
+multiple display or selection fields for modifiers (in MiniVend 3.0,
+you can have it automatically generated --see below). The modifier value
+can then be used to select data from an arbitrary database for attribute
+selection and display.
+
+MiniVend 3.0 will automatically generate the above select box
+when the C<[accessories <code> size]> or C<[item-accessories size]>
+tags are called. They have the syntax:
+
+   [item_accessories attribute*, type*, field*, database*, name*, outboard*]
+  
+   [accessories code attribute*, type*, field*, database*, name*, outboard*]
+
+=over 4
+
+=item code
+
+Not needed for item-accessories, this is the product code of the item to
+reference.
+=item attribute
+
+The item attribute as specified in the UseModifier configuration
+directive. Typical are C<size> or C<color>.
+
+=item type
+
+The action to be taken. One of:
+
+  select          Builds a dropdown <SELECT> menu for the attribute.
+                  NOTE: This is the default.
+  multiple        Builds a multiple dropdown <SELECT> menu for the
+                  attribute.  The size is equal to the number of
+                  option choices.
+                   
+  display         Shows the label text for *only the selected option*.
+   
+  show            Shows the option choices (no labels) for the option.
+   
+  radio           Builds a radio box group for the item, with spaces
+                  separating the elements.
+                   
+  radio nbsp      Builds a radio box group for the item, with &nbsp;
+                  separating the elements.
+                   
+  radio left n    Builds a radio box group for the item, inside a
+                  table, with the checkbox on the left side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+                   
+  radio right n   Builds a radio box group for the item, inside a
+                  table, with the checkbox on the right side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+
+   
+  check           Builds a checkbox group for the item, with spaces
+                  separating the elements.
+                   
+  check nbsp      Builds a checkbox group for the item, with &nbsp;
+                  separating the elements.
+                   
+  check left n    Builds a checkbox group for the item, inside a
+                  table, with the checkbox on the left side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+                   
+  check right n   Builds a checkbox group for the item, inside a
+                  table, with the checkbox on the right side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+
+The default is 'select', which builds an HTML select form entry for
+the attribute.  Also recognized is 'multiple', which generates a
+multiple-selection drop down list, 'show', which shows the list of
+possible attributes, and 'display', which shows the label text for the
+selected option only.
+
+=item field
+
+The database field name to be used to build the entry (usually a field
+in the products database).  Defaults to a field named the same as the
+attribute.
+
+=item database
+
+The database to find B<field> in, defaults to the first products file
+where the item code is found.
+
+=item name
+
+Name of the form variable to use if a form is being built. Defaults to
+mv_order_B<attribute> -- i.e.  if the attribute is B<size>, the form
+variable will be named B<mv_order_size>.
+
+=item outboard
+
+If calling the item-accessories tag, and you wish to select from an
+outboard database, you can pass the key to use to find the accessory
+data.
+
+=back
+
+When called with an attribute, the database is consulted and looks for
+a comma-separated list of attribute options. They take the form:
+
+    name=Label Text, name=Label Text*
+
+The label text is optional -- if none is given, the B<name> will
+be used.
+
+If an asterisk is the last character of the label text, the item is
+the default selection. If no default is specified, the first will be
+the default. An example:
+
+    [item_accessories color]
+
+This will search the product database for a field named "color". If
+an entry "beige=Almond, gold=Harvest Gold, White*, green=Avocado" is found,
+a select box like this will be built:
+
+    <SELECT NAME="mv_order_color">
+    <OPTION VALUE="beige">Almond
+    <OPTION VALUE="gold">Harvest Gold
+    <OPTION SELECTED>White
+    <OPTION VALUE="green">Avocado
+    </SELECT>
+
+In combination with the C<mv_order_item> and C<mv_order_quantity> variables
+this can be used to allow entry of an attribute at time of order.
+
+If used in an item list, and the user has changed the value, the generated
+select box will automatically retain the current value the user has selected.
+
+The value can then be displayed with C<[item-modifier size]> on the
+order report, order receipt, or any other page containing an
+C<[item-list]>. 
+
+
+When called with an attribute, the database is consulted and looks for
+a comma-separated list of attribute options. They take the form:
+
+    name=Label Text, name=Label Text*
+
+The label text is optional -- if none is given, the B<name> will
+be used.
+
+If an asterisk is the last character of the label text, the item is
+the default selection. If no default is specified, the first will be
+the default. An example:
+
+    [accessories TK112 color]
+
+This will search the product database for a field named "color". If
+an entry "beige=Almond, gold=Harvest Gold, White*, green=Avocado" is found,
+a select box like this will be built:
+
+    <SELECT NAME="mv_order_color">
+    <OPTION VALUE="beige">Almond
+    <OPTION VALUE="gold">Harvest Gold
+    <OPTION SELECTED>White
+    <OPTION VALUE="green">Avocado
+    </SELECT>
+
+In combination with the I<mv_order_item> and I<mv_order_quantity> variables
+this can be used to allow entry of an attribute at time of order.
+
+=over 4
+
+=item EMULATING WITH LOOP
+
+Below is a fragment from a shopping basket display form which 
+shows a selectable size with "sticky" setting. Note that this
+would always be contained within the C<[item_list]> C<[/item-list]>
+pair.
+
+    <SELECT NAME="[modifier-name size]">
+    <OPTION  [selected [modifier-name size] S]> S
+    <OPTION  [selected [modifier-name size] M]> M
+    <OPTION  [selected [modifier-name size] L]> L
+    <OPTION [selected [modifier-name size] XL]> XL
+    </SELECT>
+
+It could just as easily be done with a radio button group combined
+with the C<[checked ...]> tag.
+
+The above is essentially the same as would be output with the
+[item-accessories size] tag if the product database field C<size>
+contained the value C<S, M, L, XL>. (The [item-accessories size] tag
+is much more efficient.)
+
+=item DEPRECATED BEHAVIOR
+
+If not given one of the optional arguments, expands into the value of
+the accessories database entry for the product identified by I<code>
+as found in the products database.
+
+=back
+
+%%%
+area
+%%
+
+Named call example:
+
+    <A HREF="[area href=scan arg="
+                                     se=Impressionists
+                                     sf=category
+                                "
+                            ]">Impressionists</A>
+
+Positional call example:
+
+    <A HREF="[area ord/basket]">Check basket</A>
+
+HTML example:
+
+    <A MV="area dir/page" HREF="dir/page.html">
+
+Produces the URL to call a MiniVend page, without the surrounding
+A HREF notation. This can be used to get control of your HREF items,
+perhaps to place an ALT string or a Javascript construct.
+
+It was originally named C<area> because it also can be used in a
+client-side image map, and has an alias of C<href>. The two links below
+are identical in operation:
+
+   <A HREF="[area href=catalog]" ALT="Main catalog page">Catalog Home</A>
+   <A HREF="[href href=catalog]" ALT="Main catalog page">Catalog Home</A>
+
+The optional I<arg> is used just as in the I<page> tag.
+
+The optional C<form> argument allows you to encode a form in the link.
+
+        <A HREF="[area form="
+                mv_order_item=99-102
+                mv_order_size=L
+                mv_order_quantity=1
+                mv_separate_items=1
+                mv_todo=refresh"
+        ]"> Order t-shirt in Large size </A>
+
+The two form values I<mv_session_id> and I<mv_arg> are automatically added
+when appropriate. (I<mv_arg> is the C<arg> parameter for the tag.)
+
+If the parameter C<href> is not supplied, I<process> is used, causing
+normal MiniVend form processing.
+
+This would generate a form that ordered item number 99-102 on
+a separate line (C<mv_separate_items> being set), with size C<L>,
+in quantity 2. Since the page is not set, you will go to the default
+shopping cart page -- equally you could set C<mv_orderpage=yourpage>
+to go to C<yourpage>.
+
+You must have TolerateGet set (which is the default) and 
+all normal MiniVend form caveats apply -- you must have an action,
+you must supply a page if you don't want to go to the default,
+etc.
+
+You can theoretically submit any form with this, though none of the
+included values can have newlines or trailing whitespace. If you want
+to do something like that you will have to write a UserTag.
+
+%%%
+areatarget
+%%
+
+Inserts a Vend URL in a format to provide a targeted reference for a
+client-side imagemap. You set up the <AREA> tag with:
+
+      <AREA COORDS="220,0,270,20" HREF="[areatarget page frame]">
+
+If frames are enabled, this will expand to:
+
+      <AREA COORDS="220,0,270,20"
+         HREF="http://machine.company.com/vlink/page?ErTxVV8l;;38" TARGET="frame">
+
+If frames are I<not> enabled, this will expand to:
+
+      <AREA COORDS="220,0,270,20"
+         HREF="http://machine.company.com/vlink/page?ErTxVV8l;;38">
+
+B<IMPORTANT NOTE:> This tag is DEPRECATED and may disappear in future
+versions of MiniVend. Don't use it!
+
+%%%
+body
+%%
+
+Selects from the predefined color schemes and/or backgrounds set with
+C<Mv_LinkColor>, C<Mv_BgColor>, etc., and just becomes a <BODY> tag
+if none are defined. The C<extra> parameter is always appended. See
+I<CONTROLLING PAGE APPEARANCE>.
+
+This tag is mildly deprecated in that it will not be further enhanced;
+It should remain in some form through future versions of MiniVend.
+
+%%%
+buttonbar
+%%
+
+Selects from the predefined buttonbars, and is stripped if it
+doesn't exist. See I<CONTROLLING PAGE APPEARANCE>. This is somewhat 
+superceded by Variable and [include filename].
+
+B<IMPORTANT NOTE:> This tag is DEPRECATED and may disappear in future
+versions of MiniVend. Don't use it!
+
+%%%
+calc
+%%
+
+syntax: [calc] EXPRESSION [/calc]
+
+Starts a region where the arguments are calculated according to normal
+arithmetic symbols. For instance:
+
+    [calc] 2 + 2 [/calc]
+
+will display:
+
+    4
+
+The [calc] tag is really the same as the [perl] tag, except
+that it doesn't accept arguments, is more efficient to parse, and
+is interpolated at a higher precedence.
+
+TIP: The [calc] tag will remember variable values inside one page, so
+you can do the equivalent of a memory store and memory recall for a loop.
+
+ASP NOTE: There is almost no reason to use this tag in a [perl] or ASP section.
+
+%%%
+cart
+%%
+
+Sets the name of the current shopping cart for display of shipping, price,
+total, subtotal, and nitems tags. (The C<shipping> tag doesn't use the cart.)
+If you wish to use a different price for the cart, all of the above except
+[shipping] will reflect the normal price field.
+
+%%%
+checked
+%%
+
+You can provide a "memory" for drop-down menus, radio buttons, and
+checkboxes with the [checked] and [selected] tags.
+
+    <INPUT TYPE=radio NAME=foo
+            VALUE=on [checked name=foo value=on default=1]>
+    <INPUT TYPE=radio NAME=foo
+            VALUE=off [checked name=foo value=off]>
+
+This will output CHECKED if the variable C<var_name> is equal to
+C<value>. Not case sensitive.
+
+The C<default> parameter, if true (non-zero and non-blank), will cause
+the box to be checked if the variable has never been defined.
+
+Note that CHECKBOX items will never submit their value if not checked,
+so the box will not be reset. You must do something like:
+
+    <INPUT TYPE=checkbox NAME=foo
+            VALUE=1 [checked name=foo value=1 default=1]>
+    [value name=foo set=""]
+
+%%%
+comment
+%%
+
+syntax: [comment] code [/comment]
+
+Comments out MiniVend tags (and anything else) from a page. The contents
+are not displayed unless DisplayComments is set in minivend.cfg.
+
+%%%
+currency
+%%
+
+When passed a value of a single number, formats it according to the
+currency specification. For instance:
+
+    [currency]4[/currency]
+
+will display:
+
+    4.00
+
+Uses the I<Locale> and I<PriceCommas> settings as appropriate, and can
+contain a [calc] region. If the optional "convert" parameter is set,
+it will convert the value according to PriceDivide> for the current
+locale. If Locale is set to C<fr_FR>, and F<PriceDivide> for C<fr_FR>
+is 0.167, the following sequence
+
+    [currency convert=1] [calc] 500.00 + 1000.00 [/calc] [/currency]
+
+will cause the number 8.982,04 to be displayed.
+
+%%%
+data
+%%
+
+Returns the value of the field in a database table, or from the C<session>
+namespace. If the optional B<value> is supplied, the entry will be
+changed to that value.  If the option increment* is present, the field
+will be atomically incremented with the value in B<value>. Use negative
+numbers in C<value> to decrement.
+
+If a DBM-based database is to be modified, it must be flagged writable
+on the page calling the write tag. Use [tag flag write]products[/tag]
+to mark the C<products> database writable, for example.
+B<This must be done before any access to that table.>
+
+In addition, the C<[data ...]> tag can access a number of elements in
+the MiniVend session database:
+
+    accesses           Accesses within the last 30 seconds
+    arg                The argument passed in a [page ...] or [area ...] tag
+    browser            The user browser string
+    cybercash_error    Error from last CyberCash operation
+    cybercash_result   Hash of results from CyberCash (access with usertag)
+    host               MiniVend's idea of the host (modified by DomainTail)
+    last_error         The last error from the error logging
+    last_url           The current MiniVend path_info
+    logged_in          Whether the user is logged in (add-on UserDB feature)
+    pageCount          Number of unique URLs generated
+    prev_url           The previous path_info
+    referer            HTTP_REFERER string
+    ship_message       The last error messages from shipping
+    source             Source of original entry to MiniVend
+    time               Time (seconds since Jan 1, 1970) of last access
+    user               The REMOTE_USER string
+    username           User name logged in as (UserDB feature)
+
+NOTE: Databases will hide session values, so don't name a database "session".
+or you won't be able to use the [data ...] tag to read them. Case is
+sensitive, so in a pinch you could call the database "Session", but it
+would be better not to.
+
+%%%
+default
+%%
+
+Returns the value of the user form variable C<variable> if it is non-empty.
+Otherwise returns C<default>, which is the string "default" if there is no
+default supplied. Got that?
+
+%%%
+description
+%%
+
+Expands into the description of the product identified by code as found in the
+products database. This is the value of the database field that corresponds to
+the C<catalog.cfg> directive C<DescriptionField>. If there is more than one
+products file defined, they will be searched in order unless constrained by the
+optional argument B<base>.
+
+%%%
+discount
+%%
+
+Product discounts can be set upon display of any page. The discounts
+apply only to the customer receiving them, and are of one of three types:
+
+    1. A discount for one particular item code (code/key is the item-code)
+    2. A discount applying to all item codes (code/key is ALL_ITEMS)
+    3. A discount applied after all items are totaled
+       (code/key is ENTIRE_ORDER)
+
+The discounts are specified via a formula. The formula is scanned for
+the variables $q and $s, which are substituted for with the item
+I<quantity> and I<subtotal> respectively. In the case of the item and
+all items discount, the formula must evaluate to a new subtotal for all
+items I<of that code> that are ordered. The discount for the entire
+order is applied to the entire order, and would normally be a monetary
+amount to subtract or a flat percentage discount.
+
+Discounts are applied to the effective price of the product, including
+any quantity discounts.
+
+To apply a straight 20% discount to all items:
+
+    [discount ALL_ITEMS] $s * .8 [/discount]
+
+or with named attributes:
+
+    [discount code=ALL_ITEMS] $s * .8 [/discount]
+
+To take 25% off of only item 00-342:
+
+    [discount 00-342] $s * .75 [/discount]
+
+To subtract $5.00 from the customer's order:
+
+    [discount ENTIRE_ORDER] $s - 5 [/discount]
+
+To reset a discount, set it to the empty string: 
+
+    [discount ALL_ITEMS][/discount]
+
+Perl code can be used to apply the discounts. Here is an example of a
+discount for item code 00-343 which prices the I<second> one ordered at
+1 cent:
+
+    [discount 00-343]
+    return $s if $q == 1;
+    my $p = $s/$q;
+    my $t = ($q - 1) * $p;
+    $t .= 0.01;
+    return $t;
+    [/discount]
+
+If you want to display the discount amount, use the [item-discount] tag.
+
+    [item-list]
+    Discount for [item-code]: [item-discount]
+    [/item-list]
+
+Finally, if you want to display the discounted subtotal in a way that
+doesn't correspond to a standard MiniVend tag, you can use the [calc] tag:
+
+    [item-list]
+    Discounted subtotal for [item-code]: [currency][calc]
+                                            [item-price noformat] * [item-quantity]
+                                            [/calc][/currency]
+    [/item-list]
+
+%%%
+field
+%%
+
+HTML example: <PARAM MV=field MV.COL=column MV.ROW=key>
+
+Expands into the value of the field I<name> for the product
+identified by I<code> as found by searching the products database.
+It will return the first entry found in the series of I<Product Files>.
+the products database. If you want to constrain it to a particular
+database, use the [data base name code] tag.
+
+Note that if you only have one ProductFile C<products>, which is the default,
+C<[field column key]> is the same as C<[data products column key]>.
+
+%%%
+file
+%%
+
+Inserts the contents of the named file. The file should normally
+be relative to the catalog directory -- file names beginning with
+/ or .. are not allowed if the MiniVend server administrator
+has set I<NoAbsolute> to C<Yes>.
+
+The optional C<type> parameter will do an appropriate ASCII translation
+on the file before it is sent.
+
+%%%
+fly_list
+%%
+
+Defines an area in a random page which performs the flypage lookup
+function, implementing the tags below.
+
+   [fly-list]
+    (contents of flypage.html)
+   [/fly-list]
+
+If you place the above around the contents of the demo flypage, 
+in a file named C<flypage2.html>, it will make these two calls
+display identical pages:
+
+    [page 00-0011] One way to display the Mona Lisa [/page]
+    [page flypage2 00-0011] Another way to display the Mona Lisa [/page]
+
+%%%
+framebase
+%%
+
+Outputs a <BASE FRAME="name"> tag only if MiniVend is in frames mode.
+It should be used within the HTML <HEAD> section.
+
+=head2 frames_off
+
+Turns off the frames processing option. This can be used to disable
+frames, perhaps as a clickable option for users. It is persistent for
+the entire session, or until counteracted with a [frames_on] tag.
+
+B<IMPORTANT NOTE:> This tag is DEPRECATED and may disappear in future
+versions of MiniVend. Don't use it!
+
+IMPORTANT NOTE:  This doesn't turn of frames in your browser! If
+you let a TARGET tag escape, it will probably cause a new window
+to be opened, or other types of anomalous operation.
+
+=head2 frames_on
+
+Turns on the frames processing option, which is disabled by default.
+The proper way to use this is to put it ONLY in the first page which
+is loaded by frame-based browsers, as part of the initial frame load.
+It is persistent for the entire session, or until counteracted with a
+[frames_off] tag.
+
+B<IMPORTANT NOTE:> This tag is DEPRECATED and may disappear in future
+versions of MiniVend. Don't use it!
+
+%%%
+if
+%%
+
+Named call example: [if type="type" term="field" op="op" compare="compare"]
+
+Positional call example: [if type field op compare]
+
+negated: [if type="!type" term="field" op="op" compare="compare"]
+
+Positional call example: [if !type field op compare]
+
+Allows conditional building of HTML based on the setting of various MiniVend
+session and database values. The general form is:
+
+    [if type term op compare]
+    [then]
+                                If true, this is printed on the document.
+                                The [then] [/then] is optional in most
+                                cases. If ! is prepended to the type
+                                setting, the sense is reversed and
+                                this will be output for a false condition.
+    [/then]
+    [elsif type term op compare]
+                                Optional, tested when if fails
+    [/elsif] 
+    [else]
+                                Optional, printed when all above fail
+    [/else]
+    [/if]
+
+The C<[if]> tag can also have some variants:
+
+    [if explicit][condition] CODE [/condition]
+                Displayed if valid Perl CODE returns a true value.
+    [/if]
+
+You can do some Perl-style regular expressions:
+
+    [if value name =~ /^mike/]
+                                This is the if with Mike.
+    [elsif value name =~ /^sally/]
+                                This is an elsif with Sally.
+    [/elsif]
+    [elsif value name =~ /^pat/]
+                                This is an elsif with Pat.
+    [/elsif]
+    [else]
+                                This is the else, no name I know.
+    [/else]
+    [/if]
+
+While the new tag syntax works for C<[if ...]>, it is more convenient
+to use the old in most cases.  It will work fine with both parsers.
+The only exception is if you are planning on doing a test on the 
+results of another tag sequence:
+    
+    [if value name =~ /[value b_name]/]
+        Shipping name matches billing name.
+    [/if]
+
+Oops!  This will not work with the new parser. You must do instead
+
+    [compat]
+    [if value name =~ /[value b_name]/]
+        Shipping name matches billing name.
+    [/if]
+    [/compat]
+
+or
+
+    [if type=value term=name op="=~" compare="/[value b_name]/"]
+        Shipping name matches billing name.
+    [/if]
+
+The latter has the advantage of working with any tag:
+
+    [if type=value term=high_water op="<" compare="[shipping]"]
+        Shipping cost is too high, charter a truck.
+    [/if]
+
+If you wish to do AND and OR operations, you will have to use 
+C<[if explicit]>. This allows complex testing and parsing of
+values.
+
+There are many test targets available:
+
+=over 4
+
+=item config Directive
+
+The MiniVend configuration variables. These are set
+by the directives in your MiniVend configuration file (or
+the defaults).
+
+    [if config CreditCardAuto]
+    Auto credit card validation is enabled.
+    [/if]
+
+=item data  database::field::key
+
+The MiniVend databases. Retrieves a field in the database and
+returns true or false based on the value.
+
+    [if data products::size::99-102]
+    There is size information.
+    [else]
+    No size information.
+    [/else]
+    [/if]
+
+    [if data products::size::99-102 =~ /small/i]
+    There is a small size available.
+    [else]
+    No small size available.
+    [/else]
+    [/if]
+
+=item discount
+
+Checks to see if a discount is present for an item.
+
+    [if discount 99-102]
+    Item is discounted.
+    [/if]
+
+=item explicit
+
+A test for an explicit value. If perl code is placed between
+a [condition] [/condition] tag pair, it will be used to make
+the comparison. Arguments can be passed to import data from
+user space, just as with the [perl] tag.
+
+    [if explicit]
+    [condition]
+        $country = '[value country]';
+        return 1 if $country =~ /u\.?s\.?a?/i;
+        return 0;
+    [/condition]
+    You have indicated a US address.
+    [else]
+    You have indicated a non-US address. 
+    [/else]
+    [/if]
+
+This example is a bit contrived, as the same thing could be
+accomplished with [if value country =~ /u\.?s\.?a?/i], but
+you will run into many situations where it is useful.
+
+This will work for I<Variable> values:
+
+    [if type=explicit compare="__MYVAR__"] .. [/if]
+
+=item file
+
+Tests for existence of a file. Useful for placing image
+tags only if the image is present.
+
+    [if file /home/user/www/images/[item-code].gif]
+    <IMG SRC="[item-code].gif">
+    [/if]
+
+The C<file> test requires that the I<SafeUntrap> directive contains
+C<ftfile> (which is the default).
+
+=item items
+
+The MiniVend shopping carts. If not specified, the cart
+used is the main cart. Usually used as a litmus test to
+see if anything is in the cart, for example:
+
+  [if items]You have items in your shopping cart.[/if]
+  
+  [if items layaway]You have items on layaway.[/if]
+
+=item ordered
+
+Order status of individual items in the MiniVend shopping
+carts. If not specified, the cart used is the main cart.
+The following items refer to a part number of 99-102.
+
+  [if ordered 99-102] ... [/if]
+    Checks the status of an item on order, true if item
+    99-102 is in the main cart.
+
+  [if ordered 99-102 layaway] ... [/if]
+    Checks the status of an item on order, true if item
+    99-102 is in the layaway cart.
+
+  [if ordered 99-102 main size] ... [/if]
+    Checks the status of an item on order in the main cart,
+    true if it has a size attribute.
+
+  [if ordered 99-102 main size =~ /large/i] ... [/if]
+    Checks the status of an item on order in the main cart,
+    true if it has a size attribute containing 'large'.
+    THE CART NAME IS REQUIRED IN THE OLD SYNTAX. The new
+    syntax for that one would be:
+
+    [if type=ordered term="99-102" compare="size =~ /large/i"]
+
+    To make sure it is exactly large, you could use:
+
+    [if ordered 99-102 main size eq 'large'] ... [/if]
+
+  [if ordered 99-102 main lines] ... [/if]
+      Special case -- counts the lines that the item code is
+      present on. (Only useful, of course, when mv_separate_items
+      or SeparateItems is defined.)
+
+=item scratch
+
+The MiniVend scratchpad variables, which can be set
+with the [set name]value[/set] element. 
+
+    [if scratch mv_separate_items]
+    Ordered items will be placed on a separate line.
+    [else]
+    Ordered items will be placed on the same line.
+    [/else]
+    [/if]
+
+=item session
+
+The MiniVend session variables. Of particular interest
+are I<login>, I<frames>, I<secure>, and I<browser>.
+
+=item validcc
+
+A special case, takes the form [if validcc no type exp_date].
+Evaluates to true if the supplied credit card number, type
+of card, and expiration date pass a validity test. Does
+a LUHN-10 calculation to weed out typos or phony 
+card numbers.
+
+=item value
+
+The MiniVend user variables, typically set in search,
+control, or order forms. Variables beginning with C<mv_>
+are MiniVend special values, and should be tested/used
+with caution.
+
+=back
+
+The I<field> term is the specifier for that area. For example, [if session
+frames] would return true if the C<frames> session parameter was set.
+
+As an example, consider buttonbars for frame-based setups. It would be
+nice to display a different buttonbar (with no frame targets) for sessions
+that are not using frames:
+
+    [if session frames]
+        [buttonbar 1]
+    [else]
+        [buttonbar 2]
+    [/else]
+    [/if]
+
+Another example might be the when search matches are displayed. If
+you use the string '[value mv_match_count] titles found', it will display
+a plural for only one match. Use:
+
+    [if value mv_match_count != 1]
+        [value mv_match_count] matches found.
+    [else]
+        Only one match was found.
+    [/else]
+    [/if]
+
+The I<op> term is the compare operation to be used. Compare operations are
+as in Perl:
+
+    ==  numeric equivalence
+    eq  string equivalence
+    >   numeric greater-than
+    gt  string greater-than
+    <   numeric less-than
+    lt  string less-than
+    !=  numeric non-equivalence
+    ne  string equivalence
+
+Any simple perl test can be used, including some limited regex matching.
+More complex tests are best done with C<[if explicit]>.
+
+=over 4
+
+=item [then] text [/then]
+
+This is optional if you are not nesting if conditions, as the text
+immediately following the [if ..] tag is used as the conditionally
+substituted text. If nesting [if ...] tags you should use a [then][/then]
+on any outside conditions to ensure proper interpolation.
+
+=item [elsif type field op* compare*]
+
+named attributes: [elsif type="type" term="field" op="op" compare="compare"]
+
+Additional conditions for test, applied if the initial C<[if ..]> test
+fails.
+
+=item [else] text [/else]
+
+The optional else-text for an if or if_field conditional.
+
+=item [condition] text [/condition]
+
+Only used with the [if explicit] tag. Allows an arbitrary expression
+B<in Perl> to be placed inside, with its return value interpreted as
+the result of the test. If arguments are added to [if explicit args],
+those will be passed as arguments are in the I<[perl]> construct.
+
+=back
+
+%%%
+import
+%%
+
+Named attributes:
+
+    [import table=table_name
+            type=(TAB|PIPE|CSV|%%|LINE)
+            continue=(NOTES|UNIX|DITTO)
+            separator=c]
+
+Import one or more records into a database. The C<type> is any
+of the valid MiniVend delimiter types, with the default being defined
+by the setting of I<Delimiter>. The table must already be a defined
+MiniVend database table; it cannot be created on the fly. (If you need
+that, it is time to use SQL.)
+
+The C<type> of C<LINE> and C<continue> setting of C<NOTES> is particularly
+useful, for it allows you to name your fields and not have to remember
+the order in which they appear in the database. The following two imports
+are identical in effect:
+
+    [import table=orders]
+    code: [value mv_order_number]
+    shipping_mode: [shipping-description]
+    status: pending
+    [/import]
+  
+    [import table=orders]
+    shipping_mode: [shipping-description]
+    status: pending
+    code: [value mv_order_number]
+    [/import]
+
+The C<code> or key must always be present, and is always named C<code>.
+
+If you do not use C<NOTES> mode, you must import the fields in the
+same order as they appear in the ASCII source file.
+
+The C<[import ....] TEXT [/import]> region may contain multiple records.
+If using C<NOTES> mode, you must use a separator, which by default is
+a form-feed character (^L).
+
+%%%
+include
+%%
+
+Same as C<[file name]> except interpolates for all MiniVend tags
+and variables.
+
+%%%
+item_accessories
+%%
+
+MiniVend allows item attributes to be set for each ordered item. This
+allows a size, color, or other modifier to be attached to a common
+part number. If multiple attributes are set, then they should be
+separated by commas. Previous attribute values can be saved by means
+of a hidden field on a form, and multiple attributes for each item
+can be I<stacked> on top of each other.
+
+The configuration file directive I<UseModifier> is used to set
+the name of the modifier or modifiers. For example
+
+    UseModifier        size,color
+
+will attach both a size and color attribute to each item code that
+is ordered.
+
+B<IMPORTANT NOTE:> You may not use the following names for attributes:
+
+    item  group  quantity  code  mv_ib  mv_mi  mv_si
+
+You can also set it in scratch with the mv_UseModifier
+scratch variable -- [set mv_UseModifier]size color[/set] has the
+same effect as above. This allows multiple options to be set for
+products. Whichever one is in effect at order time will be used.
+Be careful, you cannot set it more than once on the same page.
+Setting the I<mv_separate_items> or global directive I<SeparateItems>
+places each ordered item on a separate line, simplifying attribute
+handling. The scratch setting for C<mv_separate_items> has the same
+effect.
+
+The modifier value is accessed in the [item-list] loop with the
+[item-modifier attribute] tag, and form input fields are placed with the
+[modifier-name attribute] tag. This is similar to the way that quantity
+is handled, except that attributes can be "stacked" by setting multiple
+values in an input form.
+
+You cannot define a modifier name of I<code> or I<quantity>, as they
+are already used. You must be sure that no fields in your forms
+have digits appended to their names if the variable is the same name
+as the attribute name you select, as the [modifier-name size] variables
+will be placed in the user session as the form variables size0, size1,
+size2, etc. 
+
+You can use the [loop item,item,item] list to reference multiple display
+or selection fields for modifiers (in MiniVend 3.0, you can have it
+automatically generated --see below). The modifier value can then be
+used to select data from an arbitrary database for attribute selection
+and display.
+
+Below is a fragment from a shopping basket display form which 
+shows a selectable size with "sticky" setting. Note that this
+would always be contained within the [item_list] [/item-list]
+pair.
+
+    <SELECT NAME="[modifier-name size]">
+    <OPTION  [selected [modifier-name size] S]> S
+    <OPTION  [selected [modifier-name size] M]> M
+    <OPTION  [selected [modifier-name size] L]> L
+    <OPTION [selected [modifier-name size] XL]> XL
+    </SELECT>
+
+It could just as easily be done with a radio button group combined
+with the [checked ...] tag.
+
+MiniVend 3.0 will automatically generate the above select box
+when the [accessories <code> size] or [item-accessories size]
+tags are called. They have the syntax:
+
+   [item_accessories attribute*, type*, field*, database*, name*, outboard*]
+  
+   [accessories code attribute*, type*, field*, database*, name*, outboard*]
+
+=over 4
+
+=item code
+
+Not needed for item-accessories, this is the product code of the item to
+reference.
+=item attribute
+
+The item attribute as specified in the UseModifier configuration
+directive. Typical are C<size> or C<color>.
+
+=item type
+
+The action to be taken. One of:
+
+  select          Builds a dropdown <SELECT> menu for the attribute.
+                  NOTE: This is the default.
+  multiple        Builds a multiple dropdown <SELECT> menu for the
+                  attribute.  The size is equal to the number of
+                  option choices.
+                   
+  display         Shows the label text for *only the selected option*.
+   
+  show            Shows the option choices (no labels) for the option.
+   
+  radio           Builds a radio box group for the item, with spaces
+                  separating the elements.
+                   
+  radio nbsp      Builds a radio box group for the item, with &nbsp;
+                  separating the elements.
+                   
+  radio left n    Builds a radio box group for the item, inside a
+                  table, with the checkbox on the left side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+                   
+  radio right n   Builds a radio box group for the item, inside a
+                  table, with the checkbox on the right side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+
+   
+  check           Builds a checkbox group for the item, with spaces
+                  separating the elements.
+                   
+  check nbsp      Builds a checkbox group for the item, with &nbsp;
+                  separating the elements.
+                   
+  check left n    Builds a checkbox group for the item, inside a
+                  table, with the checkbox on the left side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+                   
+  check right n   Builds a checkbox group for the item, inside a
+                  table, with the checkbox on the right side. If "n"
+                  is present and is a digit from 2 to 9, it will align
+                  the options in that many columns.
+
+The default is 'select', which builds an HTML select form entry for
+the attribute.  Also recognized is 'multiple', which generates a
+multiple-selection drop down list, 'show', which shows the list of
+possible attributes, and 'display', which shows the label text for the
+selected option only.
+
+=item field
+
+The database field name to be used to build the entry (usually a field
+in the products database).  Defaults to a field named the same as the
+attribute.
+
+=item database
+
+The database to find B<field> in, defaults to the first products file
+where the item code is found.
+
+=item name
+
+Name of the form variable to use if a form is being built. Defaults to
+mv_order_B<attribute> -- i.e.  if the attribute is B<size>, the form
+variable will be named B<mv_order_size>.
+
+=item outboard
+
+If calling the item-accessories tag, and you wish to select from an
+outboard database, you can pass the key to use to find the accessory
+data.
+
+=back
+
+When called with an attribute, the database is consulted and looks for
+a comma-separated list of attribute options. They take the form:
+
+    name=Label Text, name=Label Text*
+
+The label text is optional -- if none is given, the B<name> will
+be used.
+
+If an asterisk is the last character of the label text, the item is
+the default selection. If no default is specified, the first will be
+the default. An example:
+
+    [item_accessories color]
+
+This will search the product database for a field named "color". If
+an entry "beige=Almond, gold=Harvest Gold, White*, green=Avocado" is found,
+a select box like this will be built:
+
+    <SELECT NAME="mv_order_color">
+    <OPTION VALUE="beige">Almond
+    <OPTION VALUE="gold">Harvest Gold
+    <OPTION SELECTED>White
+    <OPTION VALUE="green">Avocado
+    </SELECT>
+
+In combination with the I<mv_order_item> and I<mv_order_quantity> variables
+this can be used to allow entry of an attribute at time of order.
+
+If used in an item list, and the user has changed the value, the generated
+select box will automatically retain the current value the user has selected.
+
+The value can then be displayed with [item-modifier size] on the
+order report, order receipt, or any other page containing an
+[item_list]. 
+
+%%%
+item_list
+%%
+
+Within any page, the [item_list cart*] element shows a list of all the
+items ordered by the customer so far. It works by repeating the source
+between [item_list] and [/item_list] once for each item ordered.
+
+NOTE: The special tags that reference item within the list are not normal
+MiniVend tags, do not take named attributes, and cannot be contained in
+an HTML tag (other than to substitute for one of its values or provide
+a conditional container). They are interpreted only inside their
+corresponding list container. Normal MiniVend tags can be interspersed,
+though they will be interpreted I<after> all of the list-specific tags.
+
+Between the item_list markers the following elements will return
+information for the current item:
+
+=over 4
+
+=item [if-data table column]
+
+If the database field C<column> in table I<table> is non-blank, the
+following text up to the [/if_data] tag is substituted. This can be
+used to substitute IMG or other tags only if the corresponding source
+item is present. Also accepts a [else]else text[/else] pair for the
+opposite condition.
+
+=item [if-data ! table column]
+
+Reverses sense for [if-data].
+
+=item [/if-data]
+
+Terminates an [if_data table column] element.
+
+=item [if-field fieldname]
+
+If the products database field I<fieldname> is non-blank, the following
+text up to the [/if_field] tag is substituted. If you have more than
+one products database table (see I<ProductFiles>), it will check
+them in order until a matching key is found. This can be used to
+substitute IMG or other tags only if the corresponding source
+item is present. Also accepts a [else]else text[/else] pair
+for the opposite condition.
+
+=item [if-field ! fieldname]
+
+Reverses sense for [if-field].
+
+=item [/if-field]
+
+Terminates an [if_field fieldname] element.
+
+=item [item-accessories attribute*, type*, field*, database*, name*]
+
+Evaluates to the value of the Accessories database entry for the item.
+If passed any of the optional arguments, initiates special processing
+of item attributes based on entries in the product database.
+
+=item [item-code]
+
+Evaluates to the product code for the current item.
+
+=item [item-data database fieldname]
+
+Evaluates to the field name I<fieldname> in the arbitrary database
+table I<database>, for the current item.
+
+=item [item-description]
+
+Evaluates to the product description (from the products file)
+for the current item.
+
+=item [item-field fieldname]
+
+Evaluates to the field name I<fieldname> in the products database,
+for the current item. If the item is not found in the first of the
+I<ProductFiles>, all will be searched in sequence.
+
+=item [item-increment]
+
+Evaluates to the number of the item in the match list. Used
+for numbering search matches or order items in the list.
+
+=item [item-last]tags[/item-last]
+
+Evaluates the output of the MiniVend tags encased inside the tags,
+and if it evaluates to a numerical non-zero number (i.e. 1, 23, or -1)
+then the list iteration will terminate. If the evaluated number is
+B<negative>, then the item itself will be skipped. If the evaluated
+number is B<positive>, then the item itself will be shown but will be
+last on the list.
+
+      [item-last][calc]
+        return -1 if '[item-field weight]' eq '';
+        return 1 if '[item-field weight]' < 1;
+        return 0;
+        [/calc][/item-last]
+
+If this is contained in your C<[item-list]> (or C<[search-list]> or
+flypage) and the weight field is empty, then a numerical C<-1> will
+be output from the [calc][/calc] tags; the list will end and the item
+will B<not> be shown. If the product's weight field is less than 1,
+a numerical 1 is output.  The item will be shown, but will be the last
+item shown. (If it is an C<[item-list]>, any price for the item will
+still be added to the subtotal.) NOTE: no HTML style.
+
+=item [item-modifier attribute]
+
+Evaluates to the modifier value of C<attribute> for the current item.
+
+=item [item-next]tags[/item_next]
+
+Evaluates the output of the MiniVend tags encased inside, and
+if it evaluates to a numerical non-zero number (i.e. 1, 23, or -1) then
+the item will be skipped with no output. Example:
+
+      [item-next][calc][item-field weight] < 1[/calc][/item-next]
+
+If this is contained in your C<[item-list]> (or C<[search-list]> or flypage)
+and the product's weight field is less than 1, then a numerical C<1> will
+be output from the [calc][/calc] operation. The item will not be shown. (If
+it is an C<[item-list]>, any price for the item will still be added to the
+subtotal.)
+
+=item [item-price n* noformat*]
+
+Evaluates to the price for quantity C<n> (from the products file)
+of the current item, with currency formatting. If the optional "noformat"
+is set, then currency formatting will not be applied.
+
+=item [discount-price n* noformat*]
+
+Evaluates to the discount price for quantity C<n> (from the products file)
+of the current item, with currency formatting. If the optional "noformat"
+is set, then currency formatting will not be applied. Returns regular
+price if not discounted.
+
+=item [item-discount]
+
+Returns the difference between the regular price and the discounted price.
+
+=item [item-quantity]
+
+Evaluates to the quantity ordered for the current item.
+
+=item [item-subtotal]
+
+Evaluates to the subtotal (quantity * price) for the current item.
+Quantity price breaks are taken into account.
+
+=item [modifier-name attribute]
+
+Evaluates to the name to give an input box in which the
+customer can specify the modifier to the ordered item.
+
+=item [quantity-name]
+
+Evaluates to the name to give an input box in which the
+customer can enter the quantity to order.
+
+=back
+
+%%%
+lookup
+%%
+
+This is essentially same as the following:
+
+    [if value name]
+    [then][value name][/then]
+    [else][data database column row][/else]
+    [/if]
+
+%%%
+loop
+%%
+
+HTML example: 
+
+    <TABLE><TR MV="loop 1 2 3"><TD>[loop-code]</TD></TR></TABLE>
+
+Returns a string consisting of the LIST, repeated for every item in a
+comma-separated or space-separated list. Operates in the same fashion
+as the [item-list] tag, except for order-item-specific values. Intended
+to pull multiple attributes from an item modifier -- but can be useful
+for other things, like building a pre-ordained product list on a page.
+
+Loop lists can be nested reliably in MiniVend 3.06 by using the 
+with="tag" parameter. New syntax:
+
+    [loop arg="A B C"]
+        [loop with="-a" arg="[loop-code]1 [loop-code]2 [loop-code]3"]
+            [loop with="-b" arg="X Y Z"]
+                [loop-code-a]-[loop-code-b]
+            [/loop]
+        [/loop]
+    [/loop]
+
+An example in the old syntax:
+
+    [compat]
+    [loop 1 2 3]   
+        [loop-a 1 2 3 ]
+        [loop-b 1 2 3]
+            [loop-code].[loop-code-a].[loop-code-b]
+        [/loop-b]
+        [/loop-a]
+    [/loop]
+    [/compat]
+
+All loop items in the inner loop-a loop need to have the C<with> value
+appended, i.e. C<[loop-field-a name]>, C<[loop-price-a]>, etc. Nesting
+is arbitrarily large, though it will be slow for many levels.
+
+You can do an arbitrary search with the search="args" parameter, just
+as in a one-click search:
+
+    [loop search="se=Americana/sf=category"]
+        [loop-code] [loop-field title]
+    [/loop]
+
+The above will show all items with a category containing the whole world
+"Americana", and will work the same in both old and new syntax.
+
+=over 4
+
+=item [if-loop-data table field] IF [else] ELSE [/else][/if-loop-field]
+
+Outputs the IF if the C<field> in C<table> is non-empty, and the ELSE (if any)
+otherwise.
+
+=item [if-loop-field field] IF [else] ELSE [/else][/if-loop-field]
+
+Outputs the B<IF> if the C<field> in the C<products> table is non-empty,
+and the B<ELSE> (if any) otherwise.
+
+=item [loop-accessories]
+
+Evaluates to the value of the Accessories database entry for
+the item.
+
+=item [loop-change marker]
+
+Same as I<[on_change]> but within loop lists.
+
+=item [loop-code]
+
+Evaluates to the product code for the current item.
+
+=item [loop-data database fieldname]
+
+Evaluates to the field name I<fieldname> in the arbitrary database
+table I<database>, for the current item.
+
+=item [loop-description]
+
+Evaluates to the product description (from the products file)
+for the current item.
+
+=item [loop-field fieldname]
+
+Evaluates to the field name I<fieldname> in the database,  for
+the current item.
+
+=item [loop-increment]
+
+Evaluates to the number of the item in the list. Used
+for numbering items in the list.
+
+=item [loop-last]tags[/loop-last]
+
+Evaluates the output of the MiniVend tags encased inside,
+and if it evaluates to a numerical non-zero number (i.e. 1, 23, or -1)
+then the loop iteration will terminate. If the evaluated number is
+B<negative>, then the item itself will be skipped. If the evaluated
+number is B<positive>, then the item itself will be shown but will be
+last on the list.
+
+      [loop-last][calc]
+        return -1 if '[loop-field weight]' eq '';
+        return 1 if '[loop-field weight]' < 1;
+        return 0;
+        [/calc][/loop-last]
+
+If this is contained in your C<[loop list]> and the weight field is empty,
+then a numerical C<-1> will be output from the [calc][/calc] tags; the
+list will end and the item will B<not> be shown. If the product's weight
+field is less than 1, a numerical 1 is output.  The item will be shown,
+but will be the last item shown.
+
+=item [loop-next]tags[/loop-next]
+
+Evaluates the output of the MiniVend tags encased inside, and
+if it evaluates to a numerical non-zero number (i.e. 1, 23, or -1) then
+the loop will be skipped with no output. Example:
+
+      [loop-next][calc][loop-field weight] < 1[/calc][/loop-next]
+
+If this is contained in your C<[loop list]> and the product's weight
+field is less than 1, then a numerical C<1> will be output from the
+[calc][/calc] operation. The item will not be shown.
+
+=item [loop-price n* noformat*]
+
+Evaluates to the price for optional quantity n (from the products file)
+of the current item, with currency formatting. If the optional "noformat"
+is set, then currency formatting will not be applied.
+
+=back
+
+%%%
+nitems
+%%
+
+Expands into the total number of items ordered so far. Takes an
+optional cart name as a parameter.
+
+%%%
+order
+%%
+
+Expands into a hypertext link which will include the specified
+code in the list of products to order and display the order page. B<code>
+should be a product code listed in one of the "products" databases. The
+optional argument B<cart/page> selects the shopping cart the item will be
+placed in (begin with / to use the default cart C<main>) and the order page
+that will display the order. The optional argument B<database> constrains
+the order to a particular products file -- if not specified, all databases
+defined as products files will be searched in sequence for the item.
+
+Example: 
+
+  Order a [order TK112]Toaster[/order] today.
+
+%%%
+page
+%%
+
+Insert a hyperlink to the specified catalog page pg. For
+example, [page shirts] will expand into <
+a href="http://machine.company.com/cgi-bin/vlink/shirts?WehUkATn;;1">. The
+catalog page displayed will come from "shirts.html" in the
+pages directory.
+
+The additional argument will be passed to MiniVend and placed in the
+{arg} session parameter. This allows programming of a conditional page
+display based on where the link came from. The argument is then available
+with the tag [data session arg], or the embedded Perl session variable
+$Safe{'session'}->{arg}. If you set the catalog configuration option
+I<NewEscape>, which is the default, then spaces and some other characters
+will be escaped with the %NN HTTP-style notation and unescaped when the
+argument is read back into the session.
+
+A bit of magic occurs if MiniVend has built a static plain HTML page
+for the target page. Instead of generating a normal MiniVend-parsed
+page reference, a static page reference will be inserted if the user
+has accepted and sent back a cookie with the session ID.
+
+The optional C<form> argument allows you to encode a form in the link.
+
+        [page form="
+                mv_order_item=99-102
+                mv_order_size=L
+                mv_order_quantity=1
+                mv_separate_items=1
+                mv_todo=refresh"] Order t-shirt in Large size </A>
+
+The two form values I<mv_session_id> and I<mv_arg> are automatically added
+when appropriate. (I<mv_arg> is the C<arg> parameter for the tag.)
+
+If the parameter C<href> is not supplied, I<process> is used, causing
+normal MiniVend form processing. If the C<href> points to an http://
+link no MiniVend URL processing will be done, but the mv_session_id
+
+This would generate a form that ordered item number 99-102 on
+a separate line (C<mv_separate_items> being set), with size C<L>,
+in quantity 2. Since the page is not set, you will go to the default
+shopping cart page -- equally you could set C<mv_orderpage=yourpage>
+to go to C<yourpage>.
+
+You must have TolerateGet set (which is the default) and
+all normal MiniVend form caveats apply -- you must have an action,
+you must supply a page if you don't want to go to the default,
+etc.
+
+You can theoretically submit any form with this, though none of the
+included values can have newlines or trailing whitespace. If you want
+to do something like that you will have to write a UserTag.
+
+%%%
+pagetarget
+%%
+
+Same as the page element above, except it specifies an output frame to
+target if frames are turned on. The name B<is> case-sensitive, and if
+it doesn't exist a new window will be popped up. This is the same as
+the [page ...] tag if frames are not activated.
+For example, [pagetarget shirts main] will expand into a link like <a
+href="http://machine.company.com/cgi-bin/vlink/shirts?WehUkATn;;1" TARGET="main">. The
+catalog page displayed will come from C<shirts.html> in the
+pages directory, and be output to the C<main> frame. Be careful,
+frame names are case-sensitive.
+
+MiniVend allows you to pass a search in a URL. Just specify the
+search with the special page reference C<scan>. Here is an
+example:
+
+     [page scan
+            se=Impressionists
+            sf=category]
+        Impressionist Paintings
+     [/page]
+
+Here is the same thing from a home page (assuming /cgi-bin/vlink is
+the CGI path for MiniVend's vlink):
+
+     <A HREF="/cgi-bin/vlink/scan/se=Impressionists/sf=category">
+        Impressionist Paintings
+     </A>
+
+The two-letter abbreviations are mapped with these letters:
+
+  DL  mv_raw_dict_look
+  MM  mv_more_matches
+  SE  mv_raw_searchspec
+  ac  mv_all_chars
+  bd  mv_base_directory
+  bs  mv_begin_string
+  co  mv_coordinate
+  cs  mv_case
+  de  mv_dict_end
+  df  mv_dict_fold
+  di  mv_dict_limit
+  dl  mv_dict_look
+  do  mv_dict_order
+  dp  mv_delay_page
+  dr  mv_record_delim
+  em  mv_exact_match
+  er  mv_spelling_errors
+  fi  mv_search_file
+  fm  mv_first_match
+  fn  mv_field_names
+  hs  mv_head_skip
+  id  mv_index_delim
+  lr  mv_line_return
+  ml  mv_matchlimit
+  mm  mv_max_matches
+  mp  mv_profile
+  ms  mv_min_string
+  ne  mv_negate
+  nu  mv_numeric
+  op  mv_column_op
+  os  mv_orsearch
+  ra  mv_return_all
+  rd  mv_return_delim
+  rf  mv_return_fields
+  rg  mv_range_alpha
+  rl  mv_range_look
+  rm  mv_range_min
+  rn  mv_return_file_name
+  rs  mv_return_spec
+  rx  mv_range_max
+  se  mv_searchspec
+  sf  mv_search_field
+  sp  mv_search_page
+  sq  mv_sql_query
+  st  mv_searchtype
+  su  mv_substring_match
+  tc  mv_sort_command
+  tf  mv_sort_field
+  to  mv_sort_option
+  ty  mv_sort_crippled
+
+They can be treated just the same as form variables on the
+page, except that they can't contain spaces, '/' in a file
+name, or quote marks. These characters can be used
+in URL hex encoding, i.e. %20 is a space, %2F is a
+C</>, etc. -- C<&sp;> or C<&#32;> will not be recognized.
+If you use one of the methods below to escape these "unsafe"
+characters, you won't have to worry about this.
+
+Beginning in MiniVend 3.08, you may specify a one-click search in
+three different ways. The first is as used in previous versions, with
+the scan URL being specified completely as the page name.  The second
+two use the "argument" parameter to the C<[page ...]> or C<[area ...]>
+tags to specify the search (an argument to a scan is never valid anyway).
+
+=over 4
+
+=item Original
+
+If you wish to do an OR search on the fields category and artist
+for the strings "Surreal" and "Gogh", while matching substrings,
+you would do:
+
+ [page scan se=Surreal/se=Gogh/os=yes/su=yes/sf=artist/sf=category]
+    Van Gogh -- compare to surrealists
+ [/page]
+
+In this method of specification, to replace a / (slash) in a file name
+(for the sp, bd, or fi parameter) you must use the shorthand of ::,
+i.e. sp=results::standard. (This may not work for some browsers, so you
+should probably either put the page in the main pages directory or define
+the page in a search profile.)
+
+=item Ampersand
+
+You can substitute & for / in the specification and be able to use / and
+quotes and spaces in the specification.
+
+ [page scan se="Van Gogh"&sp=lists/surreal&os=yes&su=yes&sf=artist&sf=category]
+    Van Gogh -- compare to surrealists
+ [/page]
+
+Any "unsafe" characters will be escaped. 
+
+=item Multi-line
+
+You can specify parameters one to a line, as well. 
+
+    [page scan
+        se="Van Gogh"
+        sp=lists/surreal
+        os=yes
+        su=yes
+        sf=artist
+        sf=category
+    ] Van Gogh -- compare to surrealists [/page]
+
+Any "unsafe" characters will be escaped. You may not search for trailing
+spaces in this method; it is allowed in the other notations.
+
+=back
+
+New syntax and old syntax handle the tags the same, though if by some
+odd chance you wanted to be able to search for a C<]> (right square bracket)
+you would need to use new syntax.
+
+The optional I<arg> is used just as in the I<page> tag.
+
+=head2 [/page]
+
+Expands into </a>. Used with the page element, such as:
+
+  [page shirts]Our shirt collection[/page]. 
+
+TIP: A small efficiency boost in large pages is to just use the </A>
+tag.
+
+%%%
+perl
+%%
+
+HTML example:
+
+    <PRE mv=perl mv.arg="values browser">
+        $name = $Safe{'values'}{'name'};
+        $name = $Safe{'browser'};
+        return "Hi, $name! How do you like your $browser?
+    </PRE>
+
+Perl code can be directly embedded in MiniVend pages. The code
+is specified as [perl arguments*] any_legal_perl_code [/perl]. The
+value returned by the code will be inserted on the page.
+
+Using MiniVend variables with embedded Perl capability is not recommended
+unless you are thoroughly familiar with Perl 5 references. You can insert
+Minivend tags inside the Perl code, though when using the new syntax,
+you will need to pass an INTERPOLATE=1 parameter to have tags inside
+[perl] and [/perl] interpreted. (In the old syntax, most tags are evaluated
+before [perl], though there are exceptions.)
+
+More often you will want to use the tag access routine B<&safe_tag>, which
+takes the tag name and any arguments as parameters. This has the advantage
+of only performing the operation when the code is executed. (A few tags can't
+be used with safe_tag, notably ones accessing a database that has not
+previously been accessed on the page.)
+
+Examples:
+
+    # Simple example, old syntax
+    [perl]
+    $comments = '[value comments]';
+    [/perl]
+
+    # New syntax
+    # If the item might contain a single quote
+    [perl interpolate=1]
+    $comments = '[value comments escaped]';
+    [/perl]
+
+    # Another method to avoid escape problems
+    $comments = q{[value comments]};
+
+    # Works with all, only executed if code is reached
+    $comments = safe_tag('value', 'comments');
+
+This allows you to pass user-space variables for most needed
+operations. You can pass whole lists of items with constructs
+like:
+
+    # Perl ignores the trailing comma
+    my(%prices) = ( [item_list]
+                    '[item_code]', '[item-price]',
+                    [/item_list]);
+
+Even easier is the definition of a subroutine:
+
+    [set Thanks]
+    my($name, $number) = @_;
+    "Thanks, $name, for your order! The order number is $number.\n";
+    [/set]
+
+    # New syntax
+    [perl arg=sub interpolate=1]
+        Thanks ('[value name escaped]', '[value mv_order_number escaped]')
+    [/perl]
+
+    # Old syntax, depends on [value ...] interpolated before [perl]
+    [perl sub]
+        Thanks ('[value name escaped]', '[value mv_order_number escaped]')
+    [/perl]
+
+(The C<escaped> causes any single quotes which might be contained in the
+values to be escaped, preventing syntax errors in the case of a name like
+"O'Reilly".)
+
+The arguments that can be passed are any to all of:
+
+=over 4
+
+=item browser
+
+The browser string from the users browser, read-only. Referred
+to in your code as $Safe{browser}.
+
+=item carts
+
+Gives read-write access to all of the shopping carts. on order. This
+is an array of hashes, and includes the product code, quantity, and any
+modifiers you have specified.  Referred to in your code as a reference
+to the array, $Safe{items} or @{$Safe{items}}.
+
+    # Move contents of 'layaway' cart to main cart
+    $Safe{carts}->{main} = $Safe{carts}->{layaway};
+    $Safe{carts}->{main} = [];
+
+Careful with this -- you can lose the items on order with improper
+code, though syntax errors will be caught before the code is run.
+
+=item cgi
+
+Gives read-only access to the actual variables that were passed
+in the current CGI session. This is useful for testing what the
+user actually placed on the form, not just what MiniVend placed
+in the session. Called with
+
+  # Set if the user had a value for name in the *current* form
+  $name = $Safe{'cgi'}->{name};
+
+=item config
+
+Gives read-write access to the configuration of the catalog. USE WITH
+EXTREME CAUTION -- many of the variables are references to anonymous
+arrays and hashes. You can crash your catalog if you modify the wrong
+thing. Referred to in your code as $Safe{config}, a reference to the
+hash containing the configuration structure. If you use this, it
+is recommended that you refer frequently to the MiniVend source code.
+
+=item discount
+
+Gives read-write access to session discounts, an
+anonymous hash. Referred to in your code as $Safe->{discounts}.
+
+=item file
+
+If specified, the anchor text is a file name to read the Perl code from.
+This allows code to be maintained in separate files, though you need
+to remember that any MiniVend tags contained will generally not be
+interpolated (depending on interpolation order and use of the [[any]]
+and [post] modifiers). The file name is relative to the MiniVend base
+directory unless specified as an absolute path.
+
+=item frames
+
+The true/false value determining whether frames processing is
+enabled. Read-only -- you can set the value with [frames-off] or
+[frames-on]. Referred to in your code as $Safe{frames}.
+
+=item items
+
+Gives read-only access to the items on order, I<for the current cart>.
+This is an array of hashes, and includes the product code, quantity,
+and any modifiers you have specified. Referred to in your code as a
+reference to the array, $Safe{items} or @{$Safe{items}}.
+
+    # Product code of first item in cart
+    $item_code = $Safe{items}->[0]->{code};  
+
+    # Quantity for third item in cart
+    $item_code = $Safe{items}->[2]->{quantity};  
+
+    # Color of second item in cart
+    $item_code = $Safe{items}->[2]->{color};  
+
+=item scratch
+
+Gives read-write access to the scratch variables, a reference to an
+anonymous hash. Referred to in your code as $Safe{scratch}.
+
+=item sub
+
+If specified, the anchor text is a subroutine name and optional
+parameters to be passed. The subroutine can be defined in three
+ways; as a global subroutine (works for entire server); as a
+catalog-wide pre-defined subroutine; or in a scratchpad variable.
+All are called with the same syntax -- the arguments are passed
+in via the @_ argument array.
+
+B<IMPORTANT NOTE:> Global subroutines are not subject to the stringent
+security checking of the I<Safe> module, so almost anything goes
+there. The subroutine will be able to modify any variable in MiniVend,
+and will be able to write to read and write any file that the MiniVend
+daemon has permission to write. Though this gives great power, it should
+be used with caution. Careful! They are defined in the main minivend.cfg
+file, so should be safe from individual users in a multi-catalog system.
+
+Global subroutines are defined in I<minivend.cfg> with the
+I<GlobalSub> directive, or in user catalogs which have been
+enabled via I<AllowGlobal>. Global subroutines are much faster
+than the others as they are pre-compiled. (Faster still are I<UserTag>
+definitions.)
+
+Catalog subroutines are defined in I<catalog.cfg>, with
+the I<Sub> directive. They are subject to the stringent I<Safe.pm>
+security restrictions that are controlled by I<SafeUntrap>. If you
+wish to have default arguments supplied to them, use the I<SubArgs>
+directive.
+
+Scratch subroutines are defined in the pages, and are also subject
+to F<Safe.pm> checking. See the beginning of this section for an
+example of a subroutine definition. There is no "sub name { }" that
+surrounds it -- the subroutine is named from the name of the 
+scratch variable.
+
+=item values
+
+Gives read-write access to the user variables, including the MiniVend
+special variables, an anonymous hash. Referred to in your code as
+%{Safe{'values'}} or $Safe{'values'}->{variable}.
+
+    # Read the user's selected shipping mode
+    my $shipmode = $Safe{values}->{mv_shipmode};
+
+=back
+
+The code can be as complex as desired, but cannot use any operators
+that modify the file system or use "unsafe" operations like "system",
+"exec", or backticks. These constraints are enforced with the default
+permissions of the standard Perl module I<Safe> -- operations may
+be untrapped on a system-wide basis with the I<SafeUntrap> directive.
+
+The result of the tag will be the result of the last expression
+evaluated, just as in a subroutine. If there is a syntax error
+or other problem with the code, there will be no output.
+
+Here is a simple one which does the equivalent of the classic
+hello.pl program:
+
+    [perl] my $tmp = "Hello, world!"; $tmp; [/perl]
+
+Of course you wouldn't need to set the variable -- it is just there
+to show the capability.
+
+To echo the user's browser, but within some HTML tags:
+
+    [perl browser]
+    my $html = '<H5>';
+    $html .= $Safe{'browser'};
+    $html .= '</H5>';
+    $html;
+    [/perl]
+
+To show the user their name, and the current time:
+
+    [perl values]
+
+    my $string = "Hi, " . $Safe{values}->{'name'} ". The time is now ";
+    $string .= localtime;
+    $string;
+
+    [/perl]
+
+%%%
+post
+%%
+
+syntax: [post] DELAYED TAGS [/post]
+
+B<NOTE:> This is ignored if using the new syntax.
+
+Selects an area that will not be interpolated until after the rest of
+the page is interpolated. If followed by a number, will match a terminating
+[/post] tag with the corresponding number.
+
+%%%
+price
+%%
+
+Expands into the price of the product identified by code as found in
+the products database. If there is more than one products file defined,
+they will be searched in order unless constrained by the optional
+argument B<base>. The optional argument B<quantity> selects an entry
+from the quantity price list. To receive a raw number, with no currency
+formatting, use the option C<noformat=1>.
+
+MiniVend maintains a price in its database for every product. The price
+field is the one required field in the product database -- it is necessary
+to build the price routines.
+
+For speed, MiniVend builds the code that is used to determine a product's
+price at catalog configuration time. If you choose to change a directive
+that affects product pricing you must reconfigure the catalog.
+
+There are several ways that MiniVend can modify the price of a product during 
+normal catalog operation. Several of them require that the I<pricing.asc>
+file be present, and that you define a pricing database. You do that by
+placing the following directive in I<catalog.cfg>:
+
+  Database  pricing pricing.asc 1
+
+Configurable directives and tags with regard to pricing:
+
+=over 4
+
+=item *
+
+Quantity price breaks are configured by means of the I<PriceBreaks> and
+I<MixMatch> directives. They require a field named specifically C<price>
+in the pricing database. The B<price> field contains a space-separated
+list of prices that correspond to the quantity levels defined in the
+F<PriceBreaks> directive. If quantity is to be applied to all items in
+the shopping cart (as opposed to quantity of just that item) then the
+I<MixMatch> directive should be set to B<Yes>.
+
+=item *
+
+Individual line-item prices can be adjusted according to the value of
+their attributes. See I<PriceAdjustment> and I<CommonAdjust>. The
+pricing database B<must> be defined unless you define the F<CommonAdjust>
+behavior.
+
+=item *
+
+Product discounts for specific products, all products, or the entire
+order can be configured with the [discount ...] tag. Discounts are applied
+on a per-user basis -- you can gate the discount based on membership in a
+club or other arbitrary means. See I<Product Discounts>.
+
+=back
+
+For example, if you decided to adjust the price of T-shirt part number
+99-102 up 1.00 when the size is extra large and down 1.00 when the size is small,
+you would have the following directives defined in <catalog.cfg>:
+
+  Database          pricing pricing.asc 1
+  UseModifier       size
+  PriceAdjustment   size
+
+To enable the automatic modifier handling of MiniVend 3.0, you would
+define a size field in products.asc:
+
+  code    description   price    size
+  99-102  T-Shirt       10.00    S=Small, M=Medium, L=Large*, XL=Extra Large
+
+You would place the proper tag within your [item-list] on the shopping-basket
+or order page:
+
+    [item-accessories size]
+
+In the pricing.asc database source, you would need:
+
+  code      S       XL
+  99-102    -1.00   1.00
+
+As of MiniVend 3.06, if you want to assign a price based on the option,
+precede the number with an equals sign:
+
+  code    S       M       L       XL
+  99-102  =9.00   =10     =10     =11
+
+IMPORTANT NOTE: Price adjustments occur AFTER quantity price breaks, so
+the above would negate anything set with the I<PriceBreaks> directive/option.
+
+Numbers that begin with an equals sign (C<=>) are used as absolute
+prices and are I<interpolated for MiniVend tags first>, so you can
+use subroutines to set the price. To facilite coordination with the
+subroutine, the session variables C<item_code> and C<item_quantity> are
+set to the code and quantity of the item being evaluated. They would
+be accessed in a global subroutine with C<$Vend::Session->>C<{item_code}>
+and C<$Vend::Session->>C<{item_quantity}>.
+
+The pricing information must always come from a database because
+of security.
+
+See I<CommonAdjust> for another scheme that makes the same adjustment
+for any item having the attribute -- both schemes cannot be used at the
+same time. (This is true even if you were to change the value of 
+$Vend::Cfg->{CommonAdjust} in a subroutine -- the pricing algorithm
+is built at catalog configuration time.)
+
+%%%
+random
+%%
+
+Selects from the predefined random messages, and is stripped if none
+exist. See I<CONTROLLING PAGE APPEARANCE> in the MiniVend documentation.
+
+%%%
+rotate
+%%
+
+Selects from the predefined rotating banner messages, and is stripped if
+none exist. The optional C<ceiling> sets the highest number that will be
+selected -- likewise C<floor> sets the lowest. The default is to sequence
+through all defined rotating banners. Each user has a separate rotation
+pattern, and each floor/ceiling combination has a separate rotation
+value.
+
+%%%
+row
+%%
+
+Formats text in tables. Intended for use in emailed reports or <PRE></PRE> HTML
+areas. The parameter I<nn> gives the number of columns to use. Inside the
+row tag, [col param=value ...] tags may be used. 
+
+=over 4
+
+=item [col width=nn wrap=yes|no gutter=n align=left|right|input spacing=n]
+
+Sets up a column for use in a [row]. This parameter can only be contained
+inside a [row nn] [/row] tag pair. Any number of columns (that fit within
+the size of the row) can be defined.
+
+The parameters are:
+
+    width=nn        The column width, I<including the gutter>. Must be
+                    supplied, there is no default. A shorthand method
+                    is to just supply the number as the I<first> parameter,
+                    as in [col 20].
+        
+    gutter=n        The number of spaces used to separate the column (on
+                    the right-hand side) from the next. Default is 2.
+        
+    spacing=n       The line spacing used for wrapped text. Default is 1,
+                    or single-spaced.
+        
+    wrap=(yes|no)   Determines whether text that is greater in length than
+                    the column width will be wrapped to the next line. Default
+                    is I<yes>.
+        
+    align=(L|R|I)   Determines whether text is aligned to the left (the default),
+                    the right, or in a way that might display an HTML text
+                    input field correctly.
+
+=item [/col]
+
+Terminates the column field.
+
+=back
+
+%%%
+salestax
+%%
+
+Expands into the sales tax on the subtotal of all the items ordered so
+far for the cart, default cart is C<main>. If there is no key field to
+derive the proper percentage, such as state or zip code, it is set to
+0. If the noformat tag is present and non-zero, the raw number with no
+currency formatting will be given.
+
+%%%
+scratch
+%%
+
+Returns the contents of a scratch variable to the page.
+
+%%%
+selected
+%%
+
+You can provide a "memory" for drop-down menus, radio buttons, and
+checkboxes with the [checked] and [selected] tags.
+
+This will output SELECTED if the variable C<var_name> is equal to
+C<value>. If the optional MULTIPLE argument is present, it will
+look for any of a variety of values. Not case sensitive.
+
+Here is a drop-down menu that remembers an item-modifier
+color selection:
+
+    <SELECT NAME="color">
+    <OPTION [selected color blue]> Blue
+    <OPTION [selected color green]> Green
+    <OPTION [selected color red]> Red
+    </SELECT>
+
+Here is the same thing, but for a shopping-basket color
+selection
+
+    <SELECT NAME="[modifier-name color]">
+    <OPTION [selected [modifier-name color] blue]> Blue
+    <OPTION [selected [modifier-name color] green]> Green
+    <OPTION [selected [modifier-name color] red]> Red
+    </SELECT>
+
+%%%
+set
+%%
+
+Sets a scratchpad variable to I<value>.
+
+Most of the mv_* variables that are used for search and order conditionals are
+in another namespace -- they can be set by means of hidden fields in a
+form.
+
+You can set an order profile with:
+
+  [set checkout]
+  name=required
+  address=required
+  [/set]
+  <INPUT TYPE=hidden NAME=mv_order_profile VALUE="checkout">
+
+A search profile would be set with:
+
+  [set substring_case]
+  mv_substring_match=yes
+  mv_case=yes
+  [/set]
+  <INPUT TYPE=hidden NAME=mv_profile VALUE="substring_case">
+
+%%%
+shipping
+%%
+
+The shipping cost of the items in the basket via C<mode> -- the default
+mode is the shipping mode currently selected in the C<mv_shipmode>
+variable. See I<SHIPPING>.
+
+%%%
+shipping_description
+%%
+
+mandatory: NONE
+
+optional: B<name> is the shipping mode identifier, i.e. C<upsg>.
+
+The text description of B<mode> -- the default is the 
+shipping mode currently selected.
+
+%%%
+sql
+%%
+
+A complete array of arrays, suitable for I<eval> by Perl, can be returned
+by this query. This tag pair encloses any valid SQL query, and returns
+the results (if any) as a string representing rows and columns, in Perl
+array syntax. If placed in an embedded Perl area as:
+
+ [perl]
+
+    my $string =<<'EOF';
+ [sql array]select * from arbitrary where code <= '19'[/sql arbitrary]
+
+ EOF
+    my $ary = eval $string;
+    my $out = '';
+    my $i;
+    foreach $i (@$ary) {
+        $out .= $i->[0];
+        $out .= "<BR>";
+    }
+    $out;
+
+ [/perl]
+
+NOTE: The 'EOF' string terminator must START the line, and not
+have trailing characters. DOS users, beware of carriage returns!
+
+=head2 [sql ...]
+
+A complete hash of hashes, suitable for I<eval> by Perl, can be returned
+by this query. This tag pair encloses any valid SQL query, and returns
+the results (if any) as a string representing rows and columns, in Perl
+associative array, or hash, syntax. If placed in an embedded Perl area as:
+
+ [perl]
+
+    my $string =<<'EOF';
+ [sql hash]select * from arbitrary where code <= '19'[/sql]
+
+ EOF
+    my $hash = eval $string;
+    my $out = '';
+    my $key;
+    foreach $key (keys %$hash) {
+        $out .= $key->{field1};
+        $out .= "<BR>";
+    }
+    $out;
+
+ [/perl]
+
+=head2 [sql ...]
+
+This tag returns a set of HTML table rows with B<bold> field names at
+the top, followed by each row in a set of table cells. The <TABLE>
+and </TABLE> tags are not supplied, so you can set your own border
+and shading options. Example:
+
+  <TABLE BORDER=2>
+  [sql html]select * from arbitrary where code > '19' order by field2[/sql]
+  </TABLE>
+
+=head2 [sql ...]
+
+This tag differs from the rest in that it passes the query enclosed
+inside the tag itself. The enclosed text is then evaluated with the
+same method as with a loop list, with data items (in columns) iterated
+over for the contents of a list. The following snippet will place
+a three-column list in an HTML table:
+
+  <TABLE BORDER=2>
+  <TR><TH><B>SKU</B></TH><TH><B>Description</B></TH><TH><B>Price</B></TH>
+  [sql list
+    select * from arbitrary where code > '19' order by field2 ]
+  <TR>
+    <TD>[page [sql-code]][sql-code]</A></TD>
+    <TD>[sql-param 1]</TD>
+    <TD>[sql-param 2]</TD>
+  </TR>
+  [/sql]
+  </TABLE>
+
+It uses the same tags as in the [loop_list], except prefixed
+with C<sql>. Available are the following, in order of interpolation:
+
+  [sql_param n]        Field n of the returned query (in the row)
+  [if_sql_field fld]   Returns enclosed text only product field not empty
+  [/if_sql_field]      Terminator for above
+  [if_sql_data db fld] Returns enclosed text only if data field not empty
+  [/if_sql_field]      Terminator for above
+  [sql_increment]      Returns integer count of row
+  [sql_code]           The first field of each row returned
+  [sql_data db fld]    Database field for [sql_code]
+  [sql_description]    Product description for [sql_code]
+  [sql_field fld]      Product field for [sql_code]
+  [sql_link]           Same as item-link
+  [sql_price q*]       Price for [sql_code], optional quantity q
+
+=head2 [sql ...]
+
+A list of keys, or in fact any SQL fields, can be returned as a set of
+parameters suitable for passing to a program or list primitive. This tag pair
+encloses any valid SQL query, and returns the results (if any) as a series of
+space separated fields, enclosed in quotes. This folds the entire return
+into a single row, so it may be used as a list of keys.
+
+=head2 [sql ...]
+
+Any arbitrary SQL query can be passed with this method. No return
+text will be sent. This might be used for passing an order to an 
+order database, perhaps on the order report or receipt page. An
+example might be:
+
+ [sql set]
+     insert into orders
+     values
+      ('[value mv_order_number]',
+       '[value name escape]',
+       '[value address escape]',
+       '[value city escape]',
+       '[value state escape]',
+       '[value zip escape]',
+       '[value phone escape]',
+       '[item-list]
+         Item: [item-code] Quan: [item-quantity] Price: [item-price]
+        [/item-list]'
+      )
+ [/sql orders]
+
+The values entered by the user are escaped, which prevents errors if
+quote characters have slipped into their entry.
+
+%%%
+subtotal
+%%
+
+
+old: [subtotal cart* noformat*]
+
+mandatory: NONE
+
+optional: cart noformat
+
+Expands into the subtotal cost, exclusive of sales tax, of
+all the items ordered so far for the optional C<cart>. If the noformat
+tag is present and non-zero, the raw number with no currency formatting
+will be given.
+
+%%%
+tag
+%%
+
+Performs any of a number of operations, based on the presence of C<arg>.
+The arguments that may be given are:
+
+=over 4
+
+=item each database
+
+Returns a loop-list with every key in C<database> evaluated
+as the [loop-code]. This will return the key and field C<name>
+for every record in the C<products> database:
+
+    [tag each products][loop-code]  [loop-field name]<BR>[/tag]
+
+=item export database file* type*
+
+Exports a complete MiniVend database to its text source file (or any
+specified file). The integer C<n>, if specified, will select export in
+one of the enumerated MiniVend export formats. The following tag will
+export the products database to products.txt (or whatever you have
+defined its source file as), in the format specified by the
+I<Database> directive:
+
+    [tag export products][/tag]
+
+Same thing, except to the file products/new_products.txt:
+
+    [tag export products products/newproducts.txt][/tag]
+
+Same thing, except the export is done with a PIPE delimiter:
+
+    [tag export products products/newproducts.txt 5][/tag]
+
+The file is relative to the catalog directory, and only may be
+an absolute path name if I<NoAbsolute> is set to C<No>.
+
+=item flag arg
+
+Sets a MiniVend condition.
+
+The following enables writes on the C<products> and C<sizes> databases
+held in MiniVend internal DBM format:
+
+    [tag flag write]products sizes[/tag]
+
+SQL databases are always writable if allowed by the SQL database itself --
+in-memory databases will never be written.
+
+The [tag flag build][/tag] combination forces static build of a page, even
+if dynamic elements are contained. Similarly, the [tag flag cache][/tag]
+forces search or page caching (not usually wise).
+
+=item log dir/file
+
+Logs a message to a file, fully interpolated for MiniVend tags.
+The following tag will send every item code and description in the user's
+shopping cart to the file logs/transactions.txt:
+
+    [tag log logs/transactions.txt]
+    [item_list][item-code]  [item-description]
+    [/item_list][/tag]
+
+The file is relative to the catalog directory, and only may be
+an absolute path name if I<NoAbsolute> is set to C<No>.
+
+=item mime description_string
+
+Returns a MIME-encapsulated message with the boundary as employed
+in the other mime tags, and the C<description_string> used as the 
+Content-Description. For example
+
+   [tag mime My Plain Text]Your message here.[/tag]
+
+will return
+
+  Content-Type: TEXT/PLAIN; CHARSET=US-ASCII
+  Content-ID: [sequential, lead as in mime boundary]
+  Content-Description: My Plain Text
+  
+  Your message here.
+
+When used in concert with [tag mime boundary], [tag mime header], and
+[tag mime id], allows MIME attachments to be included -- typically with
+PGP-encrypted credit card numbers. See the demo page ord/report.html
+for an example.
+
+=item mime boundary
+
+Returns a MIME message boundary with unique string keyed on
+session ID, page count, and time.
+
+=item mime header
+
+Returns a MIME message header with the proper boundary for that
+session ID, page count, and time.
+
+=item mime id
+
+Returns a MIME message id with the proper boundary for that
+session ID, page count, and time.
+
+=item show_tags
+
+The encased text will not be substituted for with MiniVend tags, 
+with < and [ characters changed to C<&>#lt; and C<&>#91; respectively.
+
+    [tag show_tags][value whatever][/tag]
+
+=item time
+
+Formats the current time according to POSIX strftime arguments.
+The following is the string for Thursday, April 30, 1997.
+
+    [tag time]%A, %B %d, %Y[/tag]
+
+=item touch 
+
+Touches a database to allow use of the tag_data() routine in 
+user-defined subroutines.  If this is not done, the routine
+will error out if the database has not previously been accessed
+on the page.
+
+    [tag touch products][/tag]
+
+=back
+
+%%%
+total_cost
+%%
+
+Expands into the total cost of all the items in the current shopping cart,
+including sales tax (if any).
+
+%%%
+userdb
+%%
+
+MiniVend provides a C<[userdb ...]> tag to access the UserDB functions.
+
+ [userdb
+        function=function_name
+        username="username"*
+        password="password"*
+        verify="password"*
+        oldpass="old password"*
+        shipping="fields for shipping save"
+        billing="fields for billing save"
+        preferences="fields for preferences save"
+        force_lower=1
+        param1=value*
+        param2=value*
+        ...
+        ]
+
+* Optional
+
+It is normally called in an C<mv_click> or C<mv_check> setting, as in:
+
+    [set Login]
+    mv_todo=return
+    mv_nextpage=welcome
+    [userdb function=login]
+    [/set]
+
+    <FORM ACTION="[process-target]" METHOD=POST>
+    <INPUT TYPE=hidden NAME=mv_click VALUE=Login>
+    Username <INPUT NAME=mv_username SIZE=10>
+    Password <INPUT NAME=mv_password SIZE=10>
+    </FORM>
+
+There are several global parameters that apply to any use of
+the C<userdb> functions. Most importantly, by default the database
+table is set to be I<userdb>. If you must use another table name,
+then you should include a C<database=table> parameter with any
+call to C<userdb>. The global parameters (default in parens):
+
+    database     Sets user database table (userdb)
+    show         Show the return value of certain functions
+                 or the error message, if any (0)
+    force_lower  Force possibly upper-case database fields
+                 to lower case session variable names (0)
+    billing      Set the billing fields (see Accounts)
+    shipping     Set the shipping fields (see Address Book)
+    preferences  Set the preferences fields (see Preferences)
+    bill_field   Set field name for accounts (accounts)
+    addr_field   Set field name for address book (address_book)
+    pref_field   Set field name for preferences (preferences)
+    cart_field   Set field name for cart storage (carts)
+    pass_field   Set field name for password (password)
+    time_field   Set field for storing last login time (time)
+    expire_field Set field for expiration date (expire_date)
+    acl          Set field for simple access control storage (acl)
+    file_acl     Set field for file access control storage (file_acl)
+    db_acl       Set field for database access control storage (db_acl)
+
+%%%
+value
+%%
+
+HTML examples:
+
+   <PARAM MV="value name">
+   <INPUT TYPE="text" NAME="name" VALUE="[value name]">
+
+Expands into the current value of the customer/form input field named
+by field. If C<flag> is present, single quotes will be escaped with a
+backslash; this allows you to contain the C<[value ...]> tag within
+single quotes. (It is somewhat better to use other quoting methods.)
+When the value is returned, any MiniVend tags present in the value will
+be escaped. This prevents users from entering MiniVend tags in form values,
+which would be a serious security risk.
+
+If the C<set> value is present, the form variable value will be set
+to it and the empty string returned. Use this to "uncheck" a checkbox
+or set other form variable values to defaults. B<NOTE:> This is only
+available in new-style tags, for safety reasons.
+
+%%%
+value_extended
+%%
+
+Named call example:
+
+   [value-extended 
+            name=formfield
+            outfile=filename*
+            ascii=1*
+            yes="Yes"*
+            no="No"*
+            joiner="char|string"*
+            test="isfile|length|defined"*
+            index="N|N..N|*"
+            file_contents=1*
+            elements=1*]
+
+Expands into the current value of the customer/form input field named
+by field. If there are multiple elements of that variable, it will return
+the value at C<index>; by default all joined together with a space.
+
+If the variable is a file variable coming from a multipart/form-data
+file upload, then the contents of that upload can be returned to the 
+page or optionally written to the C<outfile>.
+
+=over 4
+
+=item name
+
+The form variable NAME. If no other parameters are present, then the 
+value of the variable will be returned. If there are multiple elements,
+then by default they will all be returned joined by a space. If C<joiner>
+is present, then they will be joined by its value.
+
+In the special case of a file upload, the value returned is the name
+of the file as passed for upload.
+
+=item joiner
+
+The character or string that will join the elements of the array. Will
+accept string literals such as "\n" or "\r".
+
+=item test
+
+Three tests -- C<isfile> returns true if the variable is a file upload.
+C<length> returns the length. C<defined> returns whether the value
+has ever been set at all on a form.
+
+=item index
+
+The index of the element to return if not all are wanted. This is
+useful especially for pre-setting multiple search variables. If set
+to C<*>, will return all (joined by C<joiner>). If a range, such
+as C<0 .. 2>, will return multiple elements.
+
+=item file_contents
+
+Returns the contents of a file upload if set to a non-blank, non-zero value.
+If the variable is not a file, returns nothing.
+
+=item outfile
+
+Names a file to write the contents of a file upload to. It will not
+accept an absolute file name; the name must be relative to the catalog
+directory. If you wish to write images or other files that would go to
+HTML space, you must use the HTTP server's C<Alias> facilities or 
+make a symbolic link.
+
+=item ascii
+
+To do an auto-ASCII translation before writing the C<outfile>, set
+the C<ascii> parameter to a non-blank, non-zero value. Default is no
+translation.
+
+=item yes
+
+The value that will be returned if a test is true or a file is
+written successfully. Defaults to C<1> for tests and the empty
+string for uploads.
+
+=item no
+
+The value that will be returned if a test is false or a file write
+fails. Defaults to the empty string.
+
+=back
+
+%%%
+BEGIN
+%%
+
+=head1 MINIVEND TAG REFERENCE
+
+There are dozens of MiniVend pre-defined tag functions. If you don't see
+just what you need, you can use C<USER DEFINED TAGS> to create tags just as
+powerful as the pre-defined ones.
+
+There are two styles of tag -- HTML/new, and old. Old style is a legacy
+from prior versions of MiniVend and is no longer in standard use, but
+its positional syntax can I<usually> still be used in New/HTML mode
+for convenience.
+
+In the new style, you can specify constructs inside an HTML tag:
+
+    <TABLE MV="if items">
+    <TR MV="item-list">
+    <TD> [item-code] </TD>
+    <TD> [item-description] </TD>
+    <TD> [item-price] </TD>
+    </TR></TABLE>
+
+The above will loop over any items in the shopping cart, displaying
+their part number, description, and price, but only IF there are items
+in the cart.
+
+The same thing can be achieved with:
+
+    [if items]
+    <TABLE>
+    [item-list]
+    <TR>
+    <TD> [item-code] </TD>
+    <TD> [item-description] </TD>
+    <TD> [item-price] </TD>
+    </TR>
+    [/item-list]</TABLE>
+    [/if]
+
+To use the new more regular syntax by default, set the I<NewTags>
+directive to C<Yes>. The demo catalog is distributed with C<NewTags Yes>
+starting at MiniVend 3.07.
+
+In most cases, tags specified in the old positional fashion will work
+the same in the new style. The only time you will need to modify them
+is when there is some ambiguity as to which parameter is which (usually
+due to whitespace), or when you need to use the output of a tag as the
+attribute parameter for another tag.
+
+B<TIP:> This will not work in the new style as it did in the old:
+
+    [page scan se=[scratch somevar]]
+
+To get the output of the C<[scratch somevar]> interpreted, you must
+place it within a named and quoted attribute:
+
+    [page href=scan arg="se=[scratch somevar]"]
+
+What is done with the results of the tag depends on whether it is a
+I<container> or I<standalone> tag. A container tag is one which has
+an end tag, i.e. C<[tag] stuff [/tag]>. A standalone tag has no end
+tag, as in [area href=somepage].  (Note that [page ...] and [order ..]
+are B<not> container tags.)
+
+A container tag will have its output re-parsed for more MiniVend tags
+by default. If you wish to inhibit this behavior, you must explicitly
+set the attribute B<reparse> to 0. (Prior to MiniVend 3.09, B<reparse>
+did not exist.) Note that you will almost always wish the default action.
+
+With some exceptions ([include], [calc], [currency], and [buttonbar ..]
+among them) the output of a standalone tag will not be re-interpreted
+for MiniVend tag constructs. All tags accept the INTERPOLATE=1 tag
+modifier, which causes the interpretation to take place. It is frequent
+that you will B<not> want to interpret the contents of a [set variable]
+TAGS [/set] pair, as that might contain tags which should only be upon
+evaluating an order profile, search profile, or I<mv_click> operation. If
+you wish to perform the evaluation at the time a variable is set, you
+would use [set name=variable interpolate=1] TAGS [/set].
+
+To use the new syntax only on a particular page, place B<one> C<[new]>
+tag in your page. Likewise, to use old syntax when new is the default,
+place B<one> C<[old]> tag in the page.
+
+If you have regions of the page which work under the old style and fail
+with the new style, you can surround them with [compat] [/compat] tag
+pair. This will evaluate that region only with the old style repeated
+interpolation.
+
+=head1 TAGS
+
+Each MiniVend tag is show below. Calling information is defined
+for the main tag. Certain tags are not standalone, i.e.:
+
+    [if-data ... ]
+    [if-field ... ]
+    [on-change ... ]
+    [item-... ]            --> [item-list], [fly-list], [search-list]
+    [quantity-name ... ]
+    [modifier-name ... ]   --> [item-list]
+     
+    [on-change ... ]       --> [search-list]
+                          
+    [if-loop-... ]        
+    [loop-... ]            --> [loop]
+                          
+    [if-sql-... ]         
+    [sql-... ]             --> [sql]
+                          
+    [if-loop-... ]        
+    [loop-... ]            --> [loop]
+                          
+    [if-sql-... ]         
+    [sql-... ]             --> [sql]
+
+They are only interpreted within their container and are
+defined within the container.
+
+
+%%%
+END
+%%
+
+=head1 User-defined Tags
+
+MiniVend 3.04 allows the definition of user tags when using the new
+parsed HTML syntax (a [new] tag is on the page).  They will not work
+with the old syntax. 3.06 adds the tags on a server-wide basis, defined
+in C<minivend.cfg>.
+
+To define a tag that is catalog-specific, place I<UserTag> directives in
+your catalog.cfg file. For server-wide tags, define them in minivend.cfg.
+Catalog-specific tags take precedence if both are defined -- in fact,
+you can override the base MiniVend tag set with them. The directive
+takes the form:
+
+   UserTag  tagname  property  value
+
+where C<tagname> is the name of the tag, C<property> is the attribute
+(described below), and C<value> is the value of the property for that
+tagname.
+
+The user tags can either be based on Perl subroutines or just be
+aliases for existing tags. Some quick examples are below.
+
+An alias:
+
+    UserTag product_name Alias     data products title
+
+This will change [product_name 99-102] into [data products title 99-102],
+which will output the C<title> database field for product code C<99-102>.
+Don't use this with C<[item-data ...]> and C<[item-field ...]>, as they
+are parsed separately.  You can do C<[product-name [item-code]]>, though.
+
+A simple subroutine:
+
+    UserTag company_name Routine   sub { "Your company name" }
+
+When you place a [company-name] tag in a MiniVend page, the text 
+C<Your company name> will be substituted.
+
+A subroutine with a passed text as an argument:
+
+    UserTag caps   Routine   sub { return "\U@_" }
+    UserTag caps   HasEndTag 
+
+The tag [caps]This text should be all upper case[/caps] will become
+C<THIS TEXT SHOULD BE ALL UPPER CASE>.
+
+Here is a useful one you might wish to use:
+
+    UserTag quick_table HasEndTag
+    UserTag quick_table Interpolate
+    UserTag quick_table Order   border
+    UserTag quick_table Routine <<EOF
+    sub {
+        my ($border,$input) = @_;
+        $border = " BORDER=$border" if $border;
+        my $out = "<TABLE ALIGN=LEFT$border>";
+        my @rows = split /\n+/, $input;
+        my ($left, $right);
+        for(@rows) {
+            $out .= '<TR><TD ALIGN=RIGHT VALIGN=TOP>';
+            ($left, $right) = split /\s*:\s*/, $_, 2;
+            $out .= '<B>' unless $left =~ /</;
+            $out .= $left;
+            $out .= '</B>' unless $left =~ /</;
+            $out .= '</TD><TD VALIGN=TOP>';
+            $out .= $right;
+            $out .= '</TD></TR>';
+            $out .= "\n";
+        }
+        $out .= '</TABLE>';
+    }
+    EOF
+
+Called with:
+
+    [quick-table border=2]
+    Name: [value name]
+    City: [value city][if value state], [value state][/if] [value country]
+    [/quick_table]
+
+The properties for UserTag are are:
+
+=over 4
+
+=item Alias
+
+An alias for an existing (or other user-defined) tag. It takes the
+form:
+
+    UserTag tagname Alias    tag to insert
+
+An Alias is the only property that does not require a I<Routine>
+to process the tag.
+
+=item attrAlias
+
+An alias for an existing attribute for defined tag. It takes the
+form:
+
+    UserTag tagname attrAlias   alias attr
+
+As an example, the standard MiniVend C<value> tag takes a named
+attribute of C<name> for the variable name, meaning that C<[value name=var]>
+will display the value of form field C<var>. If you put this line
+in catalog.cfg:
+
+    UserTag value attrAlias   identifier name
+
+then C<[value identifier=var]> will be an equivalent tag.
+
+=item CanNest
+
+Notifies MiniVend that this tag must be checked for nesting.
+Only applies to tags that have I<HasEndTag> defined, of course.
+NOTE: Your routine must handle the subtleties of nesting, so
+don't use this unless you are quite conversant with parsing
+routines.  See the routines C<tag_loop_list> and C<tag_if> in 
+lib/Vend/Interpolate.pm for an example of a nesting tag.
+
+    UserTag tagname CanNest
+
+=item HasEndTag
+
+Defines an ending [/tag] to encapsulate your text -- the text in
+between the beginning C<[tagname]> and ending C<[/tagname]> will
+be the last argument sent to the defined subroutine.
+
+    UserTag tagname HasEndTag
+
+=item Implicit
+
+This defines a tag as implicit, meaning it can just be an C<attribute> 
+instead of an C<attribute=value> pair. It must be a recognized attribute
+in the tag definition, or there will be big problems. Use this with caution!
+
+    UserTag tagname Implicit attribute value
+
+If you want to set a standard include file to a fixed value by default,
+but don't want to have to specify C<[include file="/long/path/to/file"]>
+every time, you can just put:
+
+    UserTag include Implicit file file=/long/path/to/file
+
+and C<[include file]> will be the equivalent. You can still specify
+another value with C[include file="/another/path/to/file"]
+
+=item InsertHTML
+
+This attribute makes HTML tag output be inserted into the containing
+tag, in effect adding an attribute=value pair (or pairs).
+
+    UserTag tagname InsertHTML   htmltag  mvtag|mvtag2|mvtagN
+
+In MiniVend's standard tags, among others, the <OPTION ...> tag has the
+[selected ..] and [checked ...] tags included with them, so
+that you can do:
+
+   <INPUT TYPE=checkbox
+        MV="checked mvshipmode upsg" NAME=mv_shipmode> UPS Ground shipping
+
+to expand to this:
+
+   <INPUT TYPE=checkbox CHECKED NAME=mv_shipmode> UPS Ground shipping
+
+Providing, of course, that C<mv_shipmode> B<is> equal to C<upsg>.
+If you want to turn off this behavior on a per-tag basis, add the
+attribute mv.noinsert=1 to the tag on your page.
+
+=item InsideHTML
+
+To make a container tag be placed B<after> the containing
+HTML tag, use the InsideHTML setting.
+
+    UserTag tagname InsideHTML   htmltag  mvtag|mvtag2|mvtagN
+
+In MiniVend's standard tags, the only InsideHTML tag is the
+<SELECT> tag when used with I<loop>, which causes this:
+
+   <SELECT MV="loop upsg upsb upsr" NAME=mv_shipmode>
+   <OPTION VALUE="[loop-code]"> [shipping-desc [loop-code]]
+   </SELECT>
+
+to expand to this:
+
+   <SELECT NAME=mv_shipmode>
+   [loop upsg upsb upsr]
+   <OPTION VALUE="[loop-code]"> [shipping-desc [loop-code]]
+   [/loop]
+   </SELECT>
+
+Without the InsideHTML setting, the [loop ...] would have been B<outside>
+of the select -- not what you want.  If you want to turn off this
+behavior on a per-tag basis, add the attribute mv.noinside=1 to the tag
+on your page.
+
+=item Interpolate
+
+The behavior for this attribute depends on whether the tag is a container
+(i.e. C<HasEndTag> is defined). If it is not a container, the C<Interpolate>
+attribute causes the B<the resulting HTML> from the C<UserTag> will be
+re-parsed for more MiniVend tags.  If it is a container, C<Interpolate>
+causes the contents of the tag to be parsed B<before> the tag routine
+is run.
+
+    UserTag tagname Interpolate
+
+=item InvalidateCache
+
+If this is defined, the presence of the tag on a page will prevent
+search cache, page cache, and static builds from operating on the
+page.
+
+    UserTag tagname InvalidateCache
+
+It does not override [tag flag build][/tag], though.
+
+=item Order
+
+The optional arguments that can be sent to the tag. This defines not only
+the order in which they will be passed to I<Routine>, but the name of
+the tags. If encapsulated text is appropriate (I<HasEndTag> is set),
+it will be the last argument.
+
+    UserTag tagname Order param1 param2
+
+=item PosRoutine
+
+Identical to the Routine argument -- a subroutine that will be called when
+the new syntax is not used for the call, i.e. C<[usertag argument]> instead
+of C<[usertag ARG=argument]>. If not defined, I<Routine> is used, and MiniVend
+will usually do the right thing.
+
+=item ReplaceAttr
+
+Works in concert with InsertHTML, defining a B<single> attribute which
+will be replaced in the insertion operation..
+
+  UserTag tagname ReplaceAttr  htmltag attr
+
+An example is the standard HTML <A HREF=...> tag. If you want to use the
+MiniVend tag C<[area pagename]> inside of it, then you would normally
+want to replace the HREF attribute. So the equivalent to the following
+is defined within MiniVend:
+
+  UserTag  area  ReplaceAttr  a  href
+
+Causing this
+
+    <A MV="area pagename" HREF="a_test_page.html">
+
+to become
+
+    <A HREF="http://yourserver/cgi/simple/pagename?X8sl2lly;;44">
+when intepreted.
+    
+=item ReplaceHTML
+
+For HTML-style tag use only. Causes the tag containing the MiniVend tag to
+be stripped and the result of the tag to be inserted, for certain tags.
+For example:
+
+  UserTag company_name Routine sub { my $l = shift; return "$l: XYZ Company" }
+  UserTag company_name HasEndTag
+  UserTag company_name ReplaceHTML  b    company_name
+
+<BR> is the HTML tag, and "company_name" is the MiniVend tag.
+At that point, the usage:
+
+    <B MV="company-name"> Company </B>  --->>  Company: XYZ Company
+
+Tags not in the list will not be stripped:
+
+    <I MV="company-name"> Company </I> --->>  <I>Company: XYZ Company</I>
+
+=item Routine
+
+An inline subroutine that will be used to process the arguments of the tag. It
+must not be named, and will be allowed to access unsafe elements only if
+the C<minivend.cfg> parameter I<AllowGlobal> is set for the catalog.
+
+    UserTag tagname Routine  sub { "your perl code here!" }
+
+The routine may use a "here" document for readability:
+
+    UserTag tagname Routine <<EOF
+    sub {
+        my ($param1, $param2, $text) = @_;
+        return "Parameter 1 is $param1, Parameter 2 is $param2";
+    }
+    EOF
+
+The usual I<here documents> caveats apply.
+
+Parameters defined with the I<Order> property will be sent to the routine
+first, followed by any encapsulated text (I<HasEndTag> is set).
+
+=back
+
+Note that the UserTag facility, combined with AllowGlobal, allows the
+user to define tags just as powerful as the standard MiniVend tags.
+This is not recommended for the novice, though -- keep it simple. 8-)
+
index 2d0ae67..09a6b49 100644 (file)
@@ -1,6 +1,6 @@
 # Config.pm - Configure Minivend
 #
-# $Id: Config.pm,v 1.64 1999/08/05 03:51:17 mike Exp $
+# $Id: Config.pm,v 1.65 1999/08/10 09:20:04 mike Exp $
 # 
 # Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
 # Copyright 1996-1999 by Michael J. Heins <mikeh@iac.net>
@@ -40,7 +40,7 @@ use Fcntl;
 use Vend::Parse;
 use Vend::Util;
 
-$VERSION = substr(q$Revision: 1.64 $, 10);
+$VERSION = substr(q$Revision: 1.65 $, 10);
 
 for( qw(search refresh cancel return secure unsecure submit control checkout) ) {
        $Global::LegalAction{$_} = 1;
@@ -236,6 +236,7 @@ sub catalog_directives {
        ['SubArgs',                      'variable',             ''],
        ['Replace',                      'replace',              ''],
        ['Variable',             'variable',             ''],
+       ['Member',                       'variable',             ''],
        ['WritePermission',  'permission',       'user'],
        ['ReadPermission',   'permission',       'user'],
        ['SessionExpire',    'time',             '1 day'],
@@ -310,6 +311,7 @@ sub catalog_directives {
     ['BackendOrder',    undef,              ''],
     ['SalesTax',                undef,              ''],
     ['NewReport',       'yesno',                        'Yes'],
+    ['StaticDBM',               undef,              ''],
     ['Static',                  'yesno',            'No'],
     ['StaticAll',               'yesno',            'No'],
     ['StaticDepth',             undef,              '1'],
@@ -595,7 +597,7 @@ CONFIGLOOP: {
     while(<Vend::CONFIG>) {
                chomp;                  # zap trailing newline,
                if(/^\s*#include\s+(.+)/) {
-                       push @include, glob($1);
+                       push @include, grep -f $_, glob($1);
                        next;
                }
                s/^\s*#.*//;    # comments,
@@ -901,7 +903,7 @@ sub global_config {
     while(<Vend::GLOBAL>) {
                chomp;                  # zap trailing newline,
                if(/^\s*#include\s+(.+)/) {
-                       push @include, glob($1);
+                       push @include, grep -f $_, glob($1);
             next;
         }
                s/^\s*#.*//;            # comments,
@@ -1838,7 +1840,7 @@ sub save_variable {
         $c = ${"Global::$var"};
     }
 
-       if ($var eq 'Variable') {
+       if ($var eq 'Variable' || $var eq 'Member') {
                $value =~ s/^\s*(\w+)\s*//;
                $name = $1;
                return 1 if defined $c->{'save'}->{$name};
index 63920a3..2bc2542 100644 (file)
@@ -356,7 +356,7 @@ sub unfix {
        }
        my($var,$split,$index) = split /:/, $def->{attribute};
        my $join = $split;
-::logError("ECML unfix: attribute=$def->{attribute}, join=|$join|, split=$split, index=$index var=$var");
+#::logError("ECML unfix: attribute=$def->{attribute}, join=|$join|, split=$split, index=$index var=$var");
        if ($join eq '<CRLF>') {
                ($join, $split) = ("\r", '[\r\n]+');
        }
@@ -379,7 +379,7 @@ sub unfix {
                $pos = 0;
        }
        $pre = '' if index($var, $pre) == 0;
-::logError("ECML unfix: join=|$join|, split=$split, index=$index var=$var");
+#::logError("ECML unfix: join=|$join|, split=$split, index=$index var=$var");
        my @value;
        @value = split /$split/, $::Values->{"$pre$var"};
        $value[$pos] = $CGI::values{$ecml};
@@ -407,14 +407,14 @@ sub fieldfix {
        }
        my($var,$split,$index) = split /:/, $def->{attribute};
        my $join = $split;
-::logError("ECML fieldfix: attribute=$def->{attribute}, join=|$join|, split=$split, index=$index var=$var");
+#::logError("ECML fieldfix: attribute=$def->{attribute}, join=|$join|, split=$split, index=$index var=$var");
        if ($join eq '<CRLF>') {
                ($join, $split) = ('', '[\r\n]+');
        }
        $join = '' if $join =~ /^[[\\]/;
        my (@index) = map {$_ - 1} split /\s*,\s*/, $index;
        $pre = '' if index($var, $pre) == 0;
-::logError("ECML fieldfix: join=|$join|, split=$split, index=$index var=$var");
+#::logError("ECML fieldfix: join=|$join|, split=$split, index=$index var=$var");
        $value = join $join, (split /$split/, $::Values->{"$pre$var"})[@index]; 
 }
 
index 2e7266a..5b9f423 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 # Interpolate.pm - Interpret MiniVend tags
 # 
-# $Id: Interpolate.pm,v 1.88 1999/08/09 02:30:31 mike Exp mike $
+# $Id: Interpolate.pm,v 1.90 1999/08/10 10:23:41 mike Exp mike $
 #
 # Copyright 1996-1999 by Michael J. Heins <mikeh@iac.net>
 #
@@ -24,7 +24,7 @@ package Vend::Interpolate;
 require Exporter;
 @ISA = qw(Exporter);
 
-$VERSION = substr(q$Revision: 1.88 $, 10);
+$VERSION = substr(q$Revision: 1.90 $, 10);
 
 @EXPORT = qw (
 
@@ -352,7 +352,10 @@ sub cache_html {
 
        # Substitute defines from configuration file
        $html =~ s#\@\@([A-Za-z0-9]\w+[A-Za-z0-9])\@\@#$Global::Variable->{$1}#ge;
-       $html =~ s#__([A-Za-z0-9]\w*?[A-Za-z0-9])__#$Vend::Cfg->{Variable}->{$1}#ge;
+       $html =~ s#__([A-Za-z0-9]\w*?[A-Za-z0-9])__#
+                       $Vend::Cfg->{Member}->{$1} || $Vend::Cfg->{Variable}->{$1}#ge
+               if $Vend::Session->{logged_in};
+       $html =~ s#__([A-Za-z0-9]\w*?[A-Za-z0-9])__#$Vend::Cfg->{Variable}->{$1}#g;
 
        # Uncomment to use parallel MV and HTML tags
        #$html =~ s#<!--\s*$T{'alt'}\]\s*-->$Some<!--\s*$T{'/alt'}\]\s*-->##o;
@@ -378,6 +381,10 @@ sub cache_html {
                $CacheInvalid++ if $parse->{INVALID};
                $Vend::CachePage = $CacheInvalid ? undef : 1;
                $complete = \$full if $full;
+               if (defined $Vend::BuildingPages) {
+                       return $full if $full;
+                       return $parse->{OUT};
+               }
         return (\$parse->{OUT}, $complete || undef) if defined $wantref;
         return ($parse->{OUT});
     }
@@ -943,9 +950,9 @@ use vars qw/%Filters/;
 
 sub input_filter_do {
        my($varname, $opt, $routine) = @_;
-::logGlobal("filter var=$varname opt=" . Vend::Util::uneval($opt));
+#::logGlobal("filter var=$varname opt=" . Vend::Util::uneval($opt));
        return undef unless defined $CGI::values{$varname};
-::logGlobal("before filter=$CGI::values{$varname}");
+#::logGlobal("before filter=$CGI::values{$varname}");
        $routine = $opt->{routine} || ''
                if ! $routine;
        if($routine =~ /\S/) {
@@ -957,20 +964,20 @@ sub input_filter_do {
        if ($opt->{op}) {
                my @ops = grep /\S/, split /\s+/, $opt->{op};
                for(@ops) {
-::logGlobal("filter op=$_ found");
+#::logGlobal("filter op=$_ found");
                        if(/^\d+$/) {
                                $CGI::values{$varname} = substr($CGI::values{$varname} , 0, $_);
                                next;
                        }
                        next unless defined $Filters{$_};
-::logGlobal("filter op=$_ exists");
+#::logGlobal("filter op=$_ exists");
                        $CGI::values{$varname} = &{$Filters{$_}}(
                                                                                $CGI::values{$varname},
                                                                                $varname,
                                                                                );
                }
        }
-::logGlobal("after filter=$CGI::values{$varname}");
+#::logGlobal("after filter=$CGI::values{$varname}");
        return;
 }
 
@@ -2373,14 +2380,22 @@ sub tag_page {
 
        my $urlroutine = $secure ? \&secure_vendUrl : \&vendUrl;
 
-       if($Vend::Cookie and defined $Vend::Cfg->{StaticPage}{$page} and !$arg) {
-         $page = $Vend::Cfg->{StaticPage}{$page}
-                               if $Vend::Cfg->{StaticPage}{$page};
-         $page .= $Vend::Cfg->{StaticSuffix};
-         return '<a href="' . $urlroutine->($page,$arg,$Vend::Cfg->{StaticPath}) . '">';
+       while($Vend::Cookie and ! $arg) {
+               if(defined $Vend::StaticDBM{$page}) {
+                 $page = $Vend::StaticDBM{$page} || $page;
+               }
+               elsif (defined $Vend::Cfg->{StaticPage}{$page}) {
+                 $page = $Vend::Cfg->{StaticPage}{$page}
+                                       if $Vend::Cfg->{StaticPage}{$page};
+               }
+               else {
+                       last;
+               }
+               $page .= $Vend::Cfg->{StaticSuffix};
+               return '<a href="' . $urlroutine->($page,undef,$Vend::Cfg->{StaticPath}) . '">';
        }
        
-    '<a href="' . $urlroutine->($page,$arg || undef) . '">';
+    return '<a href="' . $urlroutine->($page,$arg || undef) . '">';
 }
 
 # Returns an href which will call up the specified PAGE with TARGET reference.
@@ -2427,13 +2442,21 @@ sub tag_area {
 
        my $urlroutine = $secure ? \&secure_vendUrl : \&vendUrl;
 
-       if($Vend::Cookie and defined $Vend::Cfg->{StaticPage}{$page} and ! $arg) {
-               $page = $Vend::Cfg->{StaticPage}{$page}
-                        if $Vend::Cfg->{StaticPage}{$page};
+       while($Vend::Cookie and ! $arg) {
+               if(defined $Vend::StaticDBM{$page}) {
+                 $page = $Vend::StaticDBM{$page} || $page;
+               }
+               elsif (defined $Vend::Cfg->{StaticPage}{$page}) {
+                 $page = $Vend::Cfg->{StaticPage}{$page}
+                                       if $Vend::Cfg->{StaticPage}{$page};
+               }
+               else {
+                       last;
+               }
                $page .= $Vend::Cfg->{StaticSuffix};
-       return $urlroutine->($page,'',$Vend::Cfg->{StaticPath});
+               return $urlroutine->($page,undef,$Vend::Cfg->{StaticPath});
        }
-    $urlroutine->($page, $arg);
+    return $urlroutine->($page, $arg);
 }
 
 # Returns an href which will call up the specified PAGE with TARGET reference.
@@ -4994,40 +5017,44 @@ sub timed_build {
     my $file = shift;
     my $opt = shift;
        my $abort;
-       if($file eq 'scan') {
-               $file = $Vend::Session->{last_search};
-               unless ($file =~ m:MM=:) {
-                       $file =~ s/(\W)/sprintf("%02x", ord($1))/eg;
-               }
-               else { $abort = 1 }
+
+       my $saved_file;
+       if($opt->{scan}) {
+               $saved_file = $Vend::Session->{last_search};
+               $abort = 1 if $file =~ m:MM=:;
        }
 
     if($abort or ! $CGI::cookie or ! $opt->{login} && $Vend::Session->{logged_in}) {
         return Vend::Interpolate::interpolate_html(shift);
     }
 
+    if($opt->{noframes} and $::Session->{frames}) {
+        return '';
+    }
+
        my $secs;
+       my $static;
        CHECKDIR: {
-               if (! $file) {
-                       my $dir = $Vend::Cfg->{StaticDir};
-                       $dir = 'timed' if ! -d $dir || ! -w _;
-                       $dir .= "/$1"
-                               if $file =~ s:(.*)/::;
-                       if(! -d $dir) {
-                               require File::Path;
-                               File::Path::mkpath($dir);
-                       }
-                       $file = $Global::Variable->{MV_PAGE} . $Vend::Cfg->{StaticSuffix};
-                       $file = Vend::Util::catfile($dir, $file);
+               last CHECKDIR if $file;
+               my $dir = $Vend::Cfg->{StaticDir};
+               $dir = ! -d $dir || ! -w _ ? 'timed' : do { $static = 1; $dir };
+               $file = $saved_file || $Global::Variable->{MV_PAGE};
+               if($saved_file) {
+                       $file = $saved_file;
+                       $file =~ s/(\W)/sprintf("%02x", ord($1))/eg;
                }
+               else {
+                       $saved_file = $file = $Global::Variable->{MV_PAGE};
+               }
+               $file .= $Vend::Cfg->{StaticSuffix};
+               $dir .= "/$1" 
+                       if $file =~ s:(.*)/::;
+               if(! -d $dir) {
+                       require File::Path;
+                       File::Path::mkpath($dir);
+               }
+               $file = Vend::Util::catfile($dir, $file);
        }
-    if($opt->{noframes} and $::Scratch->{frames}) {
-        return '';
-    }
-
-    if(! $CGI::cookie or ! $opt->{login} && $Vend::Session->{logged_in}) {
-        return Vend::Interpolate::interpolate_html(shift);
-    }
 
        if($opt->{minutes}) {
         $secs = int($opt->{minutes} * 60);
@@ -5039,6 +5066,17 @@ sub timed_build {
     if( ! -f $file or $secs && (stat(_))[9] < (time() - $secs) ) {
         my $out = Vend::Interpolate::interpolate_html(shift);
         Vend::Util::writefile(">$file", $out);
+               if ($Vend::Cfg->{StaticDBM}) {
+                        
+                       chmod($Vend::Cfg->{FileCreationMask} | 0444, $file);
+                       if ($opt->{scan}) {
+                               $file =~ s:.*/::;
+                               $Vend::StaticDBM{$saved_file} = $file;
+                       }
+                       else {
+                               $Vend::StaticDBM{$saved_file} = '';
+                       }
+               }
         return $out;
     }
     else {        return Vend::Util::readfile($file);    }
@@ -5122,7 +5160,6 @@ sub replace {
 
 sub write {
        shift;
-::logError("writing @_");
        push @Vend::Tags::Out, @_;
        return if ! $Hot;
        Vend::Tags::Document::send( undef, join("", splice(@Vend::Tags::Out, 0)) );
index f871818..96f65e5 100644 (file)
@@ -1,6 +1,6 @@
 # Parse.pm - Parse MiniVend tags
 # 
-# $Id: Parse.pm,v 1.54 1999/08/09 02:31:17 mike Exp mike $
+# $Id: Parse.pm,v 1.54 1999/08/09 02:31:17 mike Exp $
 #
 # Copyright 1997-1999 by Michael J. Heins <mikeh@iac.net>
 #
@@ -20,7 +20,7 @@
 
 package Vend::Parse;
 
-# $Id: Parse.pm,v 1.54 1999/08/09 02:31:17 mike Exp mike $
+# $Id: Parse.pm,v 1.54 1999/08/09 02:31:17 mike Exp $
 
 require Vend::Parser;
 
index 61b6364..edd9ef6 100644 (file)
@@ -1,6 +1,6 @@
 # Vend/Scan.pm:  Prepare searches for MiniVend
 #
-# $Id: Scan.pm,v 1.52 1999/07/16 11:05:19 mike Exp $
+# $Id: Scan.pm,v 1.53 1999/08/10 09:21:18 mike Exp $
 #
 # Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
 # Copyright 1996-1999 by Michael J. Heins <mikeh@iac.net>
@@ -29,7 +29,7 @@ require Exporter;
                        perform_search
                        );
 
-$VERSION = substr(q$Revision: 1.52 $, 10);
+$VERSION = substr(q$Revision: 1.53 $, 10);
 
 use strict;
 use Vend::Util;
@@ -812,7 +812,7 @@ sub perform_search {
        # We will come back with the above when [search-region] is called
        elsif ($c->{mv_delay_page}) {
                $Vend::Session->{search_params} = [$c, $more_matches];
-               return main::cache_page($c->{mv_delay_page}, 1);
+               return ::cache_page($c->{mv_delay_page}, 1);
        }
 
        my($v) = $::Values;
index 177340f..1bf2f75 100644 (file)
@@ -1,6 +1,6 @@
 # Server.pm:  listen for cgi requests as a background server
 #
-# $Id: Server.pm,v 1.57 1999/08/09 02:31:52 mike Exp mike $
+# $Id: Server.pm,v 1.57 1999/08/09 02:31:52 mike Exp $
 #
 # Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
 # Copyright 1996-1999 by Michael J. Heins <mikeh@iac.net>
index ea987a7..38b447e 100644 (file)
@@ -1,6 +1,6 @@
 # Util.pm - Minivend utility functions
 #
-# $Id: Util.pm,v 1.52 1999/08/05 03:51:52 mike Exp $
+# $Id: Util.pm,v 1.53 1999/08/10 09:21:24 mike Exp $
 # 
 # Copyright 1995 by Andrew M. Wilcox <awilcox@world.std.com>
 # Copyright 1996-1999 by Michael J. Heins <mikeh@iac.net>
@@ -69,7 +69,7 @@ use Config;
 use Fcntl;
 use subs qw(logError logGlobal);
 use vars qw($VERSION);
-$VERSION = substr(q$Revision: 1.52 $, 10);
+$VERSION = substr(q$Revision: 1.53 $, 10);
 
 BEGIN {
        eval {
@@ -829,8 +829,15 @@ sub is_no {
 sub vendUrl
 {
     my($path, $arguments, $r) = @_;
-    $r = $Vend::Cfg->{VendURL}
-               unless defined $r;
+       my $ct;
+       my $id;
+       if(! $r) {
+               $r = $Vend::Cfg->{VendURL};
+               $ct = $::Scratch->{mv_no_count} 
+                        ? '' : ++$Vend::Session->{pageCount};
+               $id = $CGI::cookie && $::Scratch->{mv_no_session_id}
+                        ? '' : $Vend::SessionID;
+       }
 
        $arguments =~ s!([^\w-_:#=/.%])! '%' . sprintf("%02x", ord($1))!eg
                if defined $arguments && $Vend::Cfg->{NewEscape};
@@ -839,10 +846,6 @@ sub vendUrl
                $r = $Vend::Cfg->{SecureURL};
        }
 
-       my $id = $CGI::cookie && $::Scratch->{mv_no_session_id}
-                        ? '' : $Vend::SessionID;
-       my $ct = $::Scratch->{mv_no_count}      
-                        ? '' : ++$Vend::Session->{pageCount};
     $r .= '/' . $path;
        return $r unless ($id || $arguments || $ct);
        $r .= '?' . $id .  ';' . ($arguments || '');
@@ -1087,8 +1090,10 @@ sub check_security {
                                return 1 if $access =~ m{(^|\s)$item(\s|$)};
                        }
                }
-               my $besthost = $CGI::remote_host || $CGI::remote_addr;
-        logGlobal qq{auth error host=$besthost ip=$CGI::remote_addr script=$CGI::script_name page=$CGI::path_info};
+               if($Vend::Cfg->{UserDB}{log_failed}) {
+                       my $besthost = $CGI::remote_host || $CGI::remote_addr;
+                       ::logError(qq{auth error host=$besthost ip=$CGI::remote_addr script=$CGI::script_name page=$CGI::path_info});
+               }
         return '';  
        }
        elsif($reconfig eq '1') {