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 => $_});