# See bottom of file for license and copyright information
package Foswiki::Config;
=begin TML
---+!! Class Foswiki::Config
Class representing configuration data.
---++ General concepts
The first and primary function of this class is to serve as a placeholder for
the configuration hash stored in [[?%QUERYSTRING%#ObjectAttributeData][=data=]] attribute.
Additionally this class implements:
1. Bootstrapping of a fresh install
1. Specs handling
1. Some API
There're two modes a =Foswiki::Class= object may be in: _data_ and _specs_. The
difference between them is in whether =data= attribute contains a normal plain
hash (_data_ mode) or a tied one (_specs_ mode). Whichever one is active at any
given moment of time it doesn't affect application functionality – see
[[https://perldoc.perl.org/perltie.html][perltie]].
---+++ Specs mode
A =Foswiki::Config= class is switched into _specs_ mode with a call to
[[?%QUERYSTRING%#ObjectMethodSpecsMode][=specsMode()=]] method. The switch could
be performed dynamically at any time (as well as switching back to _data_) and
preserves all configuration data.
This mode is mainly needed by the =configure= script as it provides all required
information about configuration keys and their attributes. Additionally it can
be used for debugging purposes as values stored into the configuration hash
could be transparently verified for their validity.
The following classes are working together to implement _specs_ mode:
| *Class* | *Functionality* |
| =Foswiki::Config::DataHash= | =data= attribute hash is tied to it;\
considered as top-level container class |
| =Foswiki::Config::Node= | Represents information about a key stored in\
the configuration hash |
| =Foswiki::Config::Section= | A configuration section |
---+++ Globals
This class installs the global hash =%Foswiki::cfg= to provide compatibility
with legacy code. The hash is an alias to the
[[?%QUERYSTRING%?#ObjectAttributeData][=data=]] attribute. It's assumed that
normally there is only one instance of =Foswiki::Config= class and it's the one
stored in =Foswiki::App= =cfg= attribute. For that reason =%Foswiki::cfg= is
assumed to be the configuration hash of currently active application. This
assumption could be workarounded using
[[?%QUERYSTRING%#ObjectMethodAssignGlob][=assignGLOB()=]] and
[[?%QUERYSTRING%#ObjectMethodUnassignGlob][=unAssignGLOB()=]] methods.
---++ Terminology
*LSC* is an abbreviation used throughout this documention in place of "local
site configuration".
A configuration *key* is a sequence of characters starting with a word character
(=\w= in [[https://perldoc.perl.org/perlre.html][Perl regexps]]) and followed by
any character except ='.'= (a dot), or ='='= (an equal sign), or ='{'=, or ='}'=
(curly braces).
*Spec* is a file containing information about attributes of LSC keys. The
attributes include but not limited to key default value, description,
config section it belongs to, etc.
*Key path* is a sequence of configuration *keys* in either dot or curly braces
notation. For example, a dot notation:
JQueryPlugin.Plugins.Animate.Enabled
Curly braces notation:
{JQueryPlugin}{Plugins}{Animate}{Enabled}
*Full key path* is a *key path* which includes all *keys* to identify a
distinctive configuration entry. The examples above are both representing a full
path. Sometimes a *partial key path* might be used to shorten a notation. Like,
for example, when it's known that we're speaking about =JQueryPlugin.Plugins=
part of the configuration then it would be ok to use just =Animate.Enabled=.
Current application object would often be referred here as =$app=. Most of the
time this is a shortcut for =$this->app= or legacy code supporting
=$Foswiki::app= variable. Whenever a reference to an instance of this class is
needed =$app->cfg= notation would be used.
---++ LSC File Format
The new LSC file has a line-based format. A line in the file could be a:
* comment starting with =#=
* empty line (whitespaces are allowed)
* record line
* data line of a here-document
A record line is a line starting with a full key path in dot-notation, followed
by an equal sign, and then by a valid Perl data or nothing. 'Valid data' means
that what is placed on the right side of the equal sign must produce a valid
output after being passed as a parameter to
=[[https://perldoc.perl.org/functions/eval.html][eval]]= function. For example:
MailerContrib.RespectUserPrefs='LANGUAGE'
MaxLSCBackups=10
The data could be a multi-line represented by a here-document. As any other kind
of data it must represent a valid Perl data would it be a simple multi-line string
or a complex structure:
Log.Action=< 1,
'changes' => 1,
'compare' => 1,
'edit' => 1,
'rdiff' => 1,
'register' => 1,
'rename' => 1,
'rest' => 1,
'save' => 1,
'search' => 1,
'upload' => 1,
'view' => 1,
'viewfile' => 1
}
CF_DIWS
If nothing follows the equal sign then the key's value is undefined:
Store.Encoding=
---++ Macro expansion
Macros in a configuration hash must be expanded using =expandStr()= method.
A macro is a specially formatted string embedded into a configuration key
value. The macro string starts with ='$'= (dollar sign) and followed by
full key path enclosed in curly braces. See the =parseKeys()= method and
how it handles embraced keys.
Here is few examples of valid macro strings:
${JQueryPlugin.Plugins.JEditable.Module}
${JQueryPlugin}{Plugins}{JEditable}{Module}
${JQueryPlugin.Plugins.JEditable}{Module}
Macros are exanded recursivly. I.e. if we expand an embedded macro string then
key's value it represents will be expanded too prior to inserting it into the
original data.
Consider a sample chunk of LSC file:
CoreDir='/usr/local/www/foswiki'
DataDir='${CoreDir}/data'
WorkingDir='${CoreDir}/working'
Cache.RootDir='${WorkingDir}/cache'
It will result in the following configuration data hash:
{
CoreDir => '/usr/local/www/foswiki',
DataDir => '/usr/local/www/foswiki/data',
WorkingDir => '/usr/local/www/foswiki/working',
Cache.RootDir => '/usr/local/www/foswiki/working/cache',
}
How undefined key values are handled is defined by =undef= and =undefFail=
parameters of the =expandStr()= method.
Any non-word character can be inserted using a special macro =${<chr>}= where
=<chr>= is the symbol we need to insert:
| =${$}= | _$_ |
| =${{}= | _{_ |
| =${}}= | _}_ |
| =${\}= | _\_ |
| =${$}{Key.Path}= | _${Key.Path}_ |
Note that a word char inside the curly braces is considered a valid key name.
Thus,a string _'${k}'_ will either expand into key =k= value; or if there is no
such key then the result of the expansion will depend on =undef= and =undefFail=
parameters of the =expandStr()= method.
---++ ATTRIBUTES
=cut
use Assert;
use Encode ();
use File::Basename;
use File::Spec;
use POSIX qw(locale_h);
use Cwd qw( abs_path );
use Try::Tiny;
use Unicode::Normalize;
use FindBin;
use Foswiki qw(urlEncode urlDecode make_params);
use Foswiki::Configure::FileUtil;
use Foswiki::Exception::Config;
use Foswiki::Class -app, -types;
extends qw(Foswiki::Object);
with qw(Foswiki::Util::Localize);
# Enable to trace auto-configuration (Bootstrap)
use constant TRAUTO => 1;
# This should be the one place in Foswiki that knows the syntax of valid
# configuration item keys. Only simple scalar hash keys are supported.
#
our $ITEMREGEX =
qr/(?:\{(?:'(?:\\.|[^'])+'|"(?:\\.|[^"])+"|[A-Za-z0-9_\.]+)\})+/;
our $KeyNameREGEX = '\w[^\.=\{\}]*';
our $KeyPathREGEX =
"(?(?:\{$KeyNameREGEX\})+)|(?$KeyNameREGEX(?:\.$KeyNameREGEX)*)";
our $KeyMacroREGEX =
"\\\$(?:\{(?$KeyNameREGEX(?:\.$KeyNameREGEX)*)\}|(?(?:\{$KeyNameREGEX\})+))";
# Generic booleans, used in some older LSC's
our $TRUE = 1;
our $FALSE = 0;
# Configuration items that have been deprecated and must be mapped to
# new configuration items. The value is mapped unchanged.
my %remap = (
'{StoreImpl}' => '{Store}{Implementation}',
'{AutoAttachPubFiles}' => '{RCS}{AutoAttachPubFiles}',
'{QueryAlgorithm}' => '{Store}{QueryAlgorithm}',
'{SearchAlgorithm}' => '{Store}{SearchAlgorithm}',
'{Site}{CharSet}' => '{Store}{Encoding}',
'{RCS}{FgrepCmd}' => '{Store}{FgrepCmd}',
'{RCS}{EgrepCmd}' => '{Store}{EgrepCmd}',
'{RCS}{overrideUmask}' => '{Store}{overrideUmask}',
'{RCS}{dirPermission}' => '{Store}{dirPermission}',
'{RCS}{filePermission}' => '{Store}{filePermission}',
'{RCS}{WorkAreaDir}' => '{Store}{WorkAreaDir}'
);
$Foswiki::regex{optionNameRegex} = qr/^-(?[[:alpha:]_][[:alnum:]_]*)$/;
# Hash of parser_format => Parser::Module format. If parser module doesn't load
# the corresponding key would then exists but be undefined.
# This info is ok to share across different application instances as a module
# would be loaded only once per address space.
my %parserModules;
=begin TML
#ObjectAttributeData
---+++ ObjectAttribute data
Contains configuration hash. =%Foswiki::cfg= is an alias to this attribute.
=cut
has data => (
is => 'rw',
lazy => 1,
clearer => 1,
assert => HashRef,
builder => 'prepareData',
trigger => sub {
my $this = shift;
$this->assignGLOB;
},
);
=begin TML
---+++ ObjectAttribute files
What files we read the config from in the order of reading.
=cut
has files => (
is => 'rw',
lazy => 1,
default => sub { [] },
);
=begin TML
---+++ ObjectAttribute lscFile
Default filename for local site configuration. Can be set from the following
sources (in the order from hight priority to lower):
* corresponding constructor parameter
* environment (or PSGI env) variable FOSWIKI_CONFIG
* default constant 'LocalSite.cfg'
=cut
has lscFile => (
is => 'rwp',
lazy => 1,
builder => 'prepareLscFile',
);
=begin TML
---+++ ObjectAttribute lscHeader
Default header to be put at the beginning of LSC file.
=cut
has lscHeader => (
is => 'ro',
lazy => 1,
builder => 'prepareLscHeader',
);
=begin TML
---+++ ObjectAttribute failedConfig
Keeps the name of the failed config or spec file.
=cut
has failedConfig => ( is => 'rw', );
=begin TML
---+++ ObjectAttribute bootstrapMessage
If there is something to inform user about bootstrapping stage – the message
will be here.
=cut
has bootstrapMessage => ( is => 'rw', );
=begin TML
---+++ ObjectAttribute noExpand -> Bool
Default for =readConfig()= method =$noExpand= parameter when called by
constructor. Not used otherwise.
See [[?%QUERYSTRING%#ObjectMethodNew][constructor new()]].
=cut
has noExpand => ( is => 'rw', default => 0, );
=begin TML
---+++ ObjectAttribute noSpec -> Bool
Default for =readConfig()= method =$noSpec= parameter when called by
constructor. Not used otherwise.
See [[?%QUERYSTRING%#ObjectMethodNew][constructor new()]].
=cut
has noSpec => ( is => 'rw', default => 0, );
=begin TML
---+++ ObjectAttribute configSpec -> Bool
Default for =readConfig()= method =$configSpec= parameter when called by
constructor. Not used otherwise.
See [[?%QUERYSTRING%#ObjectMethodNew][constructor new()]].
=cut
has configSpec => ( is => 'rw', default => 0, );
=begin TML
---+++ ObjectAttribute noLocal -> Bool
Default for =readConfig()= method =$noLocal= parameter when called by
constructor. Not used otherwise.
See [[?%QUERYSTRING%#ObjectMethodNew][constructor new()]].
=cut
has noLocal => ( is => 'rw', default => 0, );
=begin TML
---+++ ObjectAttribute rootSection => $rootSectionObject
The root section object. Holds a list of first-level sections in the order,
defined by specs.
=cut
has rootSection => (
is => 'rw',
lazy => 1,
clearer => 1,
assert => InstanceOf ['Foswiki::Config::Section'],
builder => 'prepareRootSection',
);
=begin TML
---+++ ObjectAttribute specFiles
A object of =Foswiki::Config::Spec::Files= class. List of specs found.
=cut
has specFiles => (
is => 'rw',
lazy => 1,
clearer => 1,
assert => InstanceOf ['Foswiki::Config::Spec::Files'],
builder => 'prepareSpecFiles',
);
=begin TML
---+++ ObjectAttribute dataHashClass
Class name used to create tied data hash.
=cut
has dataHashClass => (
is => 'ro',
lazy => 1,
clearer => 1,
builder => 'prepareDataHashClass',
);
=begin TML
---+++ ObjectAttribute _specParsers
Cache of spec parser objects.
=cut
has _specParsers => (
is => 'ro',
lazy => 1,
clearer => 1,
builder => '_prepareSpecParsers',
);
# Hash of optionName => arity
has _keyOptArity => (
is => 'ro',
lazy => 1,
clearer => 1,
builder => '_prepareKeyOptArity',
);
# Hash of optionName => arity
has _secOptArity => (
is => 'ro',
lazy => 1,
clearer => 1,
builder => '_prepareSecOptArity',
);
# LSC file object of Foswiki::File class. A temporary storage for read/write
# methods, doesn't utilize prepare method because it is always externally
# initialized.
has _lscFileObj => (
is => 'rw',
clearer => 1,
);
# List of key/value pairs. Used by both LSC read and write methods.
has _lscRecords => (
is => 'rw',
clearer => 1,
lazy => 1,
builder => '_prepareLscRecords',
);
# Current position in _lscRecords list.
has _lscRecPos => ( is => 'rw', );
# Configuration shortcut attributes.
=begin TML
---++ METHODS
=cut
=begin TML
#ObjectMethodNew
---+++ ClassMethod new([noExpand => 0/1][, noSpec => 0/1][, configSpec => 0/1][, noLoad => 0/1])
* =noExpand= - suppress expansion of $Foswiki vars embedded in
values.
* =noSpec= - can be set when the caller knows that Foswiki.spec
has already been read.
* =configSpec= - if set, will also read Config.spec files located
using the standard methods (iff !$nospec). Slow.
* =noLocal= - if set, Load will not re-read an existing LocalSite.cfg.
this is needed when testing the bootstrap. If it rereads an existing
config, it overlays all the bootstrapped settings.
=cut
sub BUILD {
my $this = shift;
my ($params) = @_;
$this->_workOutOS;
$this->_populatePresets;
$this->_guessDefaults;
#$this->data->{isVALID} =
# $this->readConfig( $this->noExpand, $this->noSpec, $this->configSpec,
# $this->noLocal, );
try {
$this->read(
noExpand => $this->noExpand,
noDefaults => $this->noSpec,
onlyMain => !$this->configSpec,
noLocal => $this->noLocal,
);
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
warn $e->stringify;
$this->data->{isVALID} = 0;
};
$this->_setupGlobals;
return;
}
sub DEMOLISH {
my $this = shift;
$this->unAssignGLOB;
}
sub _workOutOS {
my $this = shift;
unless ( $this->data->{DetailedOS} ) {
$this->data->{DetailedOS} = $^O;
}
return if $this->data->{OS};
if ( $this->data->{DetailedOS} =~ m/darwin/i ) { # Mac OS X
$this->data->{OS} = 'UNIX';
}
elsif ( $this->data->{DetailedOS} =~ m/Win/i ) {
$this->data->{OS} = 'WINDOWS';
}
elsif ( $this->data->{DetailedOS} =~ m/vms/i ) {
$this->data->{OS} = 'VMS';
}
elsif ( $this->data->{DetailedOS} =~ m/bsdos/i ) {
$this->data->{OS} = 'UNIX';
}
elsif ( $this->data->{DetailedOS} =~ m/solaris/i ) {
$this->data->{OS} = 'UNIX';
}
elsif ( $this->data->{DetailedOS} =~ m/dos/i ) {
$this->data->{OS} = 'DOS';
}
elsif ( $this->data->{DetailedOS} =~ m/^MacOS$/i ) {
# MacOS 9 or earlier
$this->data->{OS} = 'MACINTOSH';
}
elsif ( $this->data->{DetailedOS} =~ m/os2/i ) {
$this->data->{OS} = 'OS2';
}
else {
# Erm.....
$this->data->{OS} = 'UNIX';
}
}
=begin TML
---+++ ObjectMethod localize( %init ) => $holder
This method preserves current =data= attribute on =_dataStack= and sets =data=
to the values provided in =%init=.
See also: =Foswiki::Util::Localize=
=cut
sub setLocalizableAttributes { return qw(data files); }
around localize => sub {
my $orig = shift;
my $this = shift;
my %init = @_;
return $orig->( $this, data => \%init, );
};
around doLocalize => sub {
my $orig = shift;
my $this = shift;
$orig->( $this, @_ );
$this->assignGLOB;
};
=begin TML
---+++ ObjectMethod _createSpecParser( $format ) -> $parser
Creates a new parser object for =$format=.
=cut
sub _createSpecParser {
my $this = shift;
my $format = shift;
my $fmtClass = "Foswiki::Config::Spec::Format::" . $format;
$parserModules{$format} = $fmtClass;
return $this->create( $fmtClass, cfg => $this, @_ );
}
=begin TML
---+++ ObjectMethod getSpecParser( $format ) -> $parser
Returns a parser object for specified spec =$format=. Undef is returned is such
format is now known or parser module load failed.
Format modules are defined undef =Foswiki::Config::Spec::Format::= namespace and
available in corresponding directory.
=cut
sub getSpecParser {
my $this = shift;
my $format = shift;
my $parsers = $this->_specParsers;
return if exists $parsers->{$format} && !$parsers->{$format};
my $parser;
try {
$parser = $this->_createSpecParser($format);
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
# SMELL Error messages must be somehow buffered. An API must be
# considered on Foswiki::App.
say STDERR "Cannot load parser for spec format '" . $format . "': "
. $e;
$parserModules{$format} = undef;
};
$parsers->{$format} = $parser;
return undef unless $parser;
return $parser;
}
=begin TML
---+++ ObjectMethod fetchDefaults( %params )
Fetch keys default values from specs cache. Refresh cache if necessary.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =data= | Hashref to store defaults into | =$app->cfg->data= |
| =onlyMain= | Bool, fetch only the main spec file defaults (normally – =Foswiki.spec=) | _false_ |
See =Foswiki::Config::Spec::CacheFile=, =Foswiki::Config::Spec::File=.
=cut
sub fetchDefaults {
my $this = shift;
my %params = @_;
state $called = 0;
if ($called) {
# This must never happen. But if it is – no whistle and bells of very
# informative exceptions are needed. Just die, as simple as this.
die "Circular dependecy in call to fetchDefaults!";
}
$called = 1;
my $specFiles = $this->specFiles;
my @setParams = defined $params{data} ? ( data => $params{data} ) : ();
my @flist =
$params{onlyMain} ? ( $specFiles->mainSpec ) : @{ $specFiles->list };
foreach my $specFile (@flist) {
#say STDERR "Checking cache of ", $specFile->path;
$specFile->refreshCache;
foreach my $pair ( @{ $specFile->cacheFile->entries } ) {
$this->set( @$pair, @setParams );
}
}
$called = 0;
}
=begin TML
---+++ ObjectMethod readConfig( $noExpand, $noSpec, $configSpec, $noLocal )
%RED% *Must not be used any more* %ENDCOLOR%
In normal Foswiki operations as a web server this method is called by the
=BEGIN= block of =Foswiki.pm=. However, when benchmarking/debugging it can be
replaced by custom code which sets the configuration hash. To prevent us from
overriding the custom code again, we use an "unconfigurable" key
=$cfg->data->{ConfigurationFinished}= as an indicator.
Note that this method is called by Foswiki and configure, and normally reads
=Foswiki.spec= to get defaults. Other spec files (those for extensions) are
*not* read unless the $config_spec flag is set.
The assumption is that =configure= will be run when an extension is installed,
and that will add the config values to LocalSite.cfg, so no defaults are
needed. Foswiki.spec is still read because so much of the core code doesn't
provide defaults, and it would be silly to have them in two places anyway.
=cut
sub readConfig {
my $this = shift;
my ( $noExpand, $noSpec, $configSpec, $noLocal ) = @_;
# To prevent us from overriding the custom code in test mode
return 1 if $this->data->{ConfigurationFinished};
# Assume LocalSite.cfg is valid - will be set false if errors detected.
my $validLSC = 1;
# Read Foswiki.spec and LocalSite.cfg
# (Suppress Foswiki.spec if already read)
# Old configs might not bootstrap the OS settings, so set if needed.
$this->_workOutOS unless ( $this->data->{OS} && $this->data->{DetailedOS} );
# BEGIN of new specs code
# SMELL It's here for testing only.
#$this->specsMode;
# END of new specs code
unless ($noSpec) {
push @{ $this->files }, 'Foswiki.spec';
}
if ( !$noSpec && $configSpec ) {
foreach my $dir (@INC) {
foreach my $subdir ( 'Foswiki/Plugins', 'Foswiki/Contrib' ) {
my $d;
next unless opendir( $d, "$dir/$subdir" );
my %read;
foreach
my $extension ( grep { !/^\./ && !/^Empty/ } readdir $d )
{
next if $read{$extension};
$extension =~ m/(.*)/; # untaint
my $file = "$dir/$subdir/$1/Config.spec";
next unless -e $file;
push( @{ $this->files }, $file );
$read{$extension} = 1;
}
closedir($d);
}
}
}
my $lscFile = $this->lscFile;
unless ($noLocal) {
push @{ $this->files }, $lscFile;
}
for my $file ( @{ $this->files } ) {
my $return = do $file;
unless ( defined $return && $return eq '1' ) {
my $errorMessage;
if ($@) {
$errorMessage = "Failed to parse $file: $@";
warn "couldn't parse $file: $@" if $@;
}
next if ( !DEBUG && ( $file =~ m/Config\.spec$/ ) );
if ( not defined $return ) {
unless ( $! == 2 && $file eq $lscFile ) {
# LocalSite.cfg doesn't exist, which is OK
warn "couldn't do $file: $!";
$errorMessage = "Could not do $file: $!";
}
$this->failedConfig($file);
$validLSC = 0;
}
# Pointless (says CDot), Config.spec does not need 1; at the end
#elsif ( not $return eq '1' ) {
# print STDERR
# "Running file $file returned unexpected results: $return \n";
#}
if ($errorMessage) {
# SMELL die has to be replaced with an exception.
die <data->{StoreImpl} ) {
$this->data->{Store}{Implementation} =
'Foswiki::Store::' . $this->data->{StoreImpl};
delete $this->data->{StoreImpl};
}
foreach my $el ( keys %remap ) {
# Only remap if the old key extsts, and the new key does NOT exist
if ( ( eval("exists \$this->data->{$el}") ) ) {
eval( <data->{$remap{$el}}=\$this->data->{$el} unless ( exists \$this->data->{$remap{$el}} );
delete \$this->data->{$el};
CODE
print STDERR "REMAP failed $@" if ($@);
}
}
# Expand references to $this->data vars embedded in the values of
# other $this->data vars.
$this->expandValue( $this->data ) unless $noExpand;
$this->data->{ConfigurationFinished} = 1;
if ( $^O eq 'MSWin32' ) {
#force paths to use '/'
$this->data->{PubDir} =~ s|\\|/|g;
$this->data->{DataDir} =~ s|\\|/|g;
$this->data->{ToolsDir} =~ s|\\|/|g;
$this->data->{ScriptDir} =~ s|\\|/|g;
$this->data->{TemplateDir} =~ s|\\|/|g;
$this->data->{LocalesDir} =~ s|\\|/|g;
$this->data->{WorkingDir} =~ s|\\|/|g;
}
# Add explicit {Site}{CharSet} for older extensions. Default to utf-8.
# Explanation is in http://foswiki.org/Tasks/Item13435
$this->data->{Site}{CharSet} = 'utf-8';
# Explicit return true if we've completed the load
return $validLSC;
}
sub _expandAll {
my $this = shift;
my %params = @_[ 1 .. $#_ ];
if ( ref( $_[0] ) ) {
if ( ref( $_[0] ) eq 'HASH' ) {
foreach my $key ( keys %{ $_[0] } ) {
$this->_expandAll( $_[0]->{$key}, %params, );
}
}
elsif ( ref( $_[0] ) eq 'ARRAY' ) {
foreach my $val ( @{ $_[0] } ) {
$this->_expandAll( $val, %params, );
}
}
# Ignore any other ref types.
}
elsif ( defined $_[0] && $_[0] =~ /$KeyMacroREGEX/ ) {
$_[0] = $this->expandStr( %params, str => $_[0], );
}
}
=begin TML
---+++ ObjectMethod expandAll( %params )
Expands macros in all keys of a configuration data hash.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =undef= | What to replace an undefined key value with | _"undef"_ |
| =undefFail= | Bool, wether to fail if an undefined key value encountered | _false_ |
| =data= | Configuration data hash | =$app->cfg->data= |
=%params= is transparently sent over to =expandStr()= method except for =str=
key.
=cut
sub expandAll {
my $this = shift;
my %params = @_;
$params{undef} = 'undef' unless exists $params{undef};
$params{undefFail} = 0 unless exists $params{undefFail};
my $data = defined $params{data} ? $params{data} : $this->data;
$this->Throw( 'Foswiki::Exception::Fatal',
"data key must be a hashref in call to expandAll() method",
) unless ref($data) eq 'HASH';
$this->_expandAll( $data, %params );
}
=begin TML
---+++ ObjectMethod read( %params ) -> $data
This method replaces the legacy =readConfig()=. Depending on what is defined
by the parameters it:
* Fetches default values from spec files
* Reads the local site configuration file
* Expands macros in configuration keys
* Sets isVALID key to _true_ if LSC has been read successfully. It would remain _false_ whether LSC read failed or =noLocal= param is _true_.
* Normalizes =!PubDir, !DataDir, !ToolsDir, !ScriptDir, !TemplateDir, !LocalesDir,= and =WorkingDir= to correspond the OS used.
* Sets =ConfigurationFinished= configuration key to true
The method returns filled in configuration data hash.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =data= | Configuration data hash to fill | =$app->cfg->data= |
| =noDefaults= | Don't fetch spec default values | _false_ |
| =onlyMain= | Only fetch defaults of the main spec file | _false_ |
| =noLocal= | Don't read local site configuration file | _false_ |
| =noExpand= | Don't expand macros in the configuration hash | _false_ |
| =lscFile= | Name of the local configuration file | see =readLSCStart()= method |
| =_stage= | The initialization stage as defined by =Foswiki::App= =stage= context. | =$app->context->{appStage}= |
%X% *NOTE:* The =_stage= param is not used by the core but could be taken into
account by an extension overriding a pluggable method. It is also passed over to
the =expandAll()= method.
=cut
sub read {
my $this = shift;
my %params = @_;
my $data;
my ( @dataParam, @lscFileParam );
if ( $params{data} ) {
$data = $params{data};
push @dataParam, data => $data;
}
else {
$data = $this->data;
}
return if $data->{ConfigurationFinished};
my $stage = $params{_stage} // $this->app->context->{appStage};
# Similar to the old readConfig() noSpec set to true.
my $noDefaults = exists $params{noDefaults} ? $params{noDefaults} : 0;
# Similar to the old readConfig() when both noSpec and configSpec are false.
# Reads only defaults from the main spec file (Foswiki.spec for the moment).
# Taken into account only when noDefaults is false.
my $onlyMain = exists $params{onlyMain} ? $params{onlyMain} : 0;
# Don't read LSC
my $noLocal = exists $params{noLocal} ? $params{noLocal} : 0;
# Don't expand macros in the final data hash.
my $noExpand = exists $params{noExpand} ? $params{noExpand} : 0;
unless ($noDefaults) {
$this->fetchDefaults(
onlyMain => $onlyMain,
data => $data,
_stage => $stage,
);
}
$data->{isVALID} = 0;
unless ($noLocal) {
push @lscFileParam, lscFile => $params{lscFile}
if defined $params{lscFile};
$data->{isVALID} =
$this->readLSC( @dataParam, @lscFileParam, _stage => $stage, );
}
unless ($noExpand) {
$this->expandAll( @dataParam, _stage => $stage, );
}
# Make all path conformant to the OS we running under.
foreach my $dirKey (
qw(PubDir DataDir ToolsDir ScriptDir TemplateDir LocalesDir WorkingDir))
{
next unless defined $data->{$dirKey};
my ( $v, $d, $f ) = File::Spec->splitpath( $data->{$dirKey} );
my @d = File::Spec->splitdir($d);
$data->{$dirKey} =
File::Spec->canonpath(
File::Spec->catpath( $v, File::Spec->catdir(@d), $f ) );
}
# Add explicit {Site}{CharSet} for older extensions. Default to utf-8.
# Explanation is in http://foswiki.org/Tasks/Item13435
$data->{Site}{CharSet} = 'utf-8' unless defined $data->{Site}{CharSet};
$data->{ConfigurationFinished} = 1;
return $data;
}
=begin TML
---+++ ObjectMethod readLSCStart( %params ) -> $success
This method prepares reading from LSC file. Only to be called by =readLSC()=
method.
As a matter of fact this method reads the entire LSC file in %WIKITOOLNAME%
format into memory and prepares data for =readLSCRecord()= method.
Returns _false_ if failed.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =lscFile= | Full pathname of LSC file | =$this->lscFile= with _.new_ suffix appended in %WIKITOOLNAME% library directory |
%X% *NOTE:* The _.new_ suffix is a temporary solution to avoid conflicts with
legacy code. Will be removed in release.
=cut
sub readLSCStart {
my $this = shift;
my %params = @_;
$this->_clear_lscRecords;
$this->_clear_lscFileObj;
my $lscFile = $params{lscFile}
// File::Spec->catfile( Foswiki::guessLibDir, $this->lscFile . ".new" );
unless ( -r $lscFile ) {
warn "$lscFile is not readable";
return 0;
}
unless ( -f $lscFile ) {
warn "$lscFile is not a plain file";
return 0;
}
my $cfFile = $this->create(
'Foswiki::File',
path => $lscFile,
# Prevent occasional overwriting of the LSC would a bug sneak into the
# code.
autoWrite => 0,
);
my $lnum = 0;
my ( $hereDoc, $hereKey, $hereVal, @lscRecords );
foreach my $line ( split /\n/, $cfFile->content ) {
my ( $keyPath, $keyVal );
$lnum++;
chomp $line;
next if !$hereDoc && $line =~ /^\s*(?:#|\z)/;
my $doEval = 0;
if ($hereDoc) {
if ( $line =~ /^$hereDoc\s*$/ ) {
$keyVal = $hereVal;
$keyPath = $hereKey;
$doEval = 1;
undef $hereDoc;
undef $hereKey;
undef $hereVal;
}
else {
$hereVal .= "\n$line";
}
}
elsif ( $line =~ /^\s*(?$KeyPathREGEX)\s*=\s*(?.+)?$/ )
{
( $keyPath, $keyVal ) = @+{qw(keyPath keyVal)};
if ( defined($keyVal) && length($keyVal) ) {
if ( $keyVal =~ s/^\<\/ ) {
$hereDoc = $keyVal;
$hereKey = $keyPath;
# Reset to avoid preliminary setting of the key.
undef $keyPath;
if ( length($hereDoc) == 0 ) {
warn
"Bad here-document at $lscFile($lnum): must have signature";
return 0;
}
}
else {
$doEval = 1;
}
}
else {
$keyVal = undef;
}
}
else {
say STDERR "^\\s*(?$KeyPathREGEX)\\s*=\\s*(?.+)\$";
warn
"Failed to read $lscFile($lnum): unrecorgnized format of line '$line'";
return 0;
}
if ($keyPath) {
if ($doEval) {
my $interp = eval $keyVal;
if ($@) {
warn "Syntax error in $lscFile($lnum), value '$keyVal': $@";
return 0;
}
$keyVal = $interp;
}
push @lscRecords, [ $keyPath, $keyVal ];
}
}
if ($hereDoc) {
warn "Unclosed here-doc labeled $hereDoc in $lscFile";
return 0;
}
$this->_lscRecords( \@lscRecords );
$this->_lscRecPos(0);
return 1;
}
=begin TML
---+++ ObjectMethod readLSCRecord( %params ) -> ($success, $keyPath, $keyVal )
Fetches next record from LSC file. Only to be called by =readLSC()= method.
Returns a list of =($success, $keyPath, $keyVal)=. If =$success= is _false_ then
record reading failed. If =$success= is _true_ but =$keyPath= is _undef_ then it
was an attempt to read past last record.
=cut
sub readLSCRecord {
my $this = shift;
my %params = @_;
my $curPos = $this->_lscRecPos;
$this->_lscRecPos( $curPos + 1 );
return ( 1, undef, undef ) if $curPos >= scalar( @{ $this->_lscRecords } );
return ( 1, @{ $this->_lscRecords->[$curPos] } );
}
=begin TML
---+++ ObjectMethod readLSCFinalize( %params ) -> $success
Finalizes read cycle. Only to be called by =readLSC()= method.
Returns _true_ if everything is ok.
=cut
sub readLSCFinalize {
my $this = shift;
$this->_clear_lscFileObj;
$this->_clear_lscRecords;
$this->_lscRecPos(0);
return 1;
}
=begin TML
#ObjectMethodReadLSC
---+++ ObjectMethod readLSC( %params ) -> $success
Reads local site configuration.
Returns true if everything is ok.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =data= | Configuration data hash reference to read LSC into. | $app->cfg->data |
Read the Implementation Notes section on how parameters are handled.
---++++!! Implementation Notes
This method does a very simple thing:
1. Calls =readLSCStart()= method to initiate the reading
1. Fetches config records consisting of key path and value one by one with =readLSCRecord()= and stores it in the hash defined by =data= param using =set()= method.
1. Calls =readLSCFinalize()=.
Although the only parameter used by this method is =data= a user is allowed to
supply more parameters if necessary. They all will be passed over to the
=readLSCStart()= method as is with no modification except for =data= which might
be altered for a purpose and is always appended to the end of parameters so that
it would override any user supplied value for it. For the =readLSCRecord()= and
=readLSCFinalize()= method =data= is the only parameter passed.
Such approach to handling paramers makes sense if one remembers that all four =readLSC*=
methods are pluggables. So, any extra parameter not handled by the core could be
useful for a extension.
=cut
sub readLSC {
my $this = shift;
my %params = @_;
# Avoid dying of warnings here – at least until bufferized error reporting is
# in place. Otherwise we wouldn't even be able to test bootstrap.
local $SIG{__WARN__};
my $cfgData = $params{data} // $this->data;
return 0 unless $this->readLSCStart( @_, data => $cfgData, );
my ( $rc, $keyPath, $keyVal );
do {
( $rc, $keyPath, $keyVal ) = $this->readLSCRecord( data => $cfgData );
if ( $rc && defined $keyPath ) {
$this->set( $keyPath, $keyVal, data => $cfgData );
}
} while ( $rc && defined $keyPath );
# Avoid possible optimization, call and get finalize return value
# explicitly.
my $finalRc = $this->readLSCFinalize( data => $cfgData );
return ( $rc && $finalRc );
}
=begin TML
---+++ ObjectMethod _genLSCHereDoc( $val ) -> $hereDocStr
Generates a valid heredoc string which would incapsulate =$val=.
=cut
sub _genLSCHereDoc {
my $this = shift;
my $val = shift;
my $endMark;
my @a = ( 'A' .. 'Z' );
# End-mark must not be contained in the value string. So, repeat generation
# until we get a unique one.
do {
$endMark = 'CF_';
for ( 1 .. 4 ) {
$endMark .= $a[ int( rand( scalar(@a) ) ) ];
}
} while ( $val =~ /$endMark/ );
return "<<$endMark\n$val\n$endMark";
}
=begin TML
---+++ ObjectMethod writeLSCStart( %params )
Initiates LSC writing.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =lscFile= | LSC file name | _LocalSite.cfg.new_ in Foswiki lib directory |
=cut
sub writeLSCStart {
my $this = shift;
my %params = @_;
my $lscFile = $params{lscFile}
// File::Spec->catfile( Foswiki::guessLibDir, $this->lscFile . ".new" );
$this->_lscFileObj(
$this->create(
'Foswiki::File',
path => $lscFile,
autoWrite => 1,
autoCreate => 1,
content => '',
)
);
}
=begin TML
---+++ ObjectMethod writeLSCRecord( %params )
This method is for low-level writing of a single key/value pair into LSC.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =key= | Full configuration key path | |
| =value= | Key value | |
| =comment= | Comment text to be written before configuration record. Must be a clear text with no '#' prepended. | |
=cut
sub writeLSCRecord {
my $this = shift;
my %params = @_;
# Support for extensions. If an extension doesn't want a record to be stored
# in a standard location it could simply delete 'key' item from the params.
return unless defined $params{key};
my $comment =
defined $params{comment}
? join( "\n", map { "# $_" } split /\n/, $params{comment} )
: undef;
push @{ $this->_lscRecords },
{
key => $params{key},
value => $params{value},
comment => $comment,
};
}
=begin TML
---+++ ObjectMethod writeLSCFinalize( %params )
Called when all LSC records are stored.
---++++!! Parameters
No parameters are used.
=cut
sub writeLSCFinalize {
my $this = shift;
my %params = @_;
my $_lscFileObj = $this->_lscFileObj;
$_lscFileObj->autoWrite(0);
$_lscFileObj->content( $_lscFileObj->content . "\n" );
foreach my $rec ( @{ $this->_lscRecords } ) {
my $key = $rec->{key};
my $val = $rec->{value} // '';
my $comment = $rec->{comment};
if ( $val =~ /\n/ ) {
# Special notion for multiline values.
$val = $this->_genLSCHereDoc($val);
}
$_lscFileObj->content( $_lscFileObj->content
. ( $comment ? "$comment\n" : '' )
. "$key=$val\n" );
}
$_lscFileObj->autoWrite(1);
$this->_clear_lscFileObj;
$this->_clear_lscRecords;
}
=begin TML
---+++ ObjectMethod writeLSC( %params )
Writes configuration data into LSC file.
---++++!! Parameters
See also parameters of =writeLSCStart()= method.
| *Param* | *Description* | *Default* |
| =data= | Configuration data hash to be written into LSC file. | =$app->cfg->data= |
---++++!! Implementation notes
This method does the following:
1. Converts the =data= hash into specs mode unless it's a hash tied to =$app->cfg->dataHashClass= already.
1. Calls =writeLSCStart()=.
1. Gets all leaf nodes from the data hash and writes them one-by-one with =writeLSCRecord()= method.
1. Calls =writeLSCFinalize()=.
Because of the convertion into specs mode (see
=[[?%QUERYSTRING%#ObjectMethodSpecsMode][specsMode()]]= method) the data hash
may eventually contain more keys then there was initially. If this is
undesirable behavior then the =data= hash must be already in specs mode when
passed into the method.
Before writing a key into LSC file it is checked against all known keys defined
in specs. If the key is not found then it is prepended with a warning comment.
All user defined parameters are handled similar to the
=[[?%QUERYSTRING%#ObjectMethodReadLSC][readLSC()]]= method.
=cut
sub writeLSC {
my $this = shift;
my %params = @_;
my $cfgData = $params{data} // $this->data;
my $root = tied %$cfgData;
$cfgData = $this->specsMode( setAttr => 0, data => $cfgData, )
unless $root && $root->isa( $this->dataHashClass );
$root = tied %$cfgData;
$this->writeLSCStart( @_, data => $cfgData );
my @cfgKeys = sort map { $_->fullName } $root->getLeafNodes;
my %specKeys;
foreach my $sf ( @{ $this->specFiles->list } ) {
$specKeys{ $_->[0] } = 1 foreach @{ $sf->cacheFile->entries };
}
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Deepcopy = 1;
my $comment = $this->lscHeader . "\n";
foreach my $cfKey (@cfgKeys) {
unless ( $specKeys{$cfKey} ) {
$comment .= "Key $cfKey is not defined in a spec file";
#say STDERR "Key $cfKey is not defined in specs";
}
my @keys = $this->parseKeys($cfKey);
my $leafKey = pop @keys;
my $keyObject = $root->getKeyObject(@keys);
my $val;
if ( defined $keyObject ) {
$val = $keyObject->nodes->{$leafKey}->getValue;
}
if ( defined $val ) {
$val = Data::Dumper->Dump( [$val] );
chomp $val;
}
$this->writeLSCRecord(
key => $cfKey,
value => $val,
comment => $comment,
data => $cfgData,
);
undef $comment;
}
$this->writeLSCFinalize( data => $cfgData, );
return;
}
=begin TML
---+++ ObjectMethod expandValue($datum [, $mode])
Expands references to Foswiki configuration items which occur in the
values configuration items contained within the datum, which may be a
hash or array reference, or a scalar value. The replacement is done in-place.
$mode - How to handle undefined values:
* false: 'undef' (string) is returned when an undefined value is
encountered.
* 1 : return undef if any undefined value is encountered.
* 2 : return '' for any undefined value (including embedded)
* 3 : die if an undefined value is encountered.
=cut
sub expandValue {
my $this = shift;
my $undef;
$this->_expandValue( $_[0], ( $_[1] || 0 ), $undef );
$_[0] = undef if ($undef);
}
# $_[0] - value being expanded
# $_[1] - $mode
# $_[2] - $undef (return)
sub _expandValue {
my $this = shift;
if ( ref( $_[0] ) eq 'HASH' ) {
$this->expandValue( $_, $_[1] ) foreach ( values %{ $_[0] } );
}
elsif ( ref( $_[0] ) eq 'ARRAY' ) {
$this->expandValue( $_, $_[1] ) foreach ( @{ $_[0] } );
# Can't do this, because Windows uses an object (Regexp) for regular
# expressions.
# } elsif (ref($_[0])) {
# die("Can't handle a ".ref($_[0]));
}
else {
1 while ( defined( $_[0] )
&& $_[0] =~
s/(\$Foswiki::cfg$ITEMREGEX)/_handleExpand($this, $1, @_[1,2])/ges
);
}
}
# Used to expand the $Foswiki::cfg variable in the expand* routines.
# $_[0] - $item
# $_[1] - $mode
# $_[2] - $undef
sub _handleExpand {
my $this = shift;
my $val = eval( $_[0] );
$this->Throw( 'Foswiki::Exception::Fatal', "Error expanding $_[0]: $@" )
if ($@);
return $val if ( defined $val );
return 'undef' if ( !$_[1] );
return '' if ( $_[1] == 2 );
die "Undefined value in expanded string $_[0]\n" if ( $_[1] == 3 );
$_[2] = 1;
return '';
}
sub _doExpandStr {
my $this = shift;
my $str = shift;
my %params = @_;
local $params{__expLevel} = $params{__expLevel} + 1;
# Reset pos
$str =~ /^/gs;
my $expStr = "";
my @dataParam = $params{data} ? ( data => $params{data} ) : ();
# Construct like ${} is being expanded into just . This is how
# escaping is implemented. Note that this works for non-word chars only. Any
# ${.*} is commonly considered a macro.
while ($str =~ /(?.*?)\$\{(?\W)\}/gsc
|| $str =~ /(?.*?)$KeyMacroREGEX/gsc )
{
$expStr .= $+{txt};
if ( defined $+{chr} ) {
# Expand \ escaping
$expStr .= $+{chr};
}
else {
my $key = $+{key};
my $keyVal = $this->get( $key, @dataParam );
if ( defined $keyVal ) {
$expStr .= $this->_expandStr( $keyVal, %params );
}
else {
if ( $params{undefFail} ) {
$this->Throw( 'Foswiki::Exception::Fatal',
"Failed to expand string '"
. $str
. "': key "
. $key
. " value is undefined" );
}
$this->Throw( 'Foswiki::Exception::_expandStr::UndefVal', $key )
unless defined $params{undef};
$expStr .= $params{undef};
}
}
}
$str =~ /\G(?.*)$/;
$expStr .= $+{txt};
return $expStr;
}
sub _expandStr {
my $this = shift;
my $str = shift;
my %params = @_;
my $expStr;
$params{__expLevel} //= 0;
try {
$expStr = $this->_doExpandStr( $str, %params );
}
catch {
my $e = Foswiki::Exception::Fatal->transmute( $_, 0 );
if ( !$params{__expLevel}
&& $e->isa('Foswiki::Exception::_expandStr::UndefVal') )
{
$expStr = undef;
}
else {
$e->rethrow;
}
};
return $expStr;
}
=begin TML
---+++ ObjectMethod expandStr( %params ) -> expanded data
Expands a string possibly containing config value macro ${Key}
Returns an array of expanded strings if called in a list context. In a scalar
context either returns a scalar for a single expanded string; or an array ref if
multiple strings were expanded.
---++++!! Parameters
| *Param* | *Description* | *Default* |
| =data= | Configuration data hash. | =$app->cfg->data= |
| =str= | Data to be expanded. Could be a scalar or an array ref. | |
| =key= | Full path of a configuration key to expand or an array ref of keys. | |
| =undef= | What an undefined value must be replaced with. | _undef_ |
| =undefFail= | Throw a fatal exception if undefined value has been encountered | _false_ |
---++++!! Implementation details
When a configuration key with undefined value encountered during the expansion
process then method behaviour depends on =undefFail= and =undef= parameters.
With =undefFail= set to _true_ a fatal exception will be generated. Otherwise,
if =undef= is not specified or has undefined value then the whole expansion will
result in an undefined value. But if =undef= containts a value it will be used
as if it's the value of the undefined configuration key.
For example, for the following config:
UndefKey=
AKey='Here we include ${UndefKey}...'
With =undefFail= being _false_ and =undef= set to '*undef*' the resulting value will be:
Here we include *undef*...
=cut
sub expandStr {
my $this = shift;
my %params = @_;
my ( @strs, @estrs );
# $isList is true if a list is requested; i.e. any of str or key are passed
# in with an array ref.
my $isList;
if ( $params{str} ) {
if ( my $rt = ref( $params{str} ) ) {
$this->Throw( 'Foswiki::Exception::Fatal',
"expandStr method's str parameter cannot be " . $rt . " ref" )
unless $rt eq 'ARRAY';
push @strs, @{ $params{str} };
$isList = 1;
}
else {
push @strs, $params{str};
}
delete $params{str};
}
if ( $params{key} ) {
if ( my $rt = ref( $params{key} ) ) {
$this->Throw( 'Foswiki::Exception::Fatal',
"expandStr method's key parameter cannot be " . $rt . " ref" )
unless $rt eq 'ARRAY';
push @strs, $this->get($_) foreach @{ $params{key} };
$isList = 1;
}
else {
push @strs, $this->get( $params{key} );
}
delete $params{key};
}
push @estrs, $this->_expandStr( $_, %params ) foreach @strs;
return (
wantarray ? @estrs : ( @estrs > 1 || $isList ? [@estrs] : $estrs[0] ) );
}
=begin TML
---+++ ObjectMethod bootstrapSystemSettings()
This method tries to determine mandatory configuration defaults to operate
when no LocalSite.cfg is found.
=cut
sub bootstrapSystemSettings {
my $this = shift;
# Strip off any occasional configuration data which might be a result of
# previously failed readConfig.
$this->clear_data;
# Restore system-default state.
$this->_workOutOS;
$this->_populatePresets;
$this->_guessDefaults;
my $env = $this->app->env;
my $engine = $this->app->engine;
print STDERR "AUTOCONFIG: Bootstrap Phase 1: " . Data::Dumper::Dumper($env)
if (TRAUTO);
# Try to create $Foswiki::cfg in a minimal configuration,
# using paths and URLs relative to this request. If URL
# rewriting is happening in the web server this is likely
# to go down in flames, but it gives us the best chance of
# recovering. We need to guess values for all the vars that
# would trigger "undefined" errors
my $bin;
my $script = '';
if ( defined $ENV{FOSWIKI_SCRIPTS} ) {
$bin = $ENV{FOSWIKI_SCRIPTS};
}
else {
eval('require FindBin');
$this->Throw( 'Foswiki::Exception::Fatal',
"Could not load FindBin to support configuration recovery: $@" )
if $@;
FindBin::again(); # in case we are under mod_perl or similar
$FindBin::Bin =~ m/^(.*)$/;
$bin = $1;
$FindBin::Script =~ m/^(.*)$/;
$script = $1;
}
# Can't use Foswiki::decode_utf8 - this is too early in initialization
# SMELL TODO The above must not be true anymore. Yet, why not use
# Encode::decode_utf8?
print STDERR "AUTOCONFIG: Found Bin dir: "
. $bin
. ", Script name: $script using FindBin\n"
if (TRAUTO);
$this->data->{ScriptSuffix} = ( fileparse( $script, qr/\.[^.]*/ ) )[2];
$this->data->{ScriptSuffix} = ''
if ( $engine->isa('Foswiki::Engine::FastCGI')
|| $engine->isa('Foswiki::Engine::PSGI') );
print STDERR "AUTOCONFIG: Found SCRIPT SUFFIX "
. $this->data->{ScriptSuffix} . "\n"
if ( TRAUTO && $this->data->{ScriptSuffix} );
my %rel_to_root = (
DataDir => { dir => 'data', required => 0 },
LocalesDir => { dir => 'locale', required => 0 },
PubDir => { dir => 'pub', required => 0 },
ToolsDir => { dir => 'tools', required => 0 },
WorkingDir => {
dir => 'working',
required => 1,
validate_file => 'README'
},
TemplateDir => {
dir => 'templates',
required => 1,
validate_file => 'foswiki.tmpl'
},
ScriptDir => {
dir => 'bin',
required => 1,
validate_file => 'setlib.cfg'
}
);
# Note that we don't resolve x/../y to y, as this might
# confuse soft links
my $root = Foswiki::guessHomeDir;
$root =~ s{\\}{/}g;
my $fatal = '';
my $warn = '';
while ( my ( $key, $def ) = each %rel_to_root ) {
$this->data->{$key} = File::Spec->rel2abs( $def->{dir}, $root );
$this->data->{$key} = abs_path( $this->data->{$key} );
( $this->data->{$key} ) = $this->data->{$key} =~ m/^(.*)$/; # untaint
# Need to decode utf8 back to perl characters. The file path operations
# all worked with bytes, but Foswiki needs characters.
$this->data->{$key} = NFC( Encode::decode_utf8( $this->data->{$key} ) );
print STDERR "AUTOCONFIG: $key = "
. Encode::encode_utf8( $this->data->{$key} ) . "\n"
if (TRAUTO);
if ( -d $this->data->{$key} ) {
if ( $def->{validate_file}
&& !-e $this->data->{$key} . "/$def->{validate_file}" )
{
$fatal .=
"\n{$key} (guessed "
. $this->data->{$key} . ") "
. $this->data->{$key}
. "/$def->{validate_file} not found";
}
}
elsif ( $def->{required} ) {
$fatal .= "\n{$key} (guessed " . $this->data->{$key} . ")";
}
else {
$warn .=
"\n * Note: {$key} could not be guessed. Set it manually!";
}
}
# Bootstrap the Site Locale and CharSet
$this->_bootstrapSiteSettings();
# Bootstrap the store related settings.
$this->_bootstrapStoreSettings();
if ($fatal) {
my $lscFile = $this->lscFile;
$this->Throw( 'Foswiki::Exception::Fatal', <readConfig( 0, 0, 1, 1 );
print STDERR "AUTOCONFIG: Detected OS "
. $this->data->{OS}
. ": DetailedOS: "
. $this->data->{DetailedOS} . " \n"
if (TRAUTO);
$this->_setupGlobals;
$this->data->{isVALID} = 1;
$this->setBootstrap;
# Note: message is not I18N'd because there is no point; there
# is no localisation in a default cfg derived from Foswiki.spec
my $system_message = <bootstrapMessage( $system_message // '' );
}
=begin TML
---+++ ObjectMethod bootstrapWebSettings($script)
Called by bootstrapConfig. This handles the web environment specific settings only:
* ={DefaultUrlHost}=
* ={ScriptUrlPath}=
* ={ScriptUrlPaths}{view}=
* ={PubUrlPath}=
=cut
sub bootstrapWebSettings {
my $this = shift;
my $script = shift;
my $app = $this->app;
my $env = $app->env;
my $engine = $app->engine;
my $cfgData = $this->data;
print STDERR "AUTOCONFIG: Bootstrap Phase 2: "
. Data::Dumper::Dumper( \%ENV )
if (TRAUTO);
# Cannot bootstrap the web side from CLI environments
if ( !$engine->HTTPCompliant ) {
$cfgData->{DefaultUrlHost} = 'http://localhost';
$cfgData->{ScriptUrlPath} = '/bin';
$cfgData->{PubUrlPath} = '/pub';
print STDERR
"AUTOCONFIG: Bootstrap Phase 2 bypassed! n/a in the CLI Environment\n"
if (TRAUTO);
return 'Phase 2 boostrap bypassed - n/a in CLI environment\n';
}
$cfgData->{Engine} //= ref($engine);
my $protocol = $engine->secure ? 'https' : 'http';
# Figure out the DefaultUrlHost
if ( $env->{HTTP_HOST} ) {
$cfgData->{DefaultUrlHost} = "$protocol://" . $env->{HTTP_HOST};
print STDERR "AUTOCONFIG: Set DefaultUrlHost "
. $cfgData->{DefaultUrlHost}
. " from HTTP_HOST "
. $env->{HTTP_HOST} . " \n"
if (TRAUTO);
}
elsif ( $env->{SERVER_NAME} ) {
$cfgData->{DefaultUrlHost} = "$protocol://" . $env->{SERVER_NAME};
print STDERR "AUTOCONFIG: Set DefaultUrlHost "
. $cfgData->{DefaultUrlHost}
. " from SERVER_NAME "
. $env->{SERVER_NAME} . " \n"
if (TRAUTO);
}
elsif ( $env->{SCRIPT_URI} ) {
( $cfgData->{DefaultUrlHost} ) =
$env->{SCRIPT_URI} =~ m#^(https?://[^/]+)/#;
print STDERR "AUTOCONFIG: Set DefaultUrlHost "
. $cfgData->{DefaultUrlHost}
. " from SCRIPT_URI "
. $env->{SCRIPT_URI} . " \n"
if (TRAUTO);
}
else {
# OK, so this is barfilicious. Think of something better.
$cfgData->{DefaultUrlHost} = "$protocol://localhost";
say STDERR "AUTOCONFIG: barfilicious: Set DefaultUrlHost "
. $cfgData->{DefaultUrlHost}
if (TRAUTO);
}
# Examine the CGI path. The 'view' script it typically removed from the
# URL when using "Short URLs. If this BEGIN block is being run by
# 'view', then $this->data->{ScriptUrlPaths}{view} will be correctly
# bootstrapped. If run for any other script, it will be set to a
# reasonable though probably incorrect default.
#
# In order to recover the correct view path when the script is 'configure',
# the ConfigurePlugin stashes the path to the view script into a session variable.
# and then recovers it. When the jsonrpc script is called to save the configuration
# it then has the VIEWPATH parameter available. If "view" was never called during
# configuration, then it will not be set correctly.
my $path_info = $engine->path_info
|| ''; #SMELL Sometimes PATH_INFO appears to be undefined.
my $request_uri = $engine->request_uri;
say STDERR "AUTOCONFIG: REQUEST_URI is " . ( $request_uri || '(undef)' )
if (TRAUTO);
say STDERR "AUTOCONFIG: SCRIPT_URI is "
. ( $env->{SCRIPT_URI} || '(undef)' )
if (TRAUTO);
say STDERR "AUTOCONFIG: PATH_INFO is $path_info" if (TRAUTO);
say STDERR "AUTOCONFIG: ENGINE is " . $cfgData->{Engine}
if (TRAUTO);
# This code tries to break the url up into