# 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/^-(?