1 # Vend::Config - Configure Interchange
3 # Copyright (C) 2002-2011 Interchange Development Group
4 # Copyright (C) 1996-2002 Red Hat, Inc.
6 # This program was originally based on Vend 0.2 and 0.3
7 # Copyright 1995 by Andrew M. Wilcox <amw@wilcoxsolutions.com>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public
20 # License along with this program; if not, write to the Free
21 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
29 @EXPORT = qw( config global_config config_named_catalog );
31 @EXPORT_OK = qw( get_catalog_default get_global_default parse_time parse_database);
34 no warnings qw(uninitialized numeric);
37 @Locale_directives_ary @Locale_directives_scalar
38 @Locale_directives_code %tagCanon
39 %ContainerSave %ContainerTrigger %ContainerSpecial %ContainerType
41 %Dispatch_code %Dispatch_priority
42 %Cleanup_code %Cleanup_priority
43 @Locale_directives_currency @Locale_keys_currency
44 $GlobalRead $SystemCodeDone $SystemGroupsDone $CodeDest
45 $SystemReposDone $ReposDest @include
62 my ($var, $value, $end) = @_;
63 $var = $CDname{lc $var};
65 my $val = delete $ContainerSave{$var};
71 ${"Global::$var"} = $val;
77 $ContainerSave{$var} = $C ? $C->{$var} : ${"Global::$var"};
78 $ContainerSave{$var} ||= 'No';
83 my %DirectiveAlias = qw(
86 DefaultTables ProductFiles
90 for( qw(search refresh cancel return secure unsecure submit control checkout) ) {
91 $Global::LegalAction{$_} = 1;
94 @Locale_directives_currency = (
107 @Locale_keys_currency = (
126 @Locale_directives_scalar = (
148 @Locale_directives_ary = (
156 # These are extra routines that are run if certain directives are
160 # [ 'Directive', \&routine, [ @args ] ],
162 # @args are optional.
164 @Locale_directives_code = (
165 [ 'ProductFiles', \&Vend::Data::update_productbase ],
168 my %HashDefaultBlank = (qw(
175 my %DumpSource = (qw(
185 my %UseExtended = (qw(
191 my %InitializeEmpty = (qw(
195 my %AllowScalarAction = (qw(
200 my @External_directives = qw(
231 for( values %extmap ) {
243 localechange LocaleChange
245 formaction FormAction
246 ordercheck OrderCheck
254 attrdefault attrDefault
256 description Description
258 visibility Visibility
260 documentation Documentation
265 interpolate Interpolate
266 invalidatecache InvalidateCache
267 isendanchor isEndAnchor
269 norearrange noRearrange
272 posroutine PosRoutine
273 maproutine MapRoutine
275 javascriptcheck JavaScriptCheck
281 my %tagSkip = ( qw! Documentation 1 Version 1 !);
283 my %tagAry = ( qw! Order 1 Required 1 ! );
312 formaction FormAction
313 itemaction ItemAction
314 ordercheck OrderCheck
315 localechange LocaleChange
321 javascriptcheck JavaScriptCheck
327 use vars qw/ $configfile /;
329 ### This is unset when interchange script is run, so that the default
330 ### when used by an external program is not to compile subroutines
331 $Vend::ExternalProgram = 1;
333 # Report a fatal error in the configuration file.
337 $msg = errmsg($msg, @_);
342 $msg = errmsg("%s\nIn line %s of the configuration file '%s':\n%s\n",
350 if ($Vend::ExternalProgram) {
351 warn "$msg\n" unless $Vend::Quiet;
361 $msg = errmsg($msg, @_);
366 if($configfile and $Vend::config_line) {
368 "\nIn line %s of the configuration file '%s':\n%s\n",
376 ::logGlobal({level => 'notice'}, "$msg$extra");
380 $C = $_[0] || $Vend::Cfg;
383 sub global_directives {
386 # Order is not really important, catalogs are best first
388 # Directive name Parsing function Default value
390 ['RunDir', 'root_dir', $Global::RunDir || 'etc'],
391 ['DebugFile', 'root_dir', ''],
392 ['CatalogUser', 'hash', ''],
393 ['ConfigDir', undef, 'etc/lib'],
394 ['FeatureDir', 'root_dir', 'features'],
395 ['ConfigDatabase', 'config_db', ''],
396 ['ConfigAllBefore', 'root_dir_array', 'catalog_before.cfg'],
397 ['ConfigAllAfter', 'root_dir_array', 'catalog_after.cfg'],
398 ['Message', 'message', ''],
399 ['Capability', 'capability', ''],
400 ['Require', 'require', ''],
401 ['Suggest', 'suggest', ''],
402 ['VarName', 'varname', ''],
403 ['Windows', undef, $Global::Windows || ''],
404 ['LockType', undef, $Global::Windows ? 'none' : ''],
405 ['DumpStructure', 'yesno', 'No'],
406 ['DumpAllCfg', 'yesno', 'No'],
407 ['DisplayErrors', 'yesno', 'No'],
408 ['DeleteDirective', sub {
409 my $c = $Global::DeleteDirective || {};
411 my @sets = map { lc $_ } split /[,\s]+/, shift;
412 @{$c}{@sets} = map { 1 } @sets;
415 ['Inet_Mode', 'yesno', (
416 defined $Global::Inet_Mode
418 defined $Global::Unix_Mode
420 ? ($Global::Inet_Mode || 0) : 'No'],
421 ['Unix_Mode', 'yesno', (
422 defined $Global::Inet_Mode
424 defined $Global::Unix_Mode
426 ? ($Global::Unix_Mode || 0) : 'Yes'],
427 ['TcpMap', 'hash', ''],
428 ['CodeRepository', 'root_dir', ''],
429 ['AccumulateCode', 'yesno', 'No'],
430 ['Environment', 'array', ''],
431 ['TcpHost', undef, 'localhost 127.0.0.1'],
432 ['AcceptRedirect', 'yesno', 'No'],
433 ['SendMailProgram', 'executable', [
434 $Global::SendMailLocation,
435 '/usr/sbin/sendmail',
440 ['EncryptProgram', 'executable', [ 'gpg', 'pgpe', 'none', ] ],
441 ['PIDfile', 'root_dir', "etc/$Global::ExeName.pid"],
442 ['SocketFile', 'root_dir_array', ''],
443 ['SocketPerms', 'integer', 0600],
444 ['SocketReadTimeout','integer', 1],
445 ['SOAP', 'yesno', 'No'],
446 ['SOAP_Socket', 'array', ''],
447 ['SOAP_Perms', 'integer', 0600],
448 ['MaxRequestsPerChild','integer', 50],
449 ['ChildLife', 'time', 0],
450 ['StartServers', 'integer', 0],
451 ['PreFork', 'yesno', 0],
452 ['PreForkSingleFork', 'yesno', 0],
453 ['SOAP_MaxRequests', 'integer', 50],
454 ['SOAP_StartServers', 'integer', 1],
455 ['SOAP_Control', 'action', ''],
456 ['Jobs', 'hash', 'MaxLifetime 600 MaxServers 1 UseGlobal 0'],
457 ['IPCsocket', 'root_dir', 'etc/socket.ipc'],
458 ['HouseKeeping', 'time', 60],
459 ['HouseKeepingCron', 'cron', ''],
460 ['Mall', 'yesno', 'No'],
461 ['TagGroup', 'tag_group', $StdTags],
462 ['ConfigParseComments', 'warn', ''],
463 ['TagInclude', 'tag_include', 'ALL'],
464 ['ActionMap', 'action', ''],
465 ['FileControl', 'action', ''],
466 ['FormAction', 'action', ''],
467 ['MaxServers', 'integer', 10],
468 ['GlobalSub', 'subroutine', ''],
469 ['Database', 'database', ''],
470 ['FullUrl', 'yesno', 'No'],
471 ['FullUrlIgnorePort', 'yesno', 'No'],
472 ['Locale', 'locale', ''],
473 ['HitCount', 'yesno', 'No'],
474 ['IpHead', 'yesno', 'No'],
475 ['IpQuad', 'integer', '1'],
476 ['TagDir', 'root_dir_array', 'code'],
477 ['TemplateDir', 'root_dir_array', ''],
478 ['DebugTemplate', undef, ''],
479 ['DomainTail', 'yesno', 'Yes'],
480 ['CountrySubdomains','hash', ''],
481 ['TrustProxy', 'list_wildcard_full', ''],
482 ['AcrossLocks', 'yesno', 'No'],
483 ['DNSBL', 'array', ''],
484 ['NotRobotUA', 'list_wildcard', ''],
485 ['RobotUA', 'list_wildcard', ''],
486 ['RobotIP', 'list_wildcard_full', ''],
487 ['RobotHost', 'list_wildcard_full', ''],
488 ['HostnameLookups', 'yesno', 'No'],
489 ['TolerateGet', 'yesno', 'No'],
490 ['PIDcheck', 'time', '0'],
491 ['LockoutCommand', undef, ''],
492 ['SafeUntrap', 'array', 'ftfile sort'],
493 ['SafeTrap', 'array', ':base_io'],
494 ['NoAbsolute', 'yesno', 'No'],
495 ['AllowGlobal', 'boolean', ''],
496 ['PerlNoStrict', 'boolean', ''],
497 ['PerlAlwaysGlobal', 'boolean', ''],
498 ['AddDirective', 'directive', ''],
499 ['UserTag', 'tag', ''],
500 ['CodeDef', 'mapped_code', ''],
501 ['HotDBI', 'boolean', ''],
502 ['HammerLock', 'time', 30],
503 ['DataTrace', 'integer', 0],
504 ['ShowTimes', 'yesno', 0],
505 ['ErrorFile', 'root_dir', undef],
506 ['SysLog', 'hash', undef],
507 ['Logging', 'integer', 0],
508 ['CheckHTML', undef, ''],
509 ['UrlSepChar', 'url_sep_char', '&'],
510 ['Variable', 'variable', ''],
511 ['Profiles', 'profile', ''],
512 ['Catalog', 'catalog', ''],
513 ['SubCatalog', 'catalog', ''],
514 ['AutoVariable', 'autovar', 'UrlJoiner'],
515 ['XHTML', 'yesno', 'No'],
516 ['UTF8', 'yesno', $ENV{MINIVEND_DISABLE_UTF8} ? 'No' : 'Yes'],
517 ['External', 'yesno', 'No'],
518 ['ExternalFile', 'root_dir', "$Global::RunDir/external.structure"],
519 ['ExternalExport', undef, 'Global::Catalog=Catalog'],
520 ['DowncaseVarname', undef, ''],
527 sub catalog_directives {
530 # Order is somewhat important, the first 6 especially
532 # Directive name Parsing function Default value
534 ['ErrorFile', 'relative_dir', 'error.log'],
535 ['ActionMap', 'action', ''],
536 ['FileControl', 'action', ''],
537 ['FormAction', 'action', ''],
538 ['ItemAction', 'action', ''],
539 ['PageDir', 'relative_dir', 'pages'],
540 ['SpecialPageDir', undef, 'special_pages'],
541 ['ProductDir', 'relative_dir', 'products'],
542 ['OfflineDir', 'relative_dir', 'offline'],
543 ['ConfDir', 'relative_dir', 'etc'],
544 ['RunDir', 'relative_dir', ''],
545 ['ConfigDir', 'relative_dir', 'config'],
546 ['TemplateDir', 'dir_array', ''],
547 ['ConfigDatabase', 'config_db', ''],
548 ['Require', 'require', ''],
549 ['Suggest', 'suggest', ''],
550 ['Message', 'message', ''],
551 ['Variable', 'variable', ''],
552 ['VarName', 'varname', ''],
553 ['Limit', 'hash', 'option_list 5000 chained_cost_levels 32 robot_expire 1'],
554 ['ScratchDefault', 'hash', ''],
555 ['Profile', 'locale', ''],
556 ['ValuesDefault', 'hash', ''],
557 ['ProductFiles', 'array_complete', 'products'],
558 ['PageTables', 'array_complete', ''],
559 ['PageTableMap', 'hash', qq{
560 expiration_date expiration_date
566 ['DisplayErrors', 'yesno', 'No'],
567 ['ParseVariables', 'yesno', 'No'],
568 ['SpecialPage', 'special', 'order ord/basket results results search results flypage flypage'],
569 ['DirectoryIndex', undef, ''],
570 ['Sub', 'subroutine', ''],
571 ['VendURL', 'url', undef],
572 ['SecureURL', 'url', undef],
573 ['PostURL', 'url', ''],
574 ['SecurePostURL', 'url', ''],
575 ['ProcessPage', undef, 'process'],
576 ['History', 'integer', 0],
577 ['OrderReport', undef, 'etc/report'],
578 ['ScratchDir', 'relative_dir', 'tmp'],
579 ['PermanentDir', 'relative_dir', 'perm'],
580 ['SessionDB', undef, ''],
581 ['SessionType', undef, 'File'],
582 ['SessionDatabase', 'relative_dir', 'session'],
583 ['ConfigParseComments', 'warn', ''],
584 ['SessionLockFile', undef, 'etc/session.lock'],
585 ['MoreDB', 'yesno', 'No'],
586 ['DatabaseDefault', 'hash', ''],
587 ['DatabaseAuto', 'dbauto', ''],
588 ['DatabaseAutoIgnore', 'regex', ''],
589 ['Database', 'database', ''],
590 ['Preload', 'routine_array', ''],
591 ['Autoload', 'routine_array', ''],
592 ['AutoEnd', 'routine_array', ''],
593 ['Replace', 'replace', ''],
594 ['Member', 'variable', ''],
595 ['Feature', 'feature', ''],
596 ['WritePermission', 'permission', 'user'],
597 ['ReadPermission', 'permission', 'user'],
598 ['SessionExpire', 'time', '1 hour'],
599 ['SaveExpire', 'time', '30 days'],
600 ['MailOrderTo', undef, ''],
601 ['SendMailProgram', 'executable', $Global::SendMailProgram],
604 ['Glimpse', 'executable', ''],
606 ['Locale', 'locale', ''],
607 ['Route', 'locale', ''],
608 ['LocaleDatabase', 'configdb', ''],
609 ['ExecutionLocale', undef, 'C'],
610 ['DefaultLocale', undef, ''],
611 ['RouteDatabase', 'configdb', ''],
612 ['DirectiveDatabase', 'dbconfig', ''],
613 ['VariableDatabase', 'dbconfig', ''],
614 ['DirConfig', 'dirconfig', ''],
615 ['FileDatabase', undef, ''],
616 ['NoSearch', 'wildcard', 'userdb'],
617 ['AllowRemoteSearch', 'array_complete', 'products variants options'],
618 ['OrderCounter', undef, ''],
619 ['MimeType', 'hash', ''],
620 ['AliasTable', undef, ''],
621 ['ImageAlias', 'hash', ''],
622 ['TableRestrict', 'hash', ''],
623 ['Filter', 'hash', ''],
624 ['ImageDirSecure', undef, ''],
625 ['ImageDirInternal', undef, ''],
626 ['ImageDir', undef, ''],
627 ['DeliverImage', 'yesno', 'no'],
628 ['SpecialSub', 'hash', ''],
629 ['SetGroup', 'valid_group', ''],
630 ['UseModifier', 'array', ''],
631 ['AutoModifier', 'array', ''],
632 ['MaxQuantityField', undef, ''],
633 ['MinQuantityField', undef, ''],
634 ['LogFile', undef, 'etc/log'],
635 ['Pragma', 'boolean_value', ''],
636 ['NoExport', 'boolean', ''],
637 ['NoExportExternal', 'yesno', 'no'],
638 ['NoImport', 'boolean', ''],
639 ['NoImportExternal', 'yesno', 'no'],
640 ['CommonAdjust', undef, ''],
641 ['PriceDivide', undef, 1],
642 ['PriceCommas', 'yesno', 'Yes'],
643 ['OptionsEnable', undef, ''],
644 ['OptionsAttribute', undef, ''],
645 ['Options', 'locale', ''],
646 ['AlwaysSecure', 'boolean', ''],
647 ['Password', undef, ''],
648 ['AdminSub', 'boolean', ''],
649 ['ExtraSecure', 'yesno', 'No'],
650 ['FallbackIP', 'yesno', 'No'],
651 ['WideOpen', 'yesno', 'No'],
652 ['Promiscuous', 'yesno', 'No'],
653 ['Cookies', 'yesno', 'Yes'],
654 ['CookieName', undef, ''],
655 ['CookiePattern', 'regex', '[-\w:.]+'],
656 ['CookieLogin', 'yesno', 'No'],
657 ['CookieDomain', undef, ''],
658 ['MasterHost', undef, ''],
659 ['UserTag', 'tag', ''],
660 ['CodeDef', 'mapped_code', ''],
661 ['RemoteUser', undef, ''],
662 ['TaxShipping', undef, ''],
663 ['TaxInclusive', 'yesno', 'No'],
664 ['FractionalItems', 'yesno', 'No'],
665 ['SeparateItems', 'yesno', 'No'],
666 ['PageSelectField', undef, ''],
667 ['NonTaxableField', undef, ''],
668 ['CreditCardAuto', 'yesno', 'No'],
669 ['FormIgnore', 'boolean', ''],
670 ['EncryptProgram', undef, $Global::EncryptProgram || ''],
671 ['EncryptKey', undef, ''],
672 ['AsciiTrack', undef, ''],
673 ['TrackFile', undef, ''],
674 ['TrackPageParam', 'hash', ''],
675 ['TrackDateFormat', undef, ''],
676 ['SalesTax', undef, ''],
677 ['SalesTaxFunction', undef, ''],
678 ['StaticDir', undef, ''],
679 ['SOAP', 'yesno', 'No'],
680 ['SOAP_Enable', 'hash', ''],
681 ['SOAP_Action', 'action', ''],
682 ['SOAP_Control', 'action', ''],
683 ['UserDB', 'locale', ''],
684 ['UserControl', 'yesno', 'No'],
685 ['UserDatabase', undef, ''],
686 ['RobotLimit', 'integer', 0],
687 ['OrderLineLimit', 'integer', 0],
688 ['RedirectCache', undef, ''],
689 ['HTMLsuffix', undef, '.html'],
690 ['CustomShipping', undef, ''],
691 ['DefaultShipping', undef, 'default'],
692 ['UpsZoneFile', undef, ''],
693 ['OrderProfile', 'profile', ''],
694 ['SearchProfile', 'profile', ''],
695 ['OnFly', undef, ''],
696 ['CategoryField', undef, 'category'],
697 ['DescriptionField', undef, 'description'],
698 ['PriceDefault', undef, 'price'],
699 ['PriceField', undef, 'price'],
700 ['DiscountSpacesOn', 'yesno', 'no'],
701 ['DiscountSpaceVar', 'array', 'mv_discount_space'],
702 ['Jobs', 'hash', ''],
703 ['Shipping', 'locale', ''],
704 ['Accounting', 'locale', ''],
705 ['Levies', 'array', ''],
706 ['Levy', 'locale', ''],
707 ['AutoVariable', 'autovar', ''],
708 ['ErrorDestination', 'hash', ''],
709 ['XHTML', 'yesno', $Global::XHTML],
710 ['External', 'yesno', 'No'],
711 ['ExternalExport', undef, join " ", @External_directives],
712 ['CartTrigger', 'routine_array', ''],
713 ['CartTriggerQuantity', 'yesno', 'no'],
714 ['UserTrack', 'yesno', 'no'],
715 ['DebugHost', 'ip_address_regexp', ''],
716 ['BounceReferrals', 'yesno', 'no'],
717 ['BounceReferralsRobot', 'yesno', 'no'],
718 ['BounceRobotSessionURL', 'yesno', 'no'],
719 ['OrderCleanup', 'routine_array', ''],
720 ['SessionCookieSecure', 'yesno', 'no'],
721 ['SessionHashLength', 'integer', 1],
722 ['SessionHashLevels', 'integer', 2],
723 ['SourcePriority', 'array_complete', 'mv_pc mv_source'],
724 ['SourceCookie', sub { &parse_ordered_attributes(@_, [qw(name expire domain path secure)]) }, '' ],
728 push @$directives, @$Global::AddDirective
729 if $Global::AddDirective;
733 sub get_parse_routine {
738 if(ref $parse eq 'CODE') {
741 elsif( $parse =~ /^\w+$/) {
743 $routine = \&{'parse_' . $parse};
744 $rname = "parse_$rname";
748 $routine = \&{"$parse"};
751 if(ref($routine) ne 'CODE') {
752 config_error('Unknown parse routine %s', $rname);
770 or config_error("read global chunk %s: %s", $fn, $!);
772 #::logDebug("GCHUNK length: " . -s $fn);
775 my($lvar, $value) = read_config_value($_, \*GCHUNK);
778 $GlobalRead->($lvar, $value);
780 if($@ =~ /Duplicate\s+usertag/i) {
784 ::logDebug("error running global $lvar: $@");
789 Vend::Dispatch::update_global_actions();
790 finalize_mapped_code();
797 my ($area, $name, $nohup) = @_;
800 #::logDebug("code_from_file $area, $name");
801 return unless $c = $Global::TagLocation->{$area};
802 #::logDebug("We have a repos for $area");
803 return unless $fn = $c->{$name};
804 #::logDebug("code_from_file found file=$fn");
806 #::logDebug("master reading in new area=$area name=$name fn=$fn") if $nohup;
813 my $tdir = $Global::TagDir->[0];
814 my $accdir = "$tdir/Accumulated";
817 $newfn =~ s{^$Global::CodeRepository/*}{};
819 my $lfile = "$accdir/$newfn";
821 $ldir =~ s{/[^/]+$}{};
823 die "Supposed directory $ldir is a file" if -e $ldir;
824 File::Path::mkpath($ldir)
825 or die "Cannot create directory $ldir: $!";
830 ## This has already been submitted for master integration, no
835 open NEWTAG, ">> $lfile"
836 or die "Cannot write new tag file $lfile: $!";
837 if (lockfile(\*NEWTAG, 1, 0)) {
838 ## We got a lock, we are the only one
839 File::Copy::copy($fn, $lfile);
840 unlockfile(\*NEWTAG);
844 ## No lock, some other process doing same thing
849 or config_error("read system tag file %s: %s", $fn, $!);
853 my($lvar, $value) = read_config_value($_, \*SYSTAG);
856 $GlobalRead->($lvar, $value);
858 if($@ =~ /Duplicate\s+usertag/i) {
865 finalize_mapped_code($area);
870 if($area eq 'UserTag') {
871 $init = $Global::UserTag->{Bootstrap}{$name};
872 $routine = $Global::UserTag->{Routine}{$name};
873 #::logDebug("NO ROUTINE FOR area=$area name=$name") unless $routine;
876 $precursor = 'CodeDef ';
877 $init = $Global::CodeDef->{$area}{Bootstrap}{$name};
878 $routine = $Global::CodeDef->{$area}{Routine}{$name};
881 $routine = $Global::CodeDef->{$area}{MapRoutine}{$name}
882 and $routine = \&{"$routine"};
884 #::logDebug("area=$area name=$name now=" . ::uneval($Global::CodeDef->{$area}));
887 if($init and ref($routine) eq 'CODE') {
888 ## Attempt to initialize
889 $init = get_option_hash($init);
894 ## Tell the master server we have a new tag
896 #::logDebug("notifying master of new area=$area name=$name fn=$fn");
897 ## Bring this tag in global
898 open(RESTART, ">>$Global::RunDir/restart")
899 or die "open $Global::RunDir/restart: $!\n";
900 lockfile(\*RESTART, 1, 1)
901 or die "lock $Global::RunDir/restart: $!\n";
902 print RESTART "$precursor$area $name\n";
903 unlockfile(\*RESTART)
904 or die "unlock $Global::RunDir/restart: $!\n";
906 kill 'HUP', $Vend::MasterProcess;
909 #::logDebug("routine=$routine for area=$area name=$name");
910 #::logDebug("REF IS=" . ::uneval($Global::UserTag)) if $nohup;
915 my ($directive, $value, $global) = @_;
918 if($global) { $directives = global_directives(); }
919 else { $directives = catalog_directives(); }
921 my ($d, $dir, $parse);
923 foreach $d (@$directives) {
924 next unless (lc $directive) eq (lc $d->[0]);
925 $parse = get_parse_routine($d->[1]);
927 $value = $parse->($dir, $value)
931 return [$dir, $value] if defined $dir;
935 sub get_catalog_default {
936 my ($directive) = @_;
937 my $directives = catalog_directives();
940 next unless (lc $directive) eq (lc $_->[0]);
943 return undef unless defined $value;
947 sub get_global_default {
948 my ($directive) = @_;
949 my $directives = global_directives();
952 next unless (lc $directive) eq (lc $_->[0]);
955 return undef unless defined $value;
960 my ($ifdef, $reverse, $global) = @_;
961 #::logDebug("ifdef '$ifdef'");
963 $ifdef =~ /^\s*(\@?)(\w+)\s*(.*)/;
964 $global = $1 || $global || undef;
967 my $var_ref = ! $global ? $C->{Variable} : $Global::Variable;
968 #::logDebug("Variable value '$var_ref->{$var}'");
970 $status = ! (not $var_ref->{$var});
973 my $val = $var_ref->{$var} || '';
974 my $safe = new Vend::Safe;
975 my $code = "q{$val}" . " " . $cond;
976 $status = $safe->reval($code);
979 errmsg("Syntax error in ifdef evaluation at line %s of %s",
987 #::logDebug("ifdef status '$status', reverse=" . !(not $reverse));
988 return $reverse ? ! $status : $status;
991 # This is what happens when ParseVariables is true
992 sub substitute_variable {
994 1 while $val =~ s/__([A-Z][A-Z_0-9]*?[A-Z0-9])__/$C->{Variable}->{$1}/g;
995 # Only parse once for globals so they can contain other
996 # global and catalog variables
997 $val =~ s/\@\@([A-Z][A-Z_0-9]+[A-Z0-9])\@\@/$Global::Variable->{$1}/g;
1001 # Parse the configuration file for directives. Each directive sets
1002 # the corresponding variable in the Vend::Cfg:: package. E.g.
1003 # "DisplayErrors No" in the config file sets Vend::Cfg->{DisplayErrors} to 0.
1004 # Directives which have no defined default value ("undef") must be specified
1005 # in the config file.
1007 my($directives, $directive, %parse);
1010 my($catalog, $dir, $confdir, $subconfig, $existing, $passed_file) = @_;
1011 my($d, $parse, $var, $value, $lvar);
1013 $Vend::Cat = $catalog;
1015 if(ref $existing eq 'HASH') {
1016 #::logDebug("existing=$existing");
1022 $C->{CatalogName} = $catalog;
1023 $C->{VendRoot} = $dir;
1025 unless (defined $subconfig) {
1026 $C->{ErrorFile} = 'error.log';
1027 $C->{ConfigFile} = 'catalog.cfg';
1030 $C->{ConfigFile} = "$catalog.cfg";
1031 $C->{BaseCatalog} = $subconfig;
1035 unless($directives) {
1036 $directives = catalog_directives();
1037 foreach $d (@$directives) {
1038 my $ucdir = $d->[0];
1039 $directive = lc $d->[0];
1040 next if $Global::DeleteDirective->{$directive};
1041 $CDname{$directive} = $ucdir;
1042 $CPname{$directive} = $d->[1];
1043 $parse{$directive} = get_parse_routine($d->[1]);
1047 for(keys %DirectiveAlias) {
1049 my $v = $DirectiveAlias{$_};
1051 $CDname{$k} = $CDname{$lv};
1052 $CPname{$k} = $CPname{$lv};
1053 $parse{$k} = $parse{$lv};
1058 if(! $subconfig and ! $existing ) {
1059 foreach $d (@$directives) {
1060 my $ucdir = $d->[0];
1061 $directive = lc $d->[0];
1062 next if $Global::DeleteDirective->{$directive};
1063 $parse = $parse{$directive};
1066 ! defined $MV::Default{$catalog} or
1067 ! defined $MV::Default{$catalog}{$ucdir}
1070 : $MV::Default{$catalog}{$ucdir};
1072 if (defined $parse and defined $value) {
1073 #::logDebug("parsing default directive=$directive ucdir=$ucdir parse=$parse value=$value CDname=$CDname{$directive}");
1074 $value = $parse->($ucdir, $value);
1076 $C->{$CDname{$directive}} = $value;
1080 @include = ($passed_file || $C->{ConfigFile});
1081 my %include_hash = ($include[0] => 1);
1083 my ($db, $dname, $nm);
1084 my ($before, $after);
1085 my $recno = 'C0001';
1088 if(! $existing and ! $subconfig) {
1089 @hidden_config = grep -f $_,
1090 "$C->{CatalogName}.site",
1091 "$Global::ConfDir/$C->{CatalogName}.before",
1092 @{$Global::ConfigAllBefore},
1095 # Backwards because of unshift;
1096 for (@hidden_config) {
1097 unshift @include, $_;
1098 $include_hash{$_} = 1;
1101 @hidden_config = grep -f $_,
1102 "$Global::ConfDir/$C->{CatalogName}.after",
1103 @{$Global::ConfigAllAfter},
1106 for (@hidden_config) {
1108 $include_hash{$_} = 1;
1112 # %MV::Default holds command-line mods to config, which we write
1113 # to a file for easier processing
1114 if(! $existing and defined $MV::Default{$catalog}) {
1115 my $fn = "$Global::RunDir/$catalog.cmdline";
1116 open(CMDLINE, ">$fn")
1117 or die "Can't create cmdline configfile $fn: $!\n";
1118 for(@{$MV::DefaultAry{$catalog}}) {
1119 my ($d, $v) = split /\s+/, $_, 2;
1121 $v = "<<EndOfMvD\n$v\nEndOfMvD\n";
1126 printf CMDLINE '%-19s %s', $d, $v;
1130 $include_hash{$_} = 1;
1134 if($Global::DumpAllCfg) {
1135 open ALLCFG, ">$Global::RunDir/allconfigs.cfg"
1138 # Create closure that reads and sets config values
1140 my ($lvar, $value, $tie, $var) = @_;
1142 # parse variables in the value if necessary
1143 if($C->{ParseVariables} and $value =~ /(?:__|\@\@)/) {
1144 save_variable($CDname{$lvar}, $value);
1145 $value = substitute_variable($value);
1148 # call the parsing function for this directive
1149 $parse = $parse{$lvar};
1150 $value = $parse->($CDname{$lvar}, $value) if defined $parse and ! $tie;
1152 # and set the $C->directive variable
1154 watch ( $CDname{$lvar}, $value );
1157 $C->{$CDname{$lvar}} = $value;
1161 #print "include starts with @include\n";
1163 while ($configfile = shift @include) {
1165 if(ref $configfile) {
1166 ($configfile, $tellmark) = @$configfile;
1167 #print "recalling $configfile (pos $tellmark)\n";
1170 # See if anything is defined in options to do before the
1171 # main configuration file. If there is a file, then we
1172 # will do it (after pushing the main one on @include).
1174 -f $configfile && open(CONFIG, "< $configfile")
1176 my $msg = "Could not open configuration file '" . $configfile .
1177 "' for catalog '" . $catalog . "':\n$!";
1178 if(defined $done_one) {
1186 print ALLCFG "# READING FROM $configfile\n" if $allcfg;
1187 seek(CONFIG, $tellmark, 0) if $tellmark;
1188 #print "seeking to $tellmark in $configfile, include is @include\n";
1189 my ($ifdef, $begin_ifdef);
1193 unless /^\s*include\s+/i;
1195 chomp; # zap trailing newline,
1196 if(/^\s*endif\s*$/i) {
1197 #print "found $_\n";
1202 if(/^\s*if(n?)def\s+(.*)/i) {
1203 if(defined $ifdef) {
1204 config_error("Can't overlap ifdef at line %s of %s", $., $configfile);
1206 $ifdef = evaluate_ifdef($2,$1);
1208 #print "found $_\n";
1211 if(defined $ifdef) {
1214 if(/^\s*include\s+(.+)/i) {
1215 #print "found $_\n";
1217 $spec = substitute_variable($spec) if $C->{ParseVariables};
1218 if ($include_hash{$spec}) {
1219 config_error("Possible infinite loop through inclusion of $spec at line %s of %s, skipping", $., $configfile);
1222 $include_hash{$spec} = 1;
1223 my $ref = [ $configfile, tell(CONFIG)];
1224 #print "saving config $configfile (pos $ref->[1])\n";
1225 #unshift @include, [ $configfile, tell(CONFIG) ];
1226 unshift @include, $ref;
1228 unshift @include, grep -f $_, glob($spec);
1232 my ($lvar, $value, $var, $tie) =
1233 read_config_value($_, \*CONFIG, $allcfg);
1237 # Use our closure defined above
1238 $read->($lvar, $value, $tie);
1240 # If we have passed off configuration to a database we stop here...
1241 last if $C->{ConfigDatabase}->{ACTIVE};
1243 # See if we want to load the config database
1244 if(! $db and $C->{ConfigDatabase}->{LOAD}) {
1245 $db = $C->{ConfigDatabase}->{OBJECT}
1247 "ConfigDatabase $C->{ConfigDatabase}->{'name'} not active.");
1248 $dname = $C->{ConfigDatabase}{name};
1251 # Actually load ConfigDatabase if present
1253 $nm = $CDname{$lvar};
1254 my ($extended, $status);
1257 # set directive name
1258 $status = Vend::Data::set_field($db, $recno, 'directive', $nm);
1261 "ConfigDatabase failed for %s, field '%s'",
1266 # use extended value field if necessary or directed
1267 if (length($value) > 250 or $UseExtended{$nm}) {
1269 $extended =~ s/(\S+)\s*//;
1271 $status = Vend::Data::set_field($db, $recno, 'extended', $extended);
1274 "ConfigDatabase failed for %s, field '%s'",
1280 # set value -- just a name if extended was used
1281 $status = Vend::Data::set_field($db, $recno, 'value', $value);
1284 "ConfigDatabase failed for %s, field '%s'",
1295 delete $include_hash{$configfile};
1297 # See if we have an active configuration database
1298 if($C->{ConfigDatabase}->{ACTIVE}) {
1299 my ($key,$value,$dir,@val);
1300 my $name = $C->{ConfigDatabase}->{name};
1301 $db = $C->{ConfigDatabase}{OBJECT} or
1302 config_error("ConfigDatabase called ACTIVE with no database object.\n");
1303 my $items = $db->array_query("select * from $name order by code");
1305 foreach $one ( @$items ) {
1306 ($key, $dir, @val) = @$one;
1307 $value = join " ", @val;
1308 $value =~ s/\s/\n/ if $value =~ /\n/;
1312 $read->($lvar, $value);
1316 if(defined $ifdef) {
1317 config_error("Failed to close #ifdef on line %s.", $begin_ifdef);
1322 # We need to make this directory if it isn't already there....
1323 if(! $existing and $C->{ScratchDir} and ! -e $C->{ScratchDir}) {
1324 mkdir $C->{ScratchDir}, 0700
1325 or die "Can't make temporary directory $C->{ScratchDir}: $!\n";
1328 return $C if $existing;
1330 # check for unspecified directives that don't have default values
1332 # but set some first if appropriate
1333 set_defaults() unless $C->{BaseCatalog};
1336 last REQUIRED if defined $subconfig;
1337 last REQUIRED if defined $Vend::ExternalProgram;
1338 foreach $var (keys %CDname) {
1339 if (! defined $C->{$CDname{$var}}) {
1341 "Please specify the %s directive in the configuration file '%s'",
1343 ($passed_file || $C->{ConfigFile}),
1351 # Set up hash of keys to hide for BounceReferrals and BounceReferralsRobot
1352 $C->{BounceReferrals_hide} = { map { ($_, 1) } grep { !(/^cookie-/ or /^session(?:$|-)/) } @{$C->{SourcePriority}} };
1353 my @exclude = qw( mv_form_charset mv_session_id mv_tmp_session );
1354 @{$C->{BounceReferrals_hide}}{@exclude} = (1) x @exclude;
1356 finalize_mapped_code();
1358 set_readonly_config();
1359 # Ugly legacy stuff so API won't break
1360 $C->{Special} = $C->{SpecialPage} if defined $C->{SpecialPage};
1366 sub read_container {
1367 my($start, $handle, $marker, $parse, $allcfg) = @_;
1368 my $lvar = lc $marker;
1369 my $var = $CDname{$lvar};
1371 #::logDebug("Read container start=$start marker=$marker lvar=$lvar var=$var parse=$parse");
1373 #::logDebug("Read container parse value=$CPname{$lvar}");
1374 my $sub = $ContainerSpecial{$var}
1375 || $ContainerSpecial{$lvar}
1376 || $ContainerType{$CPname{$lvar}};
1379 #::logDebug("Trigger special container");
1381 $sub->($var, $start);
1382 $ContainerTrigger{$lvar} ||= $sub;
1390 $value .= "$start\n";
1393 print ALLCFG $_ if $allcfg;
1394 if ($_ =~ m{^\s*</$marker>\s*$}i) {
1400 return undef unless $foundeot;
1402 $value =~ /((?s:.)*)/;
1408 my($handle, $marker, $allcfg) = @_;
1413 print ALLCFG $_ if $allcfg;
1414 if ($_ =~ m{^$marker$}) {
1420 return undef unless $foundeot;
1422 $value =~ /((?s:.)*)/;
1427 sub config_named_catalog {
1428 my ($cat_name, $source, $db_only, $dbconfig) = @_;
1431 $g = $Global::Catalog{$cat_name};
1432 unless (defined $g) {
1433 logGlobal( "Can't find catalog '%s'" , $cat_name );
1437 $Vend::Log_suppress = 1;
1439 unless ($db_only or $Vend::Quiet) {
1440 logGlobal( "Config '%s' %s%s", $g->{'name'}, $source );
1442 undef $Vend::Log_suppress;
1445 or die "Couldn't change to $g->{'dir'}: $!\n";
1449 "Config table '%s' (file %s) for catalog %s from %s",
1455 my $cfg = $Global::Selector{$g->{script}}
1456 or die errmsg("'%s' not a catalog (%s).", $g->{name}, $g->{script});
1457 undef $cfg->{Database}{$db_only};
1458 $Vend::Cfg = config(
1466 or die errmsg("error configuring catalog %s table %s: %s",
1477 $c = config($g->{'name'},
1480 $g->{'base'} || undef,
1482 # $Vend::CommandLine->{$g->{'name'}} || undef
1483 # END OPTION_EXTENSION
1489 logGlobal( "%s config error: %s" , $g->{'name'}, $msg );
1493 if (defined $g->{base}) {
1495 dump_structure($c, "$c->{RunDir}/$g->{name}") if $Global::DumpStructure;
1501 $::Variable = $Vend::Cfg->{Variable};
1502 $::Pragma = $Vend::Cfg->{Pragma};
1503 Vend::Data::read_salestax();
1504 Vend::Data::read_shipping();
1514 logGlobal( "%s config error: %s" , $g->{'name'}, $msg );
1518 dump_structure($c, "$c->{RunDir}/$g->{name}") if $Global::DumpStructure;
1520 my $status_dir = ($c->{Source}{RunDir} ? $c->{RunDir} : $c->{ConfDir});
1522 delete $c->{Source};
1524 my $stime = scalar localtime();
1525 writefile(">$Global::RunDir/status.$g->{name}", "$stime\n$g->{dir}\n");
1526 writefile(">$status_dir/status.$g->{name}", "$stime\n");
1535 sub get_system_groups {
1539 return if (m{^\.} || ! -f $_);
1540 $File::Find::name =~ m{/([^/]+)/([^/.]+)\.(\w+)$}
1544 my $ext = $extmap{lc $3} or return;
1545 $ext =~ /Tag$/ or return;
1546 push @files, [ $group, $tname ];
1548 File::Find::find({ wanted => $wanted, follow => 1 }, @$Global::TagDir);
1550 $Global::TagGroup ||= {};
1552 my $g = $Global::TagGroup->{":$_->[0]"} ||= [];
1558 sub get_repos_code {
1560 #::logDebug("get_repos_code called");
1561 return unless $Global::CodeRepository;
1563 return if $Vend::ControllingInterchange;
1567 return if (m{^\.} || ! -f $_);
1568 return unless m{^[^.]+\.(\w+)$};
1569 my $ext = $extmap{lc $1} or return;
1570 push @files, [ $File::Find::name, $ext];
1572 File::Find::find({ wanted => $wanted, follow => 1 }, $Global::CodeRepository);
1574 my $c = $Global::TagLocation = {};
1576 # %valid_dest is scoped as my variable above
1579 my $foundfile = $_->[0];
1581 open SYSTAG, "< $foundfile"
1584 my($lvar, $value) = read_config_value($_, \*SYSTAG);
1587 if($lvar eq 'codedef') {
1588 $value =~ s/^(\S+)\s+(\S+).*//s;
1589 $dest = $valid_dest{lc $2};
1592 elsif($dest = $valid_dest{$lvar}) {
1593 $value =~ m/^(\S+)\s+/
1597 next unless $dest and $name;
1602 $c->{$dest}{$name} ||= $foundfile;
1607 #::logDebug("repos is:\n" . ::uneval($Global::TagLocation));
1611 sub get_system_code {
1613 return if $CodeDest;
1614 return if $Vend::ControllingInterchange;
1616 # defined means don't go here anymore
1617 $SystemCodeDone = '';
1620 return if (m{^\.} || ! -f $_);
1621 return unless m{^[^.]+\.(\w+)$};
1622 my $ext = $extmap{lc $1} or return;
1623 push @files, [ $File::Find::name, $ext];
1625 File::Find::find({ wanted => $wanted, follow => 1 }, @$Global::TagDir);
1629 $CodeDest = $_->[1];
1631 $configfile = $_->[0];
1632 open SYSTAG, "< $configfile"
1633 or config_error("read system tag file %s: %s", $configfile, $!);
1635 my($lvar, $value) = read_config_value($_, \*SYSTAG);
1637 $GlobalRead->($lvar, $value);
1643 # 1 means read system tag directories
1644 $SystemCodeDone = 1;
1647 sub read_config_value {
1649 return undef unless $_;
1650 my ($fh, $allcfg) = @_;
1655 chomp; # zap trailing newline,
1656 s/^\s*#.*//; # comments,
1657 # mh 2/10/96 changed comment behavior
1658 # to avoid zapping RGB values
1660 s/\s+$//; # trailing spaces
1661 return undef unless $_;
1663 local($Vend::config_line);
1664 $Vend::config_line = $_;
1666 my $container_trigger;
1670 if(s{^[ \t]*<(/?)(\w+)\s*(.*)\s*>\s*$}{$2$3}) {
1671 $container_trigger = $1;
1672 $var = $container_here = $2;
1676 # lines read from the config file become untainted
1677 m/^[ \t]*(\w+)\s+(.*)/ or config_error("Syntax error from $_");
1681 ($lvar = $var) =~ tr/A-Z/a-z/;
1683 config_error("Unknown directive '%s'", $lvar), next
1684 unless defined $CDname{$lvar};
1686 my($codere) = '[-\w_#/.]+';
1688 if ($container_trigger) { # Apache container value
1689 if(my $sub = $ContainerTrigger{$lvar}) {
1690 $sub->($var, $value, 1);
1695 if ($container_here) { # Apache container value
1697 $begin .= "\n" if length $begin;
1698 my $mark = "</$container_here>";
1700 $value = read_container($begin, $fh, $container_here, \%parse);
1701 unless (defined $value) {
1702 config_error (sprintf('%d: %s', $startline,
1703 qq#no end contaner ("</$container_here>") found#));
1706 elsif ($value =~ /^(.*)<<(\w+)\s*/) { # "here" value
1707 my $begin = $1 || '';
1708 $begin .= "\n" if $begin;
1711 $value = $begin . read_here($fh, $mark);
1712 unless (defined $value) {
1713 config_error (sprintf('%d: %s', $startline,
1714 qq#no end marker ("$mark") found#));
1717 elsif ($value =~ /^(.*)<&(\w+)\s*/) { # "here sub" value
1718 my $begin = $1 || '';
1719 $begin .= "\n" if $begin;
1722 $value = $begin . read_here($fh, $mark, $allcfg);
1723 unless (defined $value) {
1724 config_error (sprintf('%d: %s', $startline,
1725 qq#no end marker ("$mark") found#));
1735 "No Tie::Watch module installed at %s, setting %s to default.",
1742 elsif ($value =~ /^(\S+)?(\s*)?<\s*($codere)$/o) { # read from file
1743 my $confdir = $C ? $C->{ConfigDir} : $Global::ConfigDir;
1746 $value .= "\n" if $value;
1749 "%s: Can't read from file until ConfigDir defined",
1753 $file = $CDname{$lvar} unless $file;
1755 # If the file isn't already specified with an absolute path, try the
1756 # Config directory, then the current directory. When neither file
1757 # exists, use the Config directory and continue.
1758 if ($file !~ m!^/!) {
1759 my $test_with_confdir = escape_chars("$confdir/$file");
1760 if (-f $test_with_confdir) {
1761 $file = $test_with_confdir;
1764 my $test_without_confdir = escape_chars($file);
1765 if (-f $test_without_confdir) {
1766 $file = $test_without_confdir;
1769 $file = $test_with_confdir;
1774 my $tmpval = readfile($file);
1775 unless( defined $tmpval ) {
1777 "%s: read from non-existent file %s, skipping.",
1783 chomp($tmpval) unless $tmpval =~ m!.\n.!;
1786 return($lvar, $value, $var, $tie);
1789 # Parse the global configuration file for directives. Each directive sets
1790 # the corresponding variable in the Global:: package. E.g.
1791 # "DisplayErrors No" in the config file sets Global::DisplayErrors to 0.
1792 # Directives which have no default value ("undef") must be specified
1793 # in the config file.
1795 my(%parse, $var, $value, $lvar, $parse);
1796 my($directive, $seen_catalog);
1802 my $directives = global_directives();
1804 $Global::Structure = {} unless $Global::Structure;
1806 # Prevent parsers from thinking it is a catalog
1809 foreach my $d (@$directives) {
1810 $directive = lc $d->[0];
1811 $CDname{$directive} = $d->[0];
1812 $CPname{$directive} = $d->[1];
1813 $parse = get_parse_routine($d->[1]);
1814 $parse{$directive} = $parse;
1817 ! defined $MV::Default{mv_global} or
1818 ! defined $MV::Default{mv_global}{$d->[0]}
1821 : $MV::Default{mv_global}{$d->[0]};
1823 if (defined $DumpSource{$CDname{$directive}}) {
1824 $Global::Structure->{ $CDname{$directive} } = $value;
1827 if (defined $parse and defined $value) {
1828 $value = $parse->($d->[0], $value);
1831 if(defined $value) {
1832 ${'Global::' . $CDname{$directive}} = $value;
1834 $Global::Structure->{ $CDname{$directive} } = $value
1835 unless defined $DontDump{ $CDname{$directive} };
1840 my (@include) = $Global::ConfigFile;
1842 # Create closure for reading of value
1845 my ($lvar, $value, $tie) = @_;
1847 #::logDebug("Doing a GlobalRead for $lvar") unless $Global::Foreground;
1848 unless (defined $CDname{$lvar}) {
1849 config_error("Unknown directive '%s'", $var);
1853 #::logDebug("Continuing a GlobalRead for $lvar") unless $Global::Foreground;
1854 if (defined $DumpSource{$CDname{$directive}}) {
1855 $Global::Structure->{ $CDname{$directive} } = $value;
1858 # call the parsing function for this directive
1859 $parse = $parse{$lvar};
1860 #::logDebug("parse routine is $parse for $CDname{$lvar}") unless $Global::Foreground;
1861 $value = $parse->($CDname{$lvar}, $value) if defined $parse;
1863 # and set the Global::directive variable
1864 ${'Global::' . $CDname{$lvar}} = $value;
1865 #::logDebug("It is now=" . ::uneval($value)) unless $Global::Foreground;
1866 $Global::Structure->{ $CDname{$lvar} } = $value
1867 unless defined $DontDump{ $CDname{$lvar} };
1870 $GlobalRead = $read;
1873 while ($configfile = shift @include) {
1875 if(ref $configfile) {
1876 ($configfile, $tellmark) = @$configfile;
1877 #print "recalling $configfile (pos $tellmark)\n";
1880 -f $configfile && open(GLOBAL, "< $configfile")
1883 "Could not open global configuration file '%s': %s",
1887 if(defined $done_one) {
1895 seek(GLOBAL, $tellmark, 0) if $tellmark;
1896 #print "seeking to $tellmark in $configfile, include is @include\n";
1897 my ($ifdef, $begin_ifdef);
1899 if(/^\s*endif\s*$/i) {
1905 if(/^\s*if(n?)def\s+(.*)/i) {
1907 if(defined $ifdef) {
1909 "Can't overlap ifdef at line %s of %s",
1914 $ifdef = evaluate_ifdef($2,$1,1);
1918 if(defined $ifdef) {
1921 if(/^\s*include\s+(.+)/) {
1924 my $ref = [ $configfile, tell(GLOBAL)];
1925 #print "saving config $configfile (pos $ref->[1])\n";
1926 unshift @include, $ref;
1929 unshift @include, grep -f $_, glob($spec);
1933 my ($lvar, $value, $tie) = read_config_value($_, \*GLOBAL);
1935 $read->($lvar, $value, $tie);
1942 # In case no user-supplied config has been given...returns
1943 # with no effect if that has been done already.
1944 get_system_code() unless defined $SystemCodeDone;
1946 # Directive post-processing
1947 global_directive_postprocess();
1950 set_global_defaults();
1952 # check for unspecified directives that don't have default values
1953 foreach $var (keys %CDname) {
1954 last if defined $Vend::ExternalProgram;
1955 if (!defined ${'Global::' . $CDname{$var}}) {
1956 die "Please specify the $CDname{$var} directive in the\n" .
1957 "configuration file '$Global::ConfigFile'\n";
1961 # Inits Global UserTag entries
1963 Vend::Parse::global_init;
1966 ## Pulls in the places where code can be found when AccumulatingTags
1967 get_repos_code() if $Global::AccumulateCode;
1969 finalize_mapped_code();
1971 dump_structure($Global::Structure, "$Global::RunDir/$Global::ExeName")
1972 if $Global::DumpStructure and ! $Vend::ExternalProgram;
1974 delete $Global::Structure->{Source};
1980 # Use Tie::Watch to attach subroutines to config variables
1982 my($name, $value) = @_;
1983 $C->{Tie_Watch} = [] unless $C->{Tie_Watch};
1984 push @{$C->{Tie_Watch}}, $name;
1987 #::logDebug("Contents of $name: " . uneval_it($C->{$name}));
1988 if(CORE::ref($C->{$name}) =~ /ARRAY/) {
1989 #::logDebug("watch ref=array");
1991 $orig = [ @{ $C->{$name} } ];
1993 elsif(CORE::ref($C->{$name}) =~ /HASH/) {
1994 #::logDebug("watch ref=hash");
1996 $orig = { %{ $C->{$name} } };
1999 #::logDebug("watch ref=scalar");
2000 $ref = \$C->{$name};
2001 $orig = $C->{$name};
2003 #::logDebug("watch ref=$ref orig=$orig name=$name value=$value");
2004 $C->{WatchIt} = { _mvsafe => $C->{ActionMap}{_mvsafe} } if ! $C->{WatchIt};
2005 parse_action('WatchIt', "$name $value");
2006 my $coderef = $C->{WatchIt}{$name}
2009 package Vend::Interpolate;
2011 my $key = $_[0]->Args(-fetch)->[0];
2012 return $coderef->(@_, $key);
2014 package Vend::Interpolate;
2015 $Vend::Config::C->{WatchIt}{$name} = Tie::Watch->new(
2017 -fetch => [$recode,$orig],
2021 sub get_wildcard_list {
2022 my($var, $value, $base) = @_;
2024 $value =~ s/\s*#.*?$//mg;
2027 return '' if ! $value;
2029 if($value !~ /\|/) {
2030 $value =~ s/([\\\+\|\[\]\(\){}])/\\$1/g;
2031 $value =~ s/\./\\./g;
2032 $value =~ s/\*/.*/g;
2034 my @items = grep /\S/, split /\s*,\s*/, $value;
2038 if ($base && $extra =~ s/^\.\*\\\.//){
2039 push(@items,$extra) if $extra;
2042 $value = join '|', @items;
2044 return parse_regex($var, $value);
2047 sub external_global {
2052 my @sets = grep /\w/, split /[\s,]+/, $value;
2053 #::logDebug( "Parsing sets=" . join(",", @sets) . "\n" );
2057 for my $set (@sets) {
2058 #::logDebug( "Parsing $set\n" );
2059 my @keys = split /->/, $set;
2060 my ($k, $v) = split /=/, $keys[0];
2063 if($k =~ m/^(\w+)::(\w+)$/) {
2067 $major ||= 'Global';
2069 my $walk = ${"${major}::$var"};
2070 my $ref = $main->{$v} = $walk;
2071 for(my $i = 1; $i < @keys; $i++) {
2072 my $current = $keys[$i];
2073 #::logDebug( "Walking $current\n" );
2075 if( CORE::ref($ref) eq 'ARRAY' ) {
2076 $current =~ s/\D+//g;
2078 or config_error("External: Bad array index $current from $set");
2079 $ref->[$current] = $walk->[$current];
2080 #::logDebug( "setting $current to ARRAY\n" );
2082 elsif( CORE::ref($ref) eq 'HASH' ) {
2083 $ref->{$current} = $walk->{$current};
2084 #::logDebug( "setting $current to HASH\n" );
2087 config_error("External: bad data structure for $set");
2091 $walk = $walk->{$current};
2092 #::logDebug( "Walking $current\n" );
2093 if( CORE::ref($walk) eq 'HASH' ) {
2094 $ref->{$current} = {};
2095 $ref = $ref->{$current};
2098 config_error("External: bad data structure for $set");
2106 # Set the External environment, dumps, etc.
2111 or config_error( "Not in catalog configuration context." );
2114 my @sets = grep /\w/, split /[\s,]+/, $value;
2115 for my $set (@sets) {
2116 my @keys = split /->/, $set;
2119 for(my $i = 0; $i < @keys; $i++) {
2120 my $current = $keys[$i];
2122 if( CORE::ref($ref) eq 'ARRAY' ) {
2123 $current =~ s/\D+//g;
2125 or config_error("External: Bad array index $current from $set");
2126 $ref->[$current] = $walk->[$current];
2128 elsif( CORE::ref($ref) eq 'HASH' ) {
2129 $ref->{$current} = $walk->{$current};
2132 config_error("External: bad data structure for $set");
2136 $walk = $walk->{$current};
2137 if( CORE::ref($walk) eq 'HASH' ) {
2138 $ref->{$current} ||= {};
2139 $ref = $ref->{$current};
2142 config_error("External: bad data structure for $set");
2151 # Set up an ActionMap or FormAction or FileAction
2153 my ($var, $value, $mapped) = @_;
2155 return $InitializeEmpty{$var} ? '' : {};
2158 return if $Vend::ExternalProgram;
2165 $c = $C->{$var} ||= {};
2169 $c = ${"Global::$var"} ||= {};
2172 if (defined $C and ! $c->{_mvsafe}) {
2173 my $calc = Vend::Interpolate::reset_calc();
2174 $c->{_mvsafe} = $calc;
2176 my ($name, $sub) = split /\s+/, $value, 2;
2180 ## Determine if we are in a catalog config, and if
2181 ## perl should be global and/or strict
2186 $nostrict = $Global::PerlNoStrict->{$C->{CatalogName}};
2187 $perlglobal = $Global::AllowGlobal->{$C->{CatalogName}};
2190 # Untaint and strip this pup
2191 $sub =~ s/^\s*((?s:.)*\S)\s*//;
2196 if($sub =~ /::/ and ! $C) {
2197 $c->{$name} = \&{"$sub"};
2200 if($C and $C->{Sub}) {
2201 $c->{$name} = $C->{Sub}{$sub};
2204 if(! $c->{$name} and $Global::GlobalSub) {
2205 $c->{$name} = $Global::GlobalSub->{$sub};
2208 if(! $c->{$name} and $AllowScalarAction{$var}) {
2211 elsif(! $c->{$name}) {
2212 $@ = errmsg("Mapped %s action routine '%s' is non-existent.", $var, $sub);
2215 elsif ( ! $mapped and $sub !~ /^sub\b/) {
2216 if($AllowScalarAction{$var}) {
2222 return Vend::Interpolate::interpolate_html(<<EndOfThisHaiRYTHING);
2227 $c->{$name} = eval $code;
2230 elsif ($perlglobal) {
2231 package Vend::Interpolate;
2234 $c->{$name} = eval $sub;
2237 $c->{$name} = eval $sub;
2241 package Vend::Interpolate;
2242 $c->{$name} = $c->{_mvsafe}->reval($sub);
2245 config_warn("Action '%s' did not compile correctly (%s).", $name, $@);
2253 $name = $CDname{lc $name} || $name;
2259 return ${"Global::$name"};
2263 # Adds features contained in FeatureDir called by catalog
2266 my ($var, $value) = @_;
2267 my $c = $C->{$var} || {};
2268 return $c unless $value;
2272 my $fdir = Vend::File::catfile($Global::FeatureDir, $value);
2275 config_warn("Feature '%s' not found, skipping.", $value);
2279 # Get the global install files and remove them from the config list
2280 my @gfiles = glob("$fdir/*.global");
2282 @seen{@gfiles} = @gfiles;
2284 # Get the init files and remove them from the config list
2285 my @ifiles = glob("$fdir/*.init");
2286 @seen{@ifiles} = @ifiles;
2288 # Get the uninstall files and remove them from the config list
2289 my @ufiles = glob("$fdir/*.uninstall");
2290 @seen{@ufiles} = @ifiles;
2292 # Any other files are config files
2293 my @cfiles = grep ! $seen{$_}++, glob("$fdir/*");
2295 # directories are for copying
2296 my @cdirs = grep -d $_, @cfiles;
2298 # strip the directories from the config list, leaving catalog.cfg stuff
2299 @cfiles = grep -f $_, @cfiles;
2301 # Don't install global more than once
2302 @gfiles = grep ! $Global::FeatureSeen{$_}++, @gfiles;
2304 # Place the catalog configuration in the config list
2305 unshift @include, @cfiles;
2309 return unless -f $_;
2310 my $n = $File::Find::name;
2312 my $d = $File::Find::dir;
2314 push @copy, [$n, $d];
2318 File::Find::find({ wanted => $wanted, follow => 1 }, @cdirs);
2320 #::logDebug("gfiles=" . ::uneval(\@gfiles));
2321 #::logDebug("cfiles=" . ::uneval(\@cfiles));
2322 #::logDebug("ifiles=" . ::uneval(\@ifiles));
2323 #::logDebug("ufiles=" . ::uneval(\@ufiles));
2324 #::logDebug("cdirs=" . ::uneval(\@cdirs));
2325 #::logDebug("copy=" . ::uneval(\@copy));
2330 my $tf = Vend::File::catfile($C->{VendRoot}, $n);
2333 my $td = Vend::File::catfile($C->{VendRoot}, $d);
2335 File::Path::mkpath($td)
2337 config_warn("Feature %s not able to make directory %s", $value, $td);
2341 File::Copy::copy("$fdir/$n", $tf)
2343 config_warn("Feature %s not able to copy %s to %s", $value, "$fdir/$n", $tf);
2353 my $initdir = Vend::File::catfile($C->{ConfDir}, 'init', $value);
2354 File::Path::mkpath($initdir) unless -d $initdir;
2355 my $unfile = Vend::File::catfile($initdir, 'uninstall');
2357 ## Feature was previously uninstalled, we *do* need to run init
2358 my $ignore = -f $unfile;
2362 or die errmsg("Couldn't unlink $unfile: $!");
2367 $fn =~ s{^$fdir/}{};
2369 unlink "$initdir/$fn"
2370 or die errmsg("Couldn't unlink $fn: $!");
2373 next if -f "$initdir/$fn";
2375 push @{$C->{Init}}, [$_, "$initdir/$fn"];
2379 #::logDebug("Init=" . ::uneval($C->{Init}));
2385 sub uninstall_feature {
2388 or die "Not in catalog context.\n";
2390 #::logDebug("Running uninstall for cat=$Vend::Cat, from cfg ref=$c->{CatalogName}");
2393 my $fdir = Vend::File::catfile($Global::FeatureDir, $value);
2396 config_warn("Feature '%s' not found, skipping.", $value);
2400 my $etag = errmsg("feature %s uninstall -- ", $value);
2402 # Get the global install files and remove them from the config list
2403 my @gfiles = glob("$fdir/*.global");
2405 @seen{@gfiles} = @gfiles;
2407 # Get the init files and remove them from the config list
2408 my @ifiles = glob("$fdir/*.init");
2409 @seen{@ifiles} = @ifiles;
2411 # Get the uninstall files and remove them from the config list
2412 my @ufiles = glob("$fdir/*.uninstall");
2413 @seen{@ufiles} = @ifiles;
2415 # Any other files are config files
2416 my @cfiles = grep ! $seen{$_}++, glob("$fdir/*");
2418 # directories are for copying
2419 my @cdirs = grep -d $_, @cfiles;
2421 my $Tag = new Vend::Tags;
2428 return unless -f $_;
2429 my $n = $File::Find::name;
2431 my $d = $File::Find::dir;
2433 push @copy, [$n, $d];
2437 File::Find::find({ wanted => $wanted, follow => 1 }, @cdirs);
2439 #::logDebug("ufiles=" . ::uneval(\@ufiles));
2440 #::logDebug("ifiles=" . ::uneval(\@ifiles));
2441 #::logDebug("cdirs=" . ::uneval(\@cdirs));
2442 #::logDebug("copy=" . ::uneval(\@copy));
2445 #::logDebug("Running uninstall file $_");
2446 my $save = $Global::AllowGlobal->{$Vend::Cat};
2447 $Global::AllowGlobal->{$Vend::Cat} = 1;
2450 push @errors, $etag . errmsg("error reading %s: %s", $_, $!);
2452 my $chunk = join "", <UNFILE>;
2455 #::logDebug("uninstall chunk length=" . length($chunk));
2459 $out = Vend::Interpolate::interpolate_html($chunk);
2463 push @errors, $etag . errmsg("error running uninstall %s: %s", $_, $@);
2466 push @warnings, $etag . errmsg("message from %s: %s", $_, $out)
2469 $Global::AllowGlobal->{$Vend::Cat} = $save;
2475 my $tf = Vend::File::catfile($c->{VendRoot}, $n);
2478 my $contents1 = Vend::File::readfile($tf);
2480 my $sf = "$fdir/$n";
2483 or die $etag . errmsg("Couldn't read uninstall source file %s: %s", $sf, $!);
2486 my $contents2 = <UNSRC>;
2488 if($contents1 ne $contents2) {
2489 push @warnings, $etag . errmsg("will not uninstall %s, changed.", $tf);
2496 $etag . errmsg("$etag couldn't unlink file %s: %s", $tf, $!);
2500 my $td = Vend::File::catfile($c->{VendRoot}, $d);
2501 my @left = glob("$td/*");
2502 push @left, glob("$td/.?*");
2504 File::Path::rmtree($td);
2508 #::logDebug("running uninstall touch and init");
2509 my $initdir = Vend::File::catfile($c->{ConfDir}, 'init', $value);
2510 File::Path::mkpath($initdir) unless -d $initdir;
2511 my $fn = Vend::File::catfile($initdir, 'uninstall');
2512 #::logDebug("touching uninstall file $fn");
2513 open UNFILE, ">> $fn"
2514 or die errmsg("Couldn't create uninstall flag file %s: %s", $fn, $!);
2515 print UNFILE $etag . errmsg("uninstalled at %s.\n", scalar(localtime));
2522 $Tag->error({ set => $_});
2536 # Changes configuration directives into Variable settings, i.e.
2537 # DescriptionField becomes __DescriptionField__, ProductFiles becomes
2538 # __ProductFiles_0__, ProductFiles_1__, etc. Doesn't handle hash keys
2539 # that have non-word chars.
2542 my($var, $val) = @_;
2544 return '' if ! $val;
2546 my @dirs = grep /\w/, split /[\s,\0]+/, $val;
2549 foreach $name (@dirs) {
2550 next unless $name =~ /^\w+$/;
2551 my $val = get_directive($name);
2553 parse_variable('Variable', "$name $val");
2555 elsif ($val =~ /ARRAY/) {
2556 for(my $i = 0; $i < @$val; $i++) {
2557 my $an = "${name}_$i";
2558 parse_variable('Variable', "$an $val->[$i]");
2561 elsif ($val =~ /HASH/) {
2563 while ( ($k, $v) = each %$val) {
2564 next unless $k =~ /^\w+$/;
2565 parse_variable('Variable', "$k $v");
2569 config_warn('%s directive not parsable by AutoVariable', $name);
2575 # Checks to see if a globalsub, sub, usertag, or Perl module is present
2576 # If called with a third parameter, is just "suggestion"
2577 # If called with a fourth parameter, is just capability check
2579 sub parse_capability {
2580 return parse_require(@_, 1, 1);
2583 sub parse_tag_group {
2584 my ($var, $setting) = @_;
2588 $c = $C->{$var} || {};
2592 $c = ${"Global::$var"} || {};
2595 $setting =~ tr/-/_/;
2596 $setting =~ s/[,\s]+/ /g;
2597 $setting =~ s/^\s+//;
2598 $setting =~ s/\s+$//;
2600 my @pairs = Text::ParseWords::shellwords($setting);
2603 my ($group, $sets) = splice @pairs, 0, 2;
2604 my @sets = grep $_, split /\s+/, $sets;
2605 my @groups = grep /:/, @sets;
2606 @sets = grep $_ !~ /:/, @sets;
2608 next unless $c->{$_};
2609 push @sets, @{$c->{$_}};
2611 $c->{$group} = \@sets;
2616 my %incmap = qw/TagInclude TagGroup/;
2617 sub parse_tag_include {
2618 my ($var, $setting) = @_;
2623 my $mapper = $incmap{$var} || 'TagGroup';
2625 $c = $C->{$var} || {};
2626 $g = $C->{$mapper} || {};
2630 $c = ${"Global::$var"} || {};
2631 $g = ${"Global::$mapper"} || {};
2634 $setting =~ s/"/ /g;
2635 $setting =~ s/^\s+//;
2636 $setting =~ s/\s+$//;
2637 $setting =~ s/[,\s]+/ /g;
2639 if($setting eq 'ALL') {
2640 return { ALL => 1 };
2645 get_system_groups() unless $SystemGroupsDone;
2647 my @incs = Text::ParseWords::shellwords($setting);
2656 "unknown %s %s included from %s",
2663 @things = @{$g->{$_}}
2670 my $not = s/^!// ? ! $not : $not;
2671 $c->{$_} = not $not;
2678 return parse_require(@_, 1);
2682 my($var, $val, $warn, $cap) = @_;
2684 return if $Vend::ExternalProgram;
2685 return if $Vend::ControllingInterchange;
2691 if($val =~ s/\s+"(.*)"//s) {
2692 $error_message = "\a\n\n$1\n";
2695 if($val =~ s%\s+((/[\w.-]+)+)%%) {
2700 $carptype = sub { return; };
2703 $carptype = sub { return parse_message('', @_) };
2704 $error_message = "\a\n\nSuggest %s %s for proper catalog operation. Not all functions will work!\n"
2705 unless $error_message;
2708 $carptype = \&config_error;
2709 $error_message ||= 'Required %s %s not present. Aborting '
2710 . ($C ? 'catalog' : 'Interchange daemon') . '.';
2717 $nostrict = $Global::PerlNoStrict->{$C->{CatalogName}};
2718 $perlglobal = $Global::AllowGlobal->{$C->{CatalogName}};
2721 my $vref = $C ? $C->{Variable} : $Global::Variable;
2723 my $testsub = sub { 0 };
2725 if($val =~ s/^globalsub\s+//i) {
2726 $require = $Global::GlobalSub;
2727 $name = 'GlobalSub';
2729 elsif($val =~ s/^sub\s+//i) {
2730 $require = $C->{Sub};
2733 elsif($val =~ s/^taggroup\s+//i) {
2734 $require = $Global::UserTag->{Routine};
2735 my @groups = grep /\S/, split /[\s,]+/, $val;
2739 if($ref = $Global::TagGroup->{$_}) {
2740 push @needed, @$ref;
2746 $name = "TagGroup $val member";
2747 $val = join " ", @needed;
2749 elsif($val =~ s/^usertag\s+//i) {
2756 my @tries = ($Global::UserTag->{Routine});
2757 push(@tries,$C->{UserTag}->{Routine}) if $C;
2760 return 1 if defined $_->{$name};
2765 elsif($val =~ s/^(?:perl)?module\s+//i) {
2767 $name = 'Perl module';
2771 if($module =~ s/\.pl$//) {
2774 $module =~ /[^\w:]/ and return undef;
2777 unshift(@INC, $pathinfo);
2779 eval "require $module$oldtype;";
2784 ::logGlobal("while eval'ing module %s got [%s]\n", $module, $error) if $error;
2788 # Since we aren't safe to actually require, we will
2789 # just look for a readable module file
2790 $module =~ s!::!/!g;
2791 $oldtype = '.pm' if ! $oldtype;
2794 next unless -f "$_/$module$oldtype" and -r _;
2801 elsif ($val =~ s/^(?:perl)?include\s+//i) {
2802 my $path = Vend::File::make_absolute_file($val, 1);
2804 $name = 'Perl include path';
2808 unshift @INC, $path;
2814 elsif ($val =~ s/^file\s*//i) {
2816 $name = 'Readable file';
2817 $val = $pathinfo unless $val;
2820 my $path = Vend::File::make_absolute_file(shift, $C ? 0 : 1);
2821 if ($C && $path =~ s:^/+::) {
2822 $path = "$C->{VendRoot}/$path";
2827 elsif ($val =~ s/^executable\s*//i) {
2829 $name = 'Executable file';
2830 $val = $pathinfo unless $val;
2833 my $path = Vend::File::make_absolute_file(shift, $C ? 0 : 1);
2834 if ($C && $path =~ s:^/+::) {
2835 $path = "$C->{VendRoot}/$path";
2840 my @requires = grep /\S/, split /\s+/, $val;
2842 my $uname = uc $name;
2843 $uname =~ s/.*\s+//;
2845 $vref->{"MV_REQUIRE_${uname}_$_"} = 1;
2846 next if defined $require->{$_};
2847 next if $testsub->($_);
2848 delete $vref->{"MV_REQUIRE_${uname}_$_"};
2849 $carptype->( $error_message, $name, $_ );
2854 # Sets the special variable remap array
2864 my($item,$settings) = @_;
2866 return if $Vend::ExternalProgram;
2868 my($iv,$vn,$k,$v,@set);
2869 #logDebug("parse_varname: $settings");
2871 return '' if ! $settings;
2872 $C->{IV} = { %{$Global::IV} } if ! $C->{IV};
2873 $C->{VN} = { %{$Global::VN} } if ! $C->{VN};
2878 if (! $Global::VarName) {
2879 unless (-s "$Global::ConfDir/varnames" && -r _) {
2880 $settings = $Varnames . "\n$settings";
2881 writefile("$Global::ConfDir/varnames", $Varnames);
2884 $settings = readfile("$Global::ConfDir/varnames");
2888 $Global::IV = {} if ! $Global::IV;
2889 $Global::VN = {} if ! $Global::VN;
2894 @set = grep /\S/, split /\s+/, $settings;
2895 while( $k = shift @set, $v = shift @set ) {
2903 my($name, $val) = @_;
2905 return '' unless $val;
2906 unless ($val =~ /^\w+$/) {
2907 config_error("Illegal non-word value in '%s' for %s", $val, $name);
2912 # Allow addition of a new catalog directive
2913 sub parse_directive {
2914 my($name, $val) = @_;
2916 return '' unless $val;
2917 my($dir, $parser, $default) = split /\s+/, $val, 3 ;
2918 if(! defined &{"parse_$parser"} and ! defined &{"$parser"}) {
2919 if (defined $Global::GlobalSub->{"parse_$parser"}) {
2921 *{"Vend::Config::parse_$parser"} = $Global::GlobalSub->{"parse_$parser"};
2926 $default = '' if ! $default or $default eq 'undef';
2927 $Global::AddDirective = [] unless $Global::AddDirective;
2928 push @$Global::AddDirective, [ $dir, $parser, $default ];
2929 return $Global::AddDirective;
2932 # Allow a subcatalog value to completely replace a base value
2934 my($name, $val) = @_;
2936 return {} unless $val;
2938 $C->{$val} = get_catalog_default($val);
2939 $C->{$name}->{$val} = 1;
2944 # Send a message during configuration, goes to terminal if during
2945 # daemon startup, always goes to error log
2947 my($name, $val) = @_;
2949 return '' unless $val;
2951 return 1 if $Vend::Quiet;
2955 ## strip trailing whitespace if -n beins message
2956 while($val =~ s/^-([ni])\s+//) {
2957 $1 eq 'n' and $val =~ s/^-n\s+// and $strip = 1 and $val =~ s/\s+$//;
2958 $info_only = 1 if $1 eq 'i';
2961 my $msg = errmsg($val,
2967 if($info_only and $Global::Foreground) {
2971 logGlobal({level => 'info', strip => $strip },
2982 # Warn about directives no longer supported in the configuration file.
2984 my($name, $val) = @_;
2986 return '' unless $val;
2988 ::logGlobal({level => 'info'},
2989 errmsg("Directive %s no longer supported at line %s of %s.",
2997 # Each of the parse functions accepts the value of a directive from the
2998 # configuration file as a string and either returns the parsed value or
2999 # signals a syntax error.
3001 # Sets a boolean array for any type of item
3003 my($item,$settings) = @_;
3004 my(@setting) = grep /\S/, split /[\s,]+/, $settings;
3008 $c = $C->{$item} || {};
3012 $c = ${"Global::$item"} || {};
3021 # Sets a boolean array, but configurable value with tag=value
3022 sub parse_boolean_value {
3023 my($item,$settings) = @_;
3024 my(@setting) = split /[\s,]+/, $settings;
3028 $c = $C->{$item} || {};
3032 $c = ${"Global::$item"} || {};
3038 ($k,$v) = split /=/, $_, 2;
3050 setlocale localeconv
3051 LC_ALL LC_CTYPE LC_COLLATE
3052 LC_MONETARY LC_NUMERIC LC_TIME
3055 # Sets the special locale array. Tries to use POSIX setlocale,
3056 # accepts a 'custom' setting with the proper definitions of
3057 # decimal_point, mon_thousands_sep, and frac_digits (the only supported at
3058 # the moment). Otherwise uses US-English settings if not set.
3061 my($item,$settings) = @_;
3062 return ($settings || '') unless $settings =~ /[^\d.]/;
3063 $settings = '' if "\L$settings" eq 'default';
3067 $c = $C->{$item} || { };
3068 $C->{$item . "_repository"} = {}
3069 unless $C->{$item . "_repository"};
3070 $store = $C->{$item . "_repository"};
3074 $c = ${"Global::$item"} || {};
3075 ${"Global::$item" . "_repository"} = {}
3076 unless ${"Global::$item" . "_repository"};
3077 $store = ${"Global::$item" . "_repository"};
3081 if ($settings =~ s/^\s*([-\w.@]+)(?:\s+)?//) {
3085 $settings =~ /^\s*{/
3086 and $settings =~ /}\s*$/
3088 $eval and ! $safe and $safe = new Vend::Safe;
3089 if(! defined $store->{$name} and $item eq 'Locale') {
3090 my $past = POSIX::setlocale(POSIX::LC_ALL);
3091 if(POSIX::setlocale(POSIX::LC_ALL, $name) ) {
3092 $store->{$name} = POSIX::localeconv();
3094 POSIX::setlocale(POSIX::LC_ALL, $past);
3099 $sethash = $safe->reval($settings)
3100 or config_warn("bad Locale setting in %s: %s", $name, $@),
3104 $settings =~ s/^\s+//;
3105 $settings =~ s/\s+$//;
3107 %{$sethash} = Text::ParseWords::shellwords($settings);
3109 $c = $store->{$name} || {};
3110 my $nodefaults = delete $sethash->{MV_LOCALE_NO_DEFAULTS};
3111 for (keys %{$sethash}) {
3112 $c->{$_} = $sethash->{$_};
3116 config_error("Bad locale setting $settings.\n");
3119 $C->{LastLocale} = $name if $C and $item eq 'Locale';
3121 $store->{$name} = $c unless $store->{$name};
3127 # Sets a structure like Locale but with the depth and access via key
3128 # No evaled structure setting, only key-value with shell quoting
3130 sub parse_structure {
3131 my ($item, $settings) = @_;
3132 return {} unless $settings;
3135 ($key, @rest) = Text::ParseWords::shellwords($settings);
3139 $e = $c->{$key} || { };
3143 $c = ${"Global::$item"};
3144 $e = $c->{$key} || {};
3147 while(scalar @rest) {
3148 my $k = shift @rest;
3149 $e->{$k} = shift @rest;
3156 # Sets the special page array
3158 my($item,$settings) = @_;
3159 return {} unless $settings;
3160 my(%setting) = grep /\S/, split /[\s,]+/, $settings;
3161 for (keys %setting) {
3162 if($Global::NoAbsolute and file_name_is_absolute($setting{$_}) ) {
3163 config_warn("Absolute file name not allowed: %s", $setting{$_});
3166 $C->{$item}{$_} = $setting{$_};
3171 # Sets up a hash value from a configuration directive, syntax is
3173 # Directive "key" "value"
3175 # quotes are optional if word-only chars
3178 my($item,$settings) = @_;
3180 return $HashDefaultBlank{$item} ? '' : {};
3186 $c = $C->{$item} || {};
3190 $c = ${"Global::$item"} || {};
3193 return hash_string($settings,$c);
3196 # Set up illegal values for certain directives
3197 my %IllegalValue = (
3199 AutoModifier => { qw/ mv_mi 1
3208 UseModifier => { qw/ mv_mi 1
3222 %Cleanup_priority = (
3226 %Dispatch_priority = (
3229 DiscountSpaces => 5,
3235 #::logDebug("Doing AutoEnd dispatch...");
3236 Vend::Dispatch::run_macro($Vend::Cfg->{AutoEnd});
3243 #::logDebug("Doing Autoload dispatch...");
3244 my ($subname, $inspect_sub);
3246 if ($subname = $Vend::Cfg->{SpecialSub}{autoload_inspect}) {
3247 $inspect_sub = $Vend::Cfg->{Sub}{$subname} || $Global::GlobalSub->{$subname};
3250 Vend::Dispatch::run_macro($Vend::Cfg->{Autoload}, undef, $inspect_sub);
3253 CookieLogin => sub {
3254 #::logDebug("Doing CookieLogin dispatch....");
3255 if(! $Vend::Session->{logged_in}) {
3257 # Clear password cookie and don't allow automatic login
3258 # if mv_force_session is overriding the session cookie,
3259 # since user may be coming from a sister site where he
3261 (Vend::Util::read_cookie('MV_PASSWORD')
3262 and Vend::Util::set_cookie('MV_PASSWORD')), last COOKIELOGIN
3263 if $CGI::values{mv_force_session};
3267 if exists $CGI::values{mv_username}
3268 and defined $CGI::values{mv_username};
3270 unless $username = Vend::Util::read_cookie('MV_USERNAME');
3272 unless $password = Vend::Util::read_cookie('MV_PASSWORD');
3273 $CGI::values{mv_username} = $username;
3274 $CGI::values{mv_password} = $password;
3275 my $profile = Vend::Util::read_cookie('MV_USERPROFILE');
3277 undef $SIG{__DIE__};
3279 Vend::UserDB::userdb('login', profile => $profile );
3282 $Vend::Session->{failure} .= $@;
3289 #::logDebug("Doing Locale dispatch...");
3290 my $locale = $::Scratch->{mv_locale};
3291 my $curr = $::Scratch->{mv_currency};
3292 $locale || $curr or return;
3294 if($locale and ! $::Scratch->{mv_language}) {
3295 $Global::Variable->{LANG}
3296 = $::Variable->{LANG}
3297 = $::Scratch->{mv_language}
3302 return unless defined $Vend::Cfg->{Locale_repository}{$locale};
3305 return unless defined $Vend::Cfg->{Locale_repository}{$curr};
3307 #::logDebug("running locale dispatch, locale=$locale, currency=$curr");
3309 Vend::Util::setlocale( $locale, $curr, { persist => 1 } );
3312 DiscountSpaces => sub {
3313 #::logDebug("Doing DiscountSpaces dispatch...");
3315 = $Vend::Session->{discount}
3316 = $Vend::Session->{discount_space}{
3317 $Vend::DiscountSpaceName = 'main'
3321 for (@{$Vend::Cfg->{DiscountSpaceVar}}) {
3322 next unless $dspace = $CGI::values{$_};
3323 #::logDebug("$_ is set=...");
3326 return unless $dspace;
3327 $Vend::DiscountSpaceName = $dspace;
3328 #::logDebug("Discount space is set=$Vend::DiscountSpaceName...");
3330 = $Vend::Session->{discount}
3331 = $Vend::Session->{discount_space}{$Vend::DiscountSpaceName}
3337 # Set up defaults for certain directives
3338 my $Have_set_global_defaults;
3340 # Set the default search files based on ProductFiles setting
3341 # Honor a NO_SEARCH parameter in the Database structure
3342 # Set MV_DEFAULT_SEARCH_FILE to the {file} entry,
3343 # and set MV_DEFAULT_SEARCH_TABLE to the table name.
3345 # Error out if not SubCatalog and can't find a setting.
3347 sub set_default_search {
3348 my $setting = $C->{ProductFiles};
3351 return 1 if $C->{BaseCatalog};
3352 return (undef, errmsg("No ProductFiles setting!") );
3360 if ($C->{Variable}{MV_DEFAULT_SEARCH_FILE}) {
3364 $C->{Variable}{MV_DEFAULT_SEARCH_FILE};
3368 next unless exists $C->{Database}{$_};
3369 $_ = $C->{Database}{$_}{file};
3372 if ($C->{Variable}{MV_DEFAULT_SEARCH_TABLE}) {
3374 grep defined $C->{Database}{$_},
3376 $C->{Variable}{MV_DEFAULT_SEARCH_TABLE}
3382 next if $C->{Database}{$_}{NO_SEARCH};
3383 push @tout, $_ unless $notable;
3384 next unless defined $C->{Database}{$_}{file};
3385 push @fout, $C->{Database}{$_}{file}
3388 unless (scalar @fout) {
3389 return 1 if $C->{BaseCatalog};
3390 return (undef, errmsg("No default search file!") );
3392 $C->{Variable}{MV_DEFAULT_SEARCH_FILE} = \@fout;
3393 $C->{Variable}{MV_DEFAULT_SEARCH_TABLE} = \@tout;
3398 ## This rather extensive default setting is not typical for IC,
3399 ## but performance in pricing routines demands it
3401 my $o = $C->{Options_repository} ||= {};
3402 my $var = $C->{Variable};
3404 my @base = qw/Simple Matrix Old48/;
3406 @base{@base} = @base;
3409 my @types = grep !$seen{$_}++, keys %$o, @base;
3412 my $loc = $o->{$_} ||= {};
3413 eval "require Vend::Options::$_;";
3417 "Unable to use options type %s, no module. Error: %s",
3425 my $name = "Vend::Options::${_}::Default";
3427 while(my ($k,$v) = each %{"$name"}) {
3429 next if exists $loc->{$k};
3434 if($loc->{remap} ||= $C->{Variable}{MV_OPTION_TABLE_MAP}) {
3435 $loc->{remap} =~ s/^\s+//;
3436 $loc->{remap} =~ s/\s+$//;
3437 my @points = split /[\0,\s]+/, $loc->{remap};
3438 map { m{(.*?)=(.*)} and $loc->{map}{$1} = $2} @points;
3441 $C->{Options} = $o->{default} || $o->{Simple};
3444 my $o = $C->{Shipping_repository} ||= {};
3446 my @base = qw/Postal/;
3448 @base{@base} = @base;
3451 my @types = grep !$seen{$_}++, keys %$o, @base;
3453 my %module_ignore = qw/resolution 1 default 1/;
3456 next if $module_ignore{$_};
3457 my $loc = $o->{$_} ||= {};
3458 eval "require Vend::Ship::$_;";
3462 "Unable to use options type %s, no module. Error: %s",
3470 my $name = "Vend::Ship::${_}::Default";
3472 while(my ($k,$v) = each %{"$name"}) {
3474 next if exists $loc->{$k};
3479 $C->{Shipping} = $o->{default} || $o->{Postal};
3482 my $set = $C->{UserDB_repository};
3484 next unless defined $set->{$_}{admin};
3485 $C->{AdminUserDB} = {} unless $C->{AdminUserDB};
3486 $C->{AdminUserDB}{$_} = $set->{$_}{admin};
3490 UserControl => sub {
3491 return 1 unless shift;
3492 require Vend::UserControl;
3495 AutoModifier => sub {
3497 if($C->{OptionsEnable}) {
3498 $auto = $C->{AutoModifier} = []
3500 push @$auto, $C->{OptionsEnable};
3504 OptionsEnable => sub {
3507 return 1 if $C->{OptionsAttribute};
3509 $C->{OptionsAttribute} = $enable;
3513 return 1 unless shift;
3514 require Vend::Glimpse;
3517 SOAP_Socket => sub {
3518 return 1 if $Have_set_global_defaults;
3519 $Global::SOAP_Socket = ['7780']
3520 if $Global::SOAP and ! $Global::SOAP_Socket;
3524 return 1 if defined $Have_set_global_defaults;
3525 my (@sets) = keys %{$Global::TcpMap};
3526 if(scalar @sets == 1 and $sets[0] eq '-') {
3527 $Global::TcpMap = {};
3530 $Global::TcpMap->{7786} = '-';
3535 for ( keys %{$C->{Database}}) {
3536 push @del, $_ unless defined $C->{Database}{$_}{type};
3539 #::logDebug("deleted non-existent db $_");
3540 delete $C->{Database}{$_};
3545 my $repos = $C->{Locale_repository}
3547 if ($C->{DefaultLocale}) {
3548 my $def = $C->{DefaultLocale};
3549 if (exists($repos->{$def})) {
3550 $C->{Locale} = $repos->{$def};
3553 return (0, errmsg('Default locale %s missing', $def));
3558 if($repos->{$_}{default}) {
3559 $C->{Locale} = $repos->{$_};
3560 $C->{DefaultLocale} = $_;
3563 if(! $C->{DefaultLocale} and $C->{LastLocale}) {
3564 $C->{DefaultLocale} = $C->{LastLocale};
3565 $C->{Locale} = $repos->{$C->{LastLocale}};
3569 # create currency repositories
3570 for my $locale (keys %{$C->{Locale_repository}}) {
3571 for my $key (@Locale_keys_currency) {
3572 if ($C->{Locale_repository}->{$locale}->{$key}) {
3573 $C->{Currency_repository}->{$locale}->{$key}
3574 = $C->{Locale_repository}->{$locale}->{$key};
3579 push @Dispatches, 'Locale';
3583 DiscountSpacesOn => sub {
3584 return 1 unless $C->{DiscountSpacesOn};
3585 push @Dispatches, 'DiscountSpaces';
3588 CookieLogin => sub {
3589 return 1 unless $C->{CookieLogin};
3590 push @Dispatches, 'CookieLogin';
3593 ProductFiles => \&set_default_search,
3595 my $cat_template_dirs = $C->{TemplateDir} || [];
3596 if ($Global::NoAbsolute) {
3597 for (@$cat_template_dirs) {
3598 if (absolute_or_relative($_) and ! /^$C->{VendRoot}/) {
3599 config_error("TemplateDir path %s is prohibited by NoAbsolute", $_);
3603 my @paths = map { quotemeta $_ }
3605 @$cat_template_dirs,
3606 @{$Global::TemplateDir || []};
3607 my $re = join "|", @paths;
3608 $Global::AllowedFileRegex->{$C->{CatalogName}} = qr{^($re)};
3612 return 1 unless $C->{Autoload};
3613 push @Dispatches, 'Autoload';
3617 return 1 unless $C->{AutoEnd};
3618 push @Cleanups, 'AutoEnd';
3622 return 1 unless $C->{External};
3623 unless($Global::External) {
3624 config_warn("External directive set to Yes, but not allowed by Interchange configuration.");
3627 return 1 unless $C->{External};
3628 unless($Global::ExternalStructure) {
3629 $Global::ExternalStructure = external_global($Global::ExternalExport);
3631 $C->{ExternalExport} = external_cat($C->{ExternalExport});
3632 $Global::ExternalStructure->{Catalogs}{ $C->{CatalogName} }{external_config}
3633 = $C->{ExternalExport};
3634 Vend::Util::uneval_file($Global::ExternalStructure, $Global::ExternalFile);
3635 chmod 0644, $Global::ExternalFile;
3639 sub global_directive_postprocess {
3640 if ($Global::UrlSepChar eq '&') {
3641 if ($Global::Variable->{MV_HTML4_COMPLIANT}) {
3642 $Global::UrlJoiner = '&';
3643 $Global::UrlSplittor = qr/\&|\&/;
3646 $Global::UrlJoiner = '&';
3647 $Global::UrlSplittor = qr/\&/;
3651 $Global::UrlJoiner = $Global::UrlSepChar;
3652 $Global::UrlSplittor = qr/[&$Global::UrlSepChar]/o;
3655 $Global::CountrySubdomains ||= {};
3657 while (my ($key,$val) = each(%$Global::CountrySubdomains)) {
3658 $val =~ s/[\s,]+$//;
3661 $val = '\.(?:' . join('|',split('[\s,]+',$val)) . ")\\.$key";
3662 $Global::CountrySubdomains->{$key} = qr/$val/i;
3666 sub set_global_defaults {
3667 ## Nothing here currently
3670 my @readonly_members = qw/
3675 sub set_readonly_config {
3676 my $cat = $C->{CatalogName} or return;
3677 my $ro = $Global::ReadOnlyCfg{$cat} ||= {};
3678 for(@readonly_members) {
3679 $ro->{$_} = copyref($C->{$_});
3686 for(keys %Default) {
3687 my ($status, $error) = $Default{$_}->($C->{$_});
3689 return config_error(
3691 'Directive %s returned default setting error: %s',
3697 @Dispatches = sort { $Dispatch_priority{$a} cmp $Dispatch_priority{$b} } @Dispatches;
3698 @Cleanups = sort { $Cleanup_priority{$a} cmp $Cleanup_priority{$b} } @Cleanups;
3700 push @{ $C->{DispatchRoutines} ||= [] }, $Dispatch_code{$_};
3703 push @{ $C->{CleanupRoutines} ||= [] }, $Cleanup_code{$_};
3706 # check MV_HTTP_CHARSET against a valid encoding
3707 if ( !$ENV{MINIVEND_DISABLE_UTF8} &&
3708 (my $enc = $C->{Variable}->{MV_HTTP_CHARSET}) ) {
3709 if (my $norm_enc = Vend::CharSet::validate_encoding($enc)) {
3710 if ($norm_enc ne uc($enc)) {
3711 config_warn("Provided MV_HTTP_CHARSET '$enc' resolved to '$norm_enc'. Continuing.");
3712 $C->{Variable}->{MV_HTTP_CHARSET} = $norm_enc;
3716 config_error("Unrecognized/unsupported MV_HTTP_CHARSET: '%s'.", $enc);
3717 delete $C->{Variable}->{MV_HTTP_CHARSET};
3721 $Have_set_global_defaults = 1;
3725 sub parse_url_sep_char {
3730 if($val =~ /[\w%]/) {
3732 errmsg("%s character value '%s' must not be word character or %%.", $var, $val)
3735 elsif(length($val) > 1) {
3737 "%s character value '%s' longer than one character.",
3742 elsif($val !~ /[&;:]/) {
3743 config_warn("%s character value '%s' not a recommended value.", $var, $val);
3750 my ($directive, $value) = @_;
3751 return 1 unless defined $IllegalValue{$directive}->{$value};
3752 config_error ("\nYou may not use a value of '$value' in the $directive directive.");
3756 my($item,$settings) = @_;
3757 return '' unless $settings;
3758 my(@setting) = grep /\S/, split /[\s,]+/, $settings;
3763 $c = $C->{$item} || [];
3767 $c = ${"Global::$item"} || [];
3771 check_legal($item, $_);
3777 sub parse_routine_array {
3778 my($item,$settings) = @_;
3780 return '' unless $settings;
3788 $c = ${"Global::$item"};
3793 if($settings =~ /^[-\s\w,]+$/) {
3794 @mac = grep /\S/, split /[\s,]+/, $settings;
3797 push @mac, $settings;
3800 if(ref($c) eq 'ARRAY') {
3807 $c = scalar(@mac) > 1 ? [ @mac ] : $mac[0];
3813 sub parse_array_complete {
3814 my($item,$settings) = @_;
3815 return '' unless $settings;
3816 my(@setting) = grep /\S/, split /[\s,]+/, $settings;
3821 check_legal($item, $_);
3828 sub parse_list_wildcard {
3829 my $value = get_wildcard_list(@_,0);
3830 return '' unless length($value);
3834 sub parse_list_wildcard_full {
3835 my $value = get_wildcard_list(@_,1);
3836 return '' unless length($value);
3837 return qr/^($value)$/i;
3840 # Make a dos-ish regex into a Perl regex, check for errors
3841 sub parse_wildcard {
3842 my($var, $value) = @_;
3843 return '' if ! $value;
3845 $value =~ s/\./\\./g;
3846 $value =~ s/\*/.*/g;
3850 [ local $_ = $1; tr/{,}/(|)/; $_ ]eg;
3851 $value =~ s/\s+/|/g;
3853 my $never = 'NeVAirBE';
3854 $never =~ m{$value};
3858 config_error("Bad regular expression in $var.");
3864 # Check that a regex won't cause a syntax error. Uses m{}, which
3865 # should be used for all user-input regexes.
3867 my($var, $value) = @_;
3870 my $never = 'NeVAirBE';
3871 $never =~ m{$value};
3875 config_error("Bad regular expression in $var.");
3880 sub parse_ip_address_regexp {
3882 my ($var, $value) = @_;
3883 return '' unless $value;
3886 require Net::IP::Match::Regexp;
3888 $@ and config_error("$var directive requires module: $@");
3890 my $re = Net::IP::Match::Regexp::create_iprange_regexp($value)
3891 or config_error("Improper IP address range for $var");
3895 # Prepend the Global::VendRoot pathname to the relative directory specified,
3896 # unless it already starts with a leading /.
3898 sub parse_root_dir {
3899 my($var, $value) = @_;
3900 return '' unless $value;
3901 $value = "$Global::VendRoot/$value"
3902 unless file_name_is_absolute($value);
3907 sub parse_root_dir_array {
3908 my($var, $value) = @_;
3909 return [] unless $value;
3912 my $c = ${"Global::$var"} || [];
3914 my @dirs = grep /\S/, Text::ParseWords::shellwords($value);
3916 foreach my $dir (@dirs) {
3917 $dir = "$Global::VendRoot/$dir"
3918 unless file_name_is_absolute($dir);
3925 sub parse_dir_array {
3926 my($var, $value) = @_;
3927 return [] unless $value;
3929 $C->{$var} = [] unless $C->{$var};
3930 my $c = $C->{$var} || [];
3932 my @dirs = grep /\S/, Text::ParseWords::shellwords($value);
3934 foreach my $dir (@dirs) {
3935 unless (allowed_file($dir)) {
3936 config_error('Path %s not allowed in %s directive',
3939 $dir = "$C->{VendRoot}/$dir"
3940 unless file_name_is_absolute($dir);
3948 sub parse_relative_dir {
3949 my($var, $value) = @_;
3951 if (absolute_or_relative($value)) {
3952 config_error('Path %s not allowed in %s directive',
3956 $C->{Source}{$var} = $value;
3958 $value = "$C->{VendRoot}/$value"
3959 unless file_name_is_absolute($value);
3964 # Ensure only an integer value in the directive
3966 my($var, $value) = @_;
3967 $value = hex($value) if $value =~ /^0x[\dA-Fa-f]+$/;
3968 $value = oct($value) if $value =~ /^0[0-7]+$/;
3969 config_error("The $var directive (now set to '$value') must be an integer\n")
3970 unless $value =~ /^\d+$/;
3974 # Make sure no trailing slash in VendURL etc.
3976 my($var, $value) = @_;
3981 # Parses a time specification such as "1 day" and returns the
3982 # number of seconds in the interval, or undef if the string could
3985 sub time_to_seconds {
3989 ($n, $dur) = ($str =~ m/(\d+)[\s\0]*(\w+)?/);
3990 return undef unless defined $n;
3993 if (m/^s|sec|secs|second|seconds$/i) {
3995 elsif (m/^m|min|mins|minute|minutes$/i) {
3998 elsif (m/^h|hour|hours$/i) {
4001 elsif (m/^d|day|days$/i) {
4004 elsif (m/^w|week|weeks$/i) {
4005 $n *= 7 * 24 * 60 * 60;
4015 sub parse_valid_group {
4016 my($var, $value) = @_;
4018 return '' unless $value;
4020 my($name,$passwd,$gid,$members) = getgrnam($value);
4022 config_error("$var: Group name '$value' is not a valid group\n")
4023 unless defined $gid;
4024 $name = getpwuid($<);
4025 config_error("$var: Interchange user '$name' not in group '$value'\n")
4026 unless $members =~ /\b$name\b/;
4030 sub parse_executable {
4031 my($var, $initial) = @_;
4043 foreach my $value (@tries) {
4044 #::logDebug("trying $value for $var");
4048 return $value if $Global::Windows;
4049 if( ! defined $value or $value eq '') {
4052 elsif( $value eq 'none') {
4056 elsif( $value =~ /^\w+::[:\w]+\w$/) {
4057 ## Perl module like Net::SMTP
4059 eval "require $value";
4065 elsif ($root =~ m#^/# and -x $root) {
4070 my @path = split /:/, $ENV{PATH};
4072 next unless -x "$_/$root";
4078 config_error( errmsg(
4079 "Can't find executable (%s) for the %s directive\n",
4083 ) unless defined $x;
4084 #::logDebug("$var=$x");
4089 my($var, $value) = @_;
4092 return $value unless $value;
4094 # $C->{Source}->{$var} = [$value];
4096 $n = time_to_seconds($value);
4097 config_error("Bad time format ('$value') in the $var directive\n")
4103 my($var, $value) = @_;
4105 return '' unless $value =~ /\s/ and $value =~ /[a-zA-Z]/;
4107 unless($Vend::Cron::Loaded) {
4109 "Cannot use %s unless %s module loaded%s",
4112 ' (missing Set::Crontab?)',
4116 return Vend::Cron::read_cron($value);
4119 # Determine catalog structure from Catalog config line(s)
4121 my ($var, $setting) = @_;
4122 my $num = ! defined $Global::Catalog ? 0 : $Global::Catalog;
4123 return $num unless (defined $setting && $setting);
4125 my($name,$base,$dir,$script, @rest);
4126 ($name,@rest) = Text::ParseWords::shellwords($setting);
4140 my ($cat, $key, $value);
4141 if ($Global::Catalog{$name}) {
4143 $cat = $Global::Catalog{$name};
4145 $value = shift @rest;
4148 $var =~ /subcatalog/i and
4150 and file_name_is_absolute($rest[1])
4159 splice(@rest, 0, 3);
4160 $cat->{alias} = [ @rest ]
4163 elsif( file_name_is_absolute($rest[0]) ) {
4169 splice(@rest, 0, 2);
4170 $cat->{alias} = [ @rest ]
4175 $value = shift @rest;
4176 $cat = { name => $name };
4179 $key = $remap{$key} if $key && defined $remap{$key};
4184 elsif($key eq 'alias' or $key eq 'server') {
4185 $cat->{$key} = [] if ! $cat->{$key};
4186 push @{$cat->{$key}}, $value;
4187 push @{$cat->{$key}}, @rest if @rest;
4189 elsif($key eq 'global') {
4190 $cat->{$key} = $Global::AllowGlobal->{$name} = is_yes($value);
4192 elsif($key eq 'directive') {
4195 my $v = join " ", @rest;
4196 $cat->{$key} = {} if ! $cat->{$key};
4197 my $ref = set_directive($p, $v, 1);
4199 if(ref $ref->[1] =~ /HASH/) {
4200 if(! $cat->{$key}{$ref->[0]} ) {
4201 $cat->{$key}{$ref->[0]} = { %{"Global::$ref->[0]"} };
4203 for (keys %{$ref->[1]}) {
4204 $cat->{$key}{$ref->[0]}{$_} = $ref->[1]->{$_};
4208 $cat->{$key}{$ref->[0]} = $ref->[1];
4212 $cat->{$key} = $value;
4215 #::logDebug ("parsing catalog $name = " . uneval_it($cat));
4217 $Global::Catalog{$name} = $cat;
4219 # Define the main script name and array of aliases
4223 my %Explode_ref = ( qw!
4224 COLUMN_DEF COLUMN_DEF
4227 my %Hash_ref = ( qw!
4228 FILTER_FROM FILTER_FROM
4230 LENGTH_EXCEPTION LENGTH_EXCEPTION
4232 DEFAULT_SESSION DEFAULT_SESSION
4233 FIELD_ALIAS FIELD_ALIAS
4235 PREFER_NULL PREFER_NULL
4236 WRITE_CATALOG WRITE_CATALOG
4243 POSTCREATE POSTCREATE
4246 ALTERNATE_DSN ALTERNATE_DSN
4247 ALTERNATE_USER ALTERNATE_USER
4248 ALTERNATE_PASS ALTERNATE_PASS
4249 ALTERNATE_BASE_DN ALTERNATE_BASE_DN
4250 ALTERNATE_LDAP_HOST ALTERNATE_LDAP_HOST
4251 ALTERNATE_BIND_DN ALTERNATE_BIND_DN
4252 ALTERNATE_BIND_PW ALTERNATE_BIND_PW
4253 POSTEXPORT POSTEXPORT
4256 sub parse_config_db {
4257 my($name, $value) = @_;
4259 unless (defined $value && $value) {
4265 $d = $C->{ConfigDatabase};
4268 $d = $Global::ConfigDatabase;
4271 my($database,$remain) = split /[\s,]+/, $value, 2;
4273 $d->{'name'} = $database;
4275 if(!defined $d->{'file'}) {
4276 my($file, $type) = split /[\s,]+/, $remain, 2;
4277 $d->{'file'} = $file;
4278 if( $type =~ /^\d+$/ ) {
4279 $d->{'type'} = $type;
4281 elsif( $type =~ /^(dbi|sql)\b/i ) {
4283 if($type =~ /^dbi:/) {
4288 elsif( $type =~ /^ldap\b/i) {
4290 if($type =~ /^ldap:(.*)/i) {
4291 $d->{LDAP_HOST} = $1;
4295 elsif( "\U$type" eq 'TAB' ) {
4298 elsif( "\U$type" eq 'PIPE' ) {
4301 elsif( "\U$type" eq 'CSV' ) {
4304 elsif( "\U$type" eq 'DEFAULT' ) {
4307 elsif( $type =~ /[%]{1,3}|percent/i ) {
4310 elsif( $type =~ /line/i ) {
4315 $d->{DELIMITER} = $type;
4319 my($p, $val) = split /\s+/, $remain, 2;
4322 if(defined $Explode_ref{$p}) {
4324 my(@v) = Text::ParseWords::shellwords($val);
4325 @v = grep defined $_, @v;
4326 $d->{$p} = {} unless defined $d->{$p};
4328 my ($sk,$v) = split /\s*=\s*/, $_;
4329 my (@k) = grep /\w/, split /\s*,\s*/, $sk;
4331 if($d->{$p}->{$k}) {
4333 qq{Database %s explode parameter %s redefined to "%s", was "%s".},
4340 $d->{$p}->{$k} = $v;
4344 elsif(defined $Hash_ref{$p}) {
4346 my(@v) = Vend::Util::quoted_comma_string($val);
4347 @v = grep defined $_, @v;
4348 $d->{$p} = {} unless defined $d->{$p};
4350 ($k,$v) = split /\s*=\s*/, $_;
4351 if($d->{$p}->{$k}) {
4353 qq{Database %s hash parameter %s redefined to "%s", was "%s".},
4360 $d->{$p}->{$k} = $v;
4363 elsif(defined $Ary_ref{$p}) {
4364 my(@v) = Text::ParseWords::shellwords($val);
4365 $d->{$p} = [] unless defined $d->{$p};
4366 push @{$d->{$p}}, @v;
4370 and ! defined $C->{DatabaseDefault}{$p}
4372 qq{Database %s scalar parameter %s redefined to "%s", was "%s".},
4382 #::logDebug("d object: " . uneval_it($d));
4383 if($d->{ACTIVE} and ! $d->{OBJECT}) {
4384 my $name = $d->{'name'};
4385 $d->{OBJECT} = Vend::Data::import_database($d)
4386 or config_error("Config database $name failed import.\n");
4388 elsif($d->{LOAD} and ! $d->{OBJECT}) {
4389 my $name = $d->{'name'};
4390 $d->{OBJECT} = Vend::Data::import_database($d)
4391 or config_error("Config database $name failed import.\n");
4392 if( $d->{type} == 8 ) {
4393 $d->{OBJECT}->set_query("delete from $name where 1 = 1");
4402 my ($var, $value) = @_;
4403 return '' unless $value;
4404 my @inc = Vend::Table::DBI::auto_config($value);
4407 my ($t, $thing) = @$_;
4408 parse_boolean('NoImport', $t) unless $noed{$t}++;
4409 parse_database('Database', "$t $thing");
4414 sub parse_database {
4415 my ($var, $value) = @_;
4423 $c = $C ? $C->{Database} : $Global::Database;
4425 my($database,$remain) = split /[\s,]+/, $value, 2;
4427 if( ! defined $c->{$database} ) {
4428 $c->{$database} = { 'name' => $database, included_from => $configfile };
4432 my $d = $c->{$database};
4435 my($file, $type) = split /[\s,]+/, $remain, 2;
4436 $d->{'file'} = $file;
4437 if($file eq 'AUTO_SEQUENCE') {
4438 # database table missing for AUTO_SEQUENCE directive
4439 config_error('Missing database %s for AUTO_SEQUENCE %s.', $database, $type);
4442 if( $type =~ /^\d+$/ ) {
4443 $d->{'type'} = $type;
4445 elsif( $type =~ /^(dbi|sql)\b/i ) {
4447 if($type =~ /^dbi:/) {
4452 elsif( $type =~ /^ldap\b/i) {
4454 if($type =~ /^ldap:(.*)/i) {
4455 $d->{LDAP_HOST} = $1;
4459 elsif( $type =~ /^ic:(\w*)(:(.*))?/ ) {
4462 $d->{DIR} = $dir if $dir;
4463 if($class =~ /^default$/i) {
4468 if(! $Vend::Data::db_config{$class}) {
4469 config_error("unrecognized IC database class: %s (from %s)", $class, $type);
4471 $d->{Class} = $class;
4475 elsif( "\U$type" eq 'TAB' ) {
4478 elsif( "\U$type" eq 'PIPE' ) {
4481 elsif( "\U$type" eq 'CSV' ) {
4484 elsif( "\U$type" eq 'DEFAULT' ) {
4487 elsif( $type =~ /[%]{1,3}|percent/i ) {
4490 elsif( $type =~ /line/i ) {
4495 $d->{DELIMITER} = $type;
4497 if ($d->{'type'} eq '8') { $d->{Class} = 'DBI' }
4498 elsif ($d->{'type'} eq '9') { $d->{Class} = 'LDAP' }
4499 else { $d->{Class} ||= $Global::Default_database }
4501 if($C and $C->{DatabaseDefault}) {
4502 while ( my($k, $v) = each %{$C->{DatabaseDefault}}) {
4507 $d->{HOT} = 1 if $d->{Class} eq 'MEMORY';
4508 #::logDebug("parse_database: type $type -> $d->{type}");
4511 my($p, $val) = split /\s+/, $remain, 2;
4513 #::logDebug("parse_database: parameter $p = $val");
4515 if(defined $Explode_ref{$p}) {
4519 my(@v) = Text::ParseWords::shellwords($val);
4520 @v = grep length $_, @v;
4521 $d->{$p} = {} unless defined $d->{$p};
4523 my ($sk,$v) = split /\s*=\s*/, $_;
4524 my (@k) = grep /\w/, split /\s*,\s*/, $sk;
4526 if($d->{$p}->{$k}) {
4528 qq{Database %s explode parameter %s redefined to "%s", was "%s".},
4535 $d->{$p}->{$k} = $v;
4539 elsif(defined $Hash_ref{$p}) {
4541 my(@v) = Vend::Util::quoted_comma_string($val);
4542 @v = grep defined $_, @v;
4543 $d->{$p} = {} unless defined $d->{$p};
4545 ($k,$v) = split /\s*=\s*/, $_;
4546 if($d->{$p}->{$k}) {
4548 qq{Database %s hash parameter %s redefined to "%s", was "%s".},
4555 $d->{$p}->{$k} = $v;
4558 elsif(defined $Ary_ref{$p}) {
4559 my(@v) = Text::ParseWords::shellwords($val);
4560 $d->{$p} = [] unless defined $d->{$p};
4561 push @{$d->{$p}}, @v;
4563 elsif ($p eq 'COMPOSITE_KEY') {
4565 if($d->{type} == 8) {
4566 $d->{Class} = 'DBI_CompositeKey';
4571 'Database %s parameter in type with no handling. Ignored.',
4576 elsif ($p eq 'CLASS') {
4579 elsif ($p =~ /^(MEMORY|SDBM|GDBM|DB_FILE|LDAP)$/i) {
4580 $d->{Class} = uc $p;
4582 elsif ($p eq 'ALIAS') {
4583 if (defined $c->{$val}) {
4584 config_warn("Database '%s' already exists, can't alias.", $val);
4590 elsif ($p =~ /^MAP/) {
4591 Vend::Table::Shadow::_parse_config_line ($d, $p, $val);
4596 and ! defined $C->{DatabaseDefault}{$p}
4599 qq{Database %s scalar parameter %s redefined to "%s", was "%s".},
4607 $d->{HOT} = 1 if $d->{Class} eq 'MEMORY';
4614 my ($var, $value) = @_;
4615 my ($table, $file, $type);
4616 unless ($C->{Database}{$value}) {
4617 return if $Vend::ExternalProgram;
4618 ($table, $file, $type) = split /\s+/, $value, 3;
4619 $file = "$table.txt" unless $file;
4620 $type = 'TAB' unless $type;
4621 parse_database('Database',"$table $file $type");
4622 unless ($C->{Database}{$table}) {
4624 "Bad $var value '%s': %s\n%s",
4625 "Database $table $file $type",
4626 uneval($C->{Database}),
4636 unless ($db = $C->{Database}{$table}) {
4637 return if $Vend::ExternalProgram;
4639 config_warn("Bad $var '%s': %s", $table, $err);
4643 $db = Vend::Data::import_database($db);
4646 my $err = $@ || errmsg("Unable to import table '%s' for config.", $table);
4647 delete $C->{Database}{$table};
4650 return ($db, $table);
4653 my %Columnar = (Locale => 1);
4655 sub parse_configdb {
4656 my ($var, $value) = @_;
4659 return '' if ! $value;
4660 local($Vend::Cfg) = $C;
4663 ($db, $table) = get_configdb($var, $value);
4665 ::logGlobal("$var $value: $@") if $@;
4668 my ($k, @f); # key and fields
4669 my @l; # refs to locale repository
4670 my @n; # names of locales
4671 my @h; # names of locales
4673 my $base_direc = $var;
4674 $base_direc =~ s/Database$//;
4675 my $repos_name = $base_direc . '_repository';
4676 my $repos = $C->{$repos_name} ||= {};
4678 @n = $db->columns();
4681 if($Columnar{$base_direc}) {
4684 $repos->{$_} ||= {};
4685 push @l, $repos->{$_};
4688 while( ($k , undef, @f ) = $db->each_record) {
4689 for ($i = 0; $i < @f; $i++) {
4690 next unless length($f[$i]);
4691 $l[$i]->{$k} = $f[$i];
4696 while( ($k, undef, @f ) = $db->each_record) {
4697 for ($i = 0; $i < @f; $i++) {
4698 next unless length($f[$i]);
4699 $repos->{$k}{$n[$i]} = $f[$i];
4707 sub parse_dirconfig {
4708 my ($var, $value) = @_;
4710 return '' if ! $value;
4711 $value =~ s/(\w+)\s+//;
4713 #::logDebug("direc=$direc value=$value");
4715 my $ref = $C->{$direc};
4717 unless(ref($ref) eq 'HASH') {
4718 config_error("DirConfig called for non-hash configuration directive.");
4721 my $source = $C->{$var} || {};
4722 my $sref = $source->{$direc} || {};
4724 my @dirs = grep -d $_, glob($value);
4725 foreach my $dir (@dirs) {
4726 opendir(DIRCONFIG, $dir)
4728 my @files = grep /^\w+$/, readdir(DIRCONFIG);
4730 next unless -f "$dir/$_";
4731 #::logDebug("reading key=$_ from $dir/$_");
4732 $ref->{$_} = readfile("$dir/$_", $Global::NoAbsolute, 0);
4733 $ref->{$_} = substitute_variable($ref->{$_}) if $C->{ParseVariables};
4734 $sref->{$_} = "$dir/$_";
4737 $source->{$direc} = $sref;
4741 sub parse_dbconfig {
4742 my ($var, $value) = @_;
4745 return '' if ! $value;
4746 local($Vend::Cfg) = $C;
4750 ($db, $table) = get_configdb($var, $value);
4755 my ($k, @f); # key and fields
4756 my @l; # refs to locale repository
4757 my @n; # names of locales
4758 my @h; # names of locales
4760 @n = $db->columns();
4764 my $real = $CDname{lc $_};
4765 if (! ref $Vend::Cfg->{$real} or $Vend::Cfg->{$real} !~ /HASH/) {
4766 # ignore non-existent directive, but put in hash
4769 push @h, [$real, $ref];
4772 push @l, $Vend::Cfg->{$real};
4775 while( ($k, undef, @f ) = $db->each_record) {
4776 #::logDebug("Got key=$k f=@f");
4777 for ($i = 0; $i < @f; $i++) {
4778 next unless length($f[$i]);
4779 $l[$i]->{$k} = $f[$i];
4783 $Vend::Cfg->{Hash}{$_->[0]} = $_->[1];
4790 my ($var, $value) = @_;
4791 my ($c, $ref, $sref, $i);
4794 $C->{"${var}Name"} = {} if ! $C->{"${var}Name"};
4795 $sref = $C->{Source};
4796 $ref = $C->{"${var}Name"};
4797 $c = $C->{$var} || [];
4801 $sref = $Global::Source;
4802 ${"Global::${var}Name"} = {}
4803 if ! ${"Global::${var}Name"};
4804 $ref = ${"Global::${var}Name"};
4805 $c = ${"Global::$var"} || [];
4808 $sref->{$var} = $value;
4810 my (@files) = glob($value);
4814 "No leading / allowed if NoAbsolute set. Contact administrator.\n")
4815 if m.^/. and $Global::NoAbsolute;
4817 "No leading ../.. allowed if NoAbsolute set. Contact administrator.\n")
4818 if m#^\.\./.*\.\.# and $Global::NoAbsolute;
4819 push @$c, (split /\s*[\r\n]+__END__[\r\n]+\s*/, readfile($_));
4821 for($i = 0; $i < @$c; $i++) {
4822 if($c->[$i] =~ s/(^|\n)__NAME__\s+([^\n\r]+)\r?\n/$1/) {
4832 # Parse ordered or named attributes just like in a usertag. Needs to have the routine specified as follows:
4833 # ['Foo', sub { &parse_ordered_attributes(@_, [qw(foo bar baz)]) }, 'foo bar baz'],
4834 # If called directly in the normal fashion then you cannot specify the attribute order, but you can
4835 # still use it for parsing named attributes. The results are stored as a hashref (think $opt)
4836 sub parse_ordered_attributes {
4837 my ($var, $value, $order) = @_;
4839 return {} if $value !~ /\S/;
4841 my @settings = Text::ParseWords::shellwords($value);
4843 if ($settings[0] =~ /=/) {
4844 %opt = map { (split /=/, $_, 2)[0, 1] } @settings;
4847 elsif (ref $order eq 'ARRAY') {
4848 @opt{@$order} = @settings;
4852 config_error("$var only accepts named attributes.");
4858 # Designed to parse catalog subroutines and all vars
4860 my ($var, $value) = @_;
4861 my ($c, $name, $param);
4868 $c = ${"Global::$var"};
4871 if ($var eq 'Variable' || $var eq 'Member') {
4872 $value =~ s/^\s*(\w+)\s*//;
4874 return 1 if defined $c->{'save'}->{$name};
4876 $c->{'save'}->{$name} = $value;
4878 elsif ( !defined $C) {
4881 elsif ( defined $C->{Source}{$var} && ref $C->{Source}{$var}) {
4882 push @{$C->{Source}{$var}}, $value;
4884 elsif ( defined $C->{Source}{$var}) {
4885 $C->{Source}{$var} .= "\n$value";
4888 $C->{Source}{$var} = $value;
4896 my $return = ($gref = $Vend::Cfg->{CodeDef}{Widget})
4899 if(my $ref = $Global::CodeDef->{Widget}{Routine}) {
4900 while ( my ($k, $v) = each %$ref) {
4901 next if $return->{$k};
4905 if(my $ref = $Global::CodeDef->{Widget}{MapRoutine}) {
4907 while ( my ($k, $v) = each %$ref) {
4908 next if $return->{$k};
4909 $return->{$k} = \&{"$v"};
4912 if(my $ref = $Global::CodeDef->{Widget}{attrDefault}) {
4914 while ( my ($k, $v) = each %$ref) {
4915 next if $return->{$k};
4916 $return->{$k} = \&{"$v"};
4922 sub map_widget_defaults {
4924 my $return = ($gref = $Vend::Cfg->{CodeDef}{Widget})
4925 ? $gref->{attrDefault}
4927 if(my $ref = $Global::CodeDef->{Widget}{attrDefault}) {
4928 while ( my ($k, $v) = each %$ref) {
4929 next if $return->{$k};
4936 sub map_codedef_to_directive {
4946 $cfg = $C->{$type} ||= {};
4949 $c = $Global::CodeDef;
4950 $cfg =${"Global::$type"} ||= {};
4956 next unless $r = $c->{$type};
4957 next unless $ref = $r->{Routine};
4960 $cfg->{$_} = $ref->{$_};
4964 sub global_map_codedef {
4966 map_codedef_to_directive($type);
4967 Vend::Dispatch::update_global_actions();
4973 #::logDebug("Called filter MappedInit");
4975 #::logDebug("No \$C");
4977 my $c = $Global::CodeDef;
4978 my $typeref = $c->{Filter}
4980 my $submap = $typeref->{Routine}
4983 for(keys %$submap) {
4984 #::logDebug("Setting Filter for $_=$submap->{$_}");
4985 $Vend::Interpolate::Filter{$_} = $submap->{$_};
4987 if (my $ref = $typeref->{Alias}) {
4988 #::logDebug("We have an Alias ref");
4990 #::logDebug("Checking Alias ref for $_=$ref->{$_}");
4991 if (exists $Vend::Interpolate::Filter{$ref->{$_}}) {
4992 #::logDebug("Setting Alias ref to $Vend::Interpolate::Filter{$ref->{$_}}");
4994 = $Vend::Interpolate::Filter{$_}
4995 = $Vend::Interpolate::Filter{$ref->{$_}};
4999 #::logDebug("Filter is " . ::uneval(\%Vend::Interpolate::Filter));
5001 ItemAction => \&map_codedef_to_directive,
5002 OrderCheck => \&map_codedef_to_directive,
5003 ActionMap => \&global_map_codedef,
5004 FormAction => \&global_map_codedef,
5006 return unless $Vend::Cfg;
5007 $Vend::UserWidget = map_widgets();
5008 $Vend::UserWidgetDefault = map_widget_defaults();
5012 return unless $Vend::Cfg;
5013 Vend::Parse::add_tags($Global::UserTag);
5017 sub finalize_mapped_code {
5020 @types = grep $_, values %valid_dest;
5023 for my $type (@types) {
5024 if(my $sub = $MappedInit{$type}) {
5032 PosRoutine PosRoutine
5037 sub parse_mapped_code {
5038 my ($var, $value) = @_;
5040 return {} if ! $value;
5042 ## Can't give CodeDef a default or this will be premature
5043 get_system_code() unless defined $SystemCodeDone;
5045 my($tag,$p,$val) = split /\s+/, $value, 3;
5048 $p = $tagCanon{lc $p} || ''
5049 or ::logDebug("bizarre mapped code line '$value'");
5052 and config_warn("Bad characters removed from '%s'.", $tag);
5054 my $repos = $C ? ($C->{CodeDef} ||= {}) : ($Global::CodeDef ||= {});
5060 my $dest = $valid_dest{lc $p} || $current_dest{$tag} || $CodeDest;
5063 config_warn("no destination for %s %s, skipping.", $var, $tag);
5066 $current_dest{$tag} = $dest;
5067 $repos->{$dest} ||= {};
5069 my $c = $repos->{$dest};
5072 $c->{$Compiled{$p}} ||= {};
5073 parse_action($var, "$tag $val", $c->{$Compiled{$p}} ||= {});
5075 elsif(defined $tagAry{$p}) {
5076 my(@v) = Text::ParseWords::shellwords($val);
5077 $c->{$p}{$tag} = [] unless defined $c->{$p}{$tag};
5078 push @{$c->{$p}{$tag}}, @v;
5080 elsif(defined $tagHash{$p}) {
5081 my(%v) = Text::ParseWords::shellwords($val);
5082 $c->{$p}{$tag} = {} unless defined $c->{$p}{$tag};
5084 $c->{$p}{$tag}{$_} = $v{$_};
5087 elsif(defined $tagBool{$p}) {
5089 unless defined $val and $val =~ /^[0nf]/i;
5092 config_warn("%s %s scalar parameter %s redefined.", $var, $tag, $p)
5093 if defined $c->{$p}{$tag};
5094 $c->{$p}{$tag} = $val;
5100 # Parses the user tags
5102 my ($var, $value) = @_;
5105 #::logDebug("parse_tag var=$var val=$value") unless $Global::Foreground;
5106 return if $Vend::ExternalProgram;
5108 unless (defined $value && $value) {
5112 return parse_mapped_code($var, $value)
5113 if $var ne 'UserTag';
5115 #::logDebug("ready to read tag, C='$C' SystemCodeDone=$Syst