InifileConfiguration.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\config\impl;
12 
23 
24 /**
25  * InifileConfiguration reads the application configuration from ini files.
26  * @note This class only supports ini files with sections.
27  *
28  * @author ingo herwig <ingo@wemove.com>
29  */
31 
32  private $configArray = []; // an assoziate array that holds sections with keys with values
33  private $comments = []; // an assoziate array that holds the comments/blank lines in the file
34  // (each comment is attached to the following section/key)
35  // the key ';' holds the comments at the end of the file
36  private $lookupTable = []; // an assoziate array that has lowercased section or section:key
37  // keys and [section, key] values for fast lookup
38 
39  private $isModified = false;
40  private $addedFiles = []; // files added to the configuration
41  private $containedFiles = []; // all included files (also by config include)
42 
43  private $configPath = null;
44  private $configExtension = 'ini';
45  private $cachePath = null;
46 
47  private $fileUtil = null;
48 
49  private static $logger = null;
50 
51  /**
52  * Constructor.
53  * @param $configPath The path, either absolute or relative to the executed script
54  * @param $cachePath The cache path, either absolute or relative to the executed script (optional)
55  */
56  public function __construct($configPath, $cachePath=null) {
57  $this->configPath = $configPath;
58  $this->cachePath = $cachePath;
59  $this->fileUtil = new FileUtil();
60  if (self::$logger == null) {
61  self::$logger = LogManager::getLogger(__CLASS__);
62  }
63  }
64 
65  public function __toString() {
66  $configArray = $this->configArray;
67  ksort($configArray);
68  $result = '';
69  foreach($configArray as $section => $config) {
70  $result .= PHP_EOL.'['.$section.']'.PHP_EOL;
71  ksort($config);
72  foreach ($config as $key => $value) {
73  $result .= $key.' = '.json_encode($value).PHP_EOL;
74  }
75  }
76  return $result;
77  }
78 
79  /**
80  * Get the file system path to the configuration files.
81  * @return The path, either absolute or relative to the executed script
82  */
83  public function getConfigPath() {
84  return $this->configPath;
85  }
86 
87  /**
88  * Configuration interface
89  */
90 
91  /**
92  * @see Configuration::getConfigurations()
93  */
94  public function getConfigurations() {
95  return $this->fileUtil->getFiles($this->configPath, '/\.'.$this->configExtension.'$/', true);
96  }
97 
98  /**
99  * @see Configuration::addConfiguration()
100  * Name is the ini file to be parsed (relative to configPath)
101  * @note ini files referenced in section 'config' key 'include' are parsed afterwards
102  */
103  public function addConfiguration($name, $processValues=true) {
104  if (self::$logger->isDebugEnabled()) {
105  self::$logger->debug("Add configuration: ".$name);
106  }
107  $filename = $this->configPath.$name;
108 
109  // do nothing, if the requested file was the last parsed file
110  // we don't only check if it's parsed, because order matters
111  $numParsedFiles = sizeof($this->addedFiles);
112  $lastFile = $numParsedFiles > 0 ? $this->addedFiles[$numParsedFiles-1] : '';
113  if (self::$logger->isDebugEnabled()) {
114  self::$logger->debug("Parsed files: ".$numParsedFiles.", last file: ".$lastFile);
115  foreach($this->addedFiles as $addedFile) {
116  self::$logger->debug("File date ".$addedFile.": ".@filemtime($addedFile));
117  }
118  $cachedFile = $this->getSerializeFilename($this->addedFiles);
119  self::$logger->debug("Cache file date ".$cachedFile.": ".@filemtime($cachedFile));
120  }
121  if ($numParsedFiles > 0 && $lastFile == $filename &&
122  !$this->checkFileDate($this->addedFiles, $this->getSerializeFilename($this->addedFiles))) {
123  if (self::$logger->isDebugEnabled()) {
124  self::$logger->debug("Skipping");
125  }
126  return;
127  }
128 
129  if (!file_exists($filename)) {
130  throw new ConfigurationException('Configuration file '.$filename.' not found!');
131  }
132 
133  if (self::$logger->isDebugEnabled()) {
134  self::$logger->debug("Adding...");
135  }
136  // try to unserialize an already parsed ini file sequence
137  $this->addedFiles[] = $filename;
138  if (!$this->unserialize($this->addedFiles)) {
139  if (self::$logger->isDebugEnabled()) {
140  self::$logger->debug("Parse first time");
141  }
142  $result = $this->processFile($filename, $this->configArray, $this->containedFiles);
143  $this->configArray = $result['config'];
144  $this->containedFiles = array_unique($result['files']);
145 
146  if ($processValues) {
147  $this->processValues();
148  }
149 
150  // re-build lookup table
151  $this->buildLookupTable();
152 
153  // serialize the parsed ini file sequence
154  $this->serialize();
155 
156  // notify configuration change listeners
157  $this->configChanged();
158  }
159  else {
160  if (self::$logger->isDebugEnabled()) {
161  self::$logger->debug("Reuse from cache");
162  }
163  }
164  }
165 
166  /**
167  * Process the given file recursively
168  * @param $filename The filename
169  * @param $configArray Configuration array
170  * @param $parsedFiles Parsed files
171  * @return Associative array with keys 'config' (configuration array) and 'files'
172  * (array of parsed files)
173  */
174  protected function processFile($filename, $configArray=[], $parsedFiles=[]) {
175  // avoid circular includes
176  if (!in_array($filename, $parsedFiles)) {
177  $parsedFiles[] = $filename;
178 
179  $content = $this->parseIniFile($filename);
180 
181  // process includes
182  $includes = $this->getConfigIncludes($content);
183  if ($includes) {
184  $this->processValue($includes);
185  foreach ($includes as $include) {
186  $result = $this->processFile($this->configPath.$include, $configArray, $parsedFiles);
187  $configArray = $this->configMerge($configArray, $result['config'], true);
188  $parsedFiles = $result['files'];
189  }
190  }
191 
192  // process self
193  $configArray = $this->configMerge($configArray, $content, true);
194  }
195  return ['config' => $configArray, 'files' => $parsedFiles];
196  }
197 
198  /**
199  * @see Configuration::getSections()
200  */
201  public function getSections() {
202  return array_keys($this->configArray);
203  }
204 
205  /**
206  * @see Configuration::hasSection()
207  */
208  public function hasSection($section) {
209  return ($this->lookup($section) != null);
210  }
211 
212  /**
213  * @see Configuration::getSection()
214  */
215  public function getSection($section, $includeMeta=false) {
216  $lookupEntry = $this->lookup($section);
217  if ($lookupEntry == null) {
218  throw new ConfigurationException('Section \''.$section.'\' not found!');
219  }
220  else {
221  if ($includeMeta) {
222  return $this->configArray[$lookupEntry[0]];
223  }
224  else {
225  return array_filter($this->configArray[$lookupEntry[0]], function($k) {
226  return !preg_match('/^__/', $k);
227  }, \ARRAY_FILTER_USE_KEY);
228  }
229  }
230  }
231 
232  /**
233  * @see Configuration::hasValue()
234  */
235  public function hasValue($key, $section) {
236  return ($this->lookup($section, $key) != null);
237  }
238 
239  /**
240  * @see Configuration::getValue()
241  */
242  public function getValue($key, $section) {
243  $lookupEntry = $this->lookup($section, $key);
244  if ($lookupEntry == null || sizeof($lookupEntry) == 1) {
245  throw new ConfigurationException('Key \''.$key.'\' not found in section \''.$section.'\'!');
246  }
247  else {
248  return $this->configArray[$lookupEntry[0]][$lookupEntry[1]];
249  }
250  }
251 
252  /**
253  * @see Configuration::getBooleanValue()
254  */
255  public function getBooleanValue($key, $section) {
256  $value = $this->getValue($key, $section);
257  return StringUtil::getBoolean($value);
258  }
259 
260  /**
261  * @see Configuration::getDirectoryValue()
262  */
263  public function getDirectoryValue($key, $section) {
264  $value = $this->getValue($key, $section);
265  $isArray = is_array($value);
266  $values = !$isArray ? [$value] : $value;
267 
268  $result = [];
269  foreach ($values as $path) {
270  $absPath = WCMF_BASE.$path;
271  $result[] = $this->fileUtil->realpath($absPath).'/';
272  }
273 
274  return $isArray ? $result : (sizeof($result) > 0 ? $result[0] : null);
275  }
276 
277  /**
278  * @see Configuration::getFileValue()
279  */
280  public function getFileValue($key, $section) {
281  $value = $this->getValue($key, $section);
282  $isArray = is_array($value);
283  $values = !$isArray ? [$value] : $value;
284 
285  $result = [];
286  foreach ($values as $path) {
287  $absPath = WCMF_BASE.$path;
288  $result[] = $this->fileUtil->realpath(dirname($absPath)).'/'.$this->fileUtil->basename($absPath);
289  }
290 
291  return $isArray ? $result : (sizeof($result) > 0 ? $result[0] : null);
292  }
293 
294  /**
295  * @see Configuration::getKey()
296  */
297  public function getKey($value, $section) {
298  $map = array_flip($this->getSection($section));
299  if (!isset($map[$value])) {
300  throw new ConfigurationException('Value \''.$value.'\' not found in section \''.$section.'\'!');
301  }
302  return $map[$value];
303  }
304 
305  /**
306  * WritableConfiguration interface
307  */
308 
309  /**
310  * @see WritableConfiguration::isEditable()
311  */
312  public function isEditable($section) {
313  if ($this->hasValue('readonlySections', 'config')) {
314  $readonlySections = $this->getValue('readonlySections', 'config');
315  $sectionLower = strtolower($section);
316  if (is_array($readonlySections)) {
317  foreach($readonlySections as $readonlySection) {
318  if ($sectionLower == strtolower($readonlySection)) {
319  return false;
320  }
321  }
322  }
323  }
324  return true;
325  }
326 
327  /**
328  * @see WritableConfiguration::isModified()
329  */
330  public function isModified() {
331  return $this->isModified;
332  }
333 
334  /**
335  * @see WritableConfiguration::createSection()
336  */
337  public function createSection($section) {
338  $section = trim($section);
339  if (strlen($section) == 0) {
340  throw new IllegalArgumentException('Empty section names are not allowed!');
341  }
342  if ($this->hasSection($section)) {
343  throw new IllegalArgumentException('Section \''.$section.'\' already exists!');
344  }
345  $this->configArray[$section] = '';
346  $this->buildLookupTable();
347  $this->isModified = true;
348  return true;
349  }
350 
351  /**
352  * @see WritableConfiguration::removeSection()
353  */
354  public function removeSection($section) {
355  if (!$this->isEditable($section)) {
356  throw new IllegalArgumentException('Section \''.$section.'\' is not editable!');
357  }
358  $lookupEntry = $this->lookup($section);
359  if ($lookupEntry != null) {
360  unset($this->configArray[$lookupEntry[0]]);
361  $this->buildLookupTable();
362  $this->isModified = true;
363  }
364  }
365 
366  /**
367  * @see WritableConfiguration::renameSection()
368  */
369  public function renameSection($oldname, $newname) {
370  $newname = trim($newname);
371  if (strlen($newname) == 0) {
372  throw new IllegalArgumentException('Empty section names are not allowed!');
373  }
374  $lookupEntryOld = $this->lookup($oldname);
375  if ($lookupEntryOld == null) {
376  throw new IllegalArgumentException('Section \''.$oldname.'\' does not exist!');
377  }
378  if (!$this->isEditable($oldname)) {
379  throw new IllegalArgumentException('Section \''.$oldname.'\' is not editable!');
380  }
381  $lookupEntryNew = $this->lookup($newname);
382  if ($lookupEntryNew != null) {
383  throw new IllegalArgumentException('Section \''.$newname.'\' already exists!');
384  }
385  // do rename
386  $value = $this->configArray[$lookupEntryOld[0]];
387  $this->configArray[$newname] = $value;
388  unset($this->configArray[$lookupEntryOld[0]]);
389  $this->buildLookupTable();
390  $this->isModified = true;
391  }
392 
393  /**
394  * @see WritableConfiguration::setValue()
395  */
396  public function setValue($key, $value, $section, $createSection=true) {
397  $key = trim($key);
398  if (strlen($key) == 0) {
399  throw new IllegalArgumentException('Empty key names are not allowed!');
400  }
401  $lookupEntrySection = $this->lookup($section);
402  if ($lookupEntrySection == null && !$createSection) {
403  throw new IllegalArgumentException('Section \''.$section.'\' does not exist!');
404  }
405  if ($lookupEntrySection != null && !$this->isEditable($section)) {
406  throw new IllegalArgumentException('Section \''.$section.'\' is not editable!');
407  }
408 
409  // create section if requested and determine section name
410  if ($lookupEntrySection == null && $createSection) {
411  $section = trim($section);
412  $this->configArray[$section] = [];
413  $finalSectionName = $section;
414  }
415  else {
416  $finalSectionName = $lookupEntrySection[0];
417  }
418  // determine key name
419  if ($lookupEntrySection != null) {
420  $lookupEntryKey = $this->lookup($section, $key);
421  if ($lookupEntryKey == null) {
422  // key does not exist yet
423  $finalKeyName = $key;
424  }
425  else {
426  $finalKeyName = $lookupEntryKey[1];
427  }
428  }
429  else {
430  $finalKeyName = $key;
431  }
432  $this->configArray[$finalSectionName][$finalKeyName] = $value;
433  $this->buildLookupTable();
434  $this->isModified = true;
435  }
436 
437  /**
438  * @see WritableConfiguration::removeKey()
439  */
440  public function removeKey($key, $section) {
441  if (!$this->isEditable($section)) {
442  throw new IllegalArgumentException('Section \''.$section.'\' is not editable!');
443  }
444  $lookupEntry = $this->lookup($section, $key);
445  if ($lookupEntry != null) {
446  unset($this->configArray[$lookupEntry[0]][$lookupEntry[1]]);
447  $this->buildLookupTable();
448  $this->isModified = true;
449  }
450  }
451 
452  /**
453  * @see WritableConfiguration::renameKey()
454  */
455  public function renameKey($oldname, $newname, $section) {
456  $newname = trim($newname);
457  if (strlen($newname) == 0) {
458  throw new IllegalArgumentException('Empty key names are not allowed!');
459  }
460  if (!$this->hasSection($section)) {
461  throw new IllegalArgumentException('Section \''.$section.'\' does not exist!');
462  }
463  if (!$this->isEditable($section)) {
464  throw new IllegalArgumentException('Section \''.$section.'\' is not editable!');
465  }
466  $lookupEntryOld = $this->lookup($section, $oldname);
467  if ($lookupEntryOld == null) {
468  throw new IllegalArgumentException('Key \''.$oldname.'\' does not exist in section \''.$section.'\'!');
469  }
470  $lookupEntryNew = $this->lookup($section, $newname);
471  if ($lookupEntryNew != null) {
472  throw new IllegalArgumentException('Key \''.$newname.'\' already exists in section \''.$section.'\'!');
473  }
474  // do rename
475  $value = $this->configArray[$lookupEntryOld[0]][$lookupEntryOld[1]];
476  $this->configArray[$lookupEntryOld[0]][$newname] = $value;
477  unset($this->configArray[$lookupEntryOld[0]][$lookupEntryOld[1]]);
478  $this->buildLookupTable();
479  $this->isModified = true;
480  }
481 
482  /**
483  * @see WritableConfiguration::writeConfiguration()
484  */
485  public function writeConfiguration($name) {
486  $filename = $name;
487  $content = "";
488  foreach($this->configArray as $section => $values) {
489  $sectionString = "[".$section."]";
490  $content .= $this->comments[$sectionString];
491  $content .= $sectionString."\n";
492  if (is_array($values)) {
493  foreach($values as $key => $value) {
494  if (is_array($value)) {
495  $value = "{".join(", ", $value)."}";
496  }
497  // unescape double quotes
498  $value = str_replace("\\\"", "\"", $value);
499  $content .= $this->comments[$section][$key];
500  $content .= $key." = ".$value."\n";
501  }
502  }
503  }
504  $content .= $this->comments[';'];
505 
506  if (!$fh = fopen($filename, 'w')) {
507  throw new IOException('Can\'t open ini file \''.$filename.'\'!');
508  }
509 
510  if (!fwrite($fh, $content)) {
511  throw new IOException('Can\'t write ini file \''.$filename.'\'!');
512  }
513  fclose($fh);
514 
515  // notify configuration change listeners
516  $this->configChanged();
517  $this->isModified = false;
518  }
519 
520  /**
521  * Private interface
522  */
523 
524  /**
525  * Load in the ini file specified in filename, and return
526  * the settings in a multidimensional array, with the section names and
527  * settings included. All section names and keys are lowercased.
528  * @param $filename The filename of the ini file to parse
529  * @return An associative array containing the data
530  *
531  * @author: Sebastien Cevey <seb@cine7.net>
532  * Original Code base: <info@megaman.nl>
533  * Added comment handling/Removed process sections flag: Ingo Herwig
534  */
535  protected function parseIniFile($filename) {
536  if (!file_exists($filename)) {
537  throw new ConfigurationException('The config file '.$filename.' does not exist.');
538  }
539  $configArray = [];
540  $sectionName = '';
541  $lines = file($filename);
542  $commentsPending = '';
543  foreach($lines as $line) {
544  $line = trim($line);
545  // comments/blank lines
546  if($line == '' || $line[0] == ';') {
547  $commentsPending .= $line."\n";
548  continue;
549  }
550 
551  if($line[0] == '[' && $line[strlen($line)-1] == ']') {
552  $sectionName = substr($line, 1, strlen($line)-2);
553  $configArray[$sectionName] = [];
554 
555  // store comments/blank lines for section
556  $this->comments[$line] = $commentsPending;
557  $commentsPending = '';
558  }
559  else {
560  $parts = explode('=', $line, 2);
561  $key = trim($parts[0]);
562  $value = trim($parts[1]);
563  $configArray[$sectionName][$key] = $value;
564 
565  // store comments/blank lines for key
566  $this->comments[$sectionName][$key] = $commentsPending;
567  $commentsPending = "";
568  }
569  }
570  // store comments/blank lines from the end of the file
571  $this->comments[';'] = substr($commentsPending, 0, -1);
572 
573  return $configArray;
574  }
575 
576  /**
577  * Process the values in the ini array.
578  * This method turns string values that hold array definitions
579  * (comma separated values enclosed by curly brackets) into array values.
580  */
581  protected function processValues() {
582  array_walk_recursive($this->configArray, [$this, 'processValue']);
583  }
584 
585  /**
586  * Process the values in the ini array.
587  * This method turns string values that hold array definitions
588  * (comma separated values enclosed by curly brackets) into array values.
589  * @param $value A reference to the value
590  */
591  protected function processValue(&$value) {
592  if (!is_array($value)) {
593  // decode encoded (%##) values
594  if (preg_match ("/%/", $value)) {
595  $value = urldecode($value);
596  }
597  // make arrays
598  if(preg_match("/^{.*}$/", $value)) {
599  $arrayValues = StringUtil::quotesplit(substr($value, 1, -1));
600  $value = [];
601  foreach ($arrayValues as $arrayValue) {
602  $value[] = trim($arrayValue);
603  }
604  }
605  }
606  }
607 
608  /**
609  * Merge the second array into the first, preserving entries of the first array
610  * unless the second array contains the special key '__inherit' set to false
611  * or they are re-defined in the second array.
612  * @param $array1 First array.
613  * @param $array2 Second array.
614  * @param $override Boolean whether values defined in array1 should be overridden by values defined in array2.
615  * @return The merged array.
616  */
617  protected function configMerge($array1, $array2, $override) {
618  $result = $array1;
619  foreach(array_keys($array2) as $key) {
620  if (!array_key_exists($key, $result)) {
621  // copy complete section, if new
622  $result[$key] = $array2[$key];
623  }
624  else {
625  // process existing section
626  // remove old keys, if inheritence is disabled
627  $inherit = !isset($array2[$key]['__inherit']) || $array2[$key]['__inherit'] == false;
628  if (!$inherit) {
629  foreach(array_keys($result[$key]) as $subkey) {
630  unset($result[$key][$subkey]);
631  }
632  }
633  // merge in new keys
634  foreach(array_keys($array2[$key]) as $subkey) {
635  if ((array_key_exists($subkey, $result[$key]) && $override) || !isset($result[$key][$subkey])) {
636  $result[$key][$subkey] = $array2[$key][$subkey];
637  }
638  }
639  }
640  }
641  return $result;
642  }
643 
644  /**
645  * Search the given value for a 'include' key in a section named 'config' (case-insensivite)
646  * @param $array The array to search in
647  * @return Mixed
648  */
649  protected function getConfigIncludes($array) {
650  $sectionMatches = null;
651  if (preg_match('/(?:^|,)(config)(?:,|$)/i', join(',', array_keys($array)), $sectionMatches)) {
652  $sectionKey = sizeof($sectionMatches) > 0 ? $sectionMatches[1] : null;
653  if ($sectionKey) {
654  $keyMatches = null;
655  if (preg_match('/(?:^|,)(include)(?:,|$)/i', join(',', array_keys($array[$sectionKey])), $keyMatches)) {
656  return sizeof($keyMatches) > 0 ? $array[$sectionKey][$keyMatches[1]] : null;
657  }
658  }
659  }
660  return null;
661  }
662 
663  /**
664  * Store the instance in the file system. If the instance is modified, this call is ignored.
665  */
666  protected function serialize() {
667  if (!$this->isModified() && ($cacheFile = $this->getSerializeFilename($this->addedFiles))) {
668  if (self::$logger->isDebugEnabled()) {
669  self::$logger->debug("Serialize configuration: ".join(',', $this->addedFiles)." to file: ".$cacheFile);
670  }
671  $this->fileUtil->mkdirRec(dirname($cacheFile));
672  if ($fh = @fopen($cacheFile, "w")) {
673  if (@fwrite($fh, serialize(array_filter(get_object_vars($this), function($value, $name) {
674  return $name != 'comments'; // don't store comments
675  }, ARRAY_FILTER_USE_BOTH)))) {
676  @fclose($fh);
677  }
678  }
679  }
680  }
681 
682  /**
683  * Retrieve parsed ini data from the file system and update the current instance.
684  * If the current instance is modified or any file given in parsedFiles
685  * is newer than the serialized data, this call is ignored.
686  * If InifileConfiguration class changed, the call will be ignored as well.
687  * @param $parsedFiles An array of ini filenames that must be contained in the data.
688  * @return Boolean whether the data could be retrieved or not
689  */
690  protected function unserialize($parsedFiles) {
691  if (!$this->isModified() && ($cacheFile = $this->getSerializeFilename($parsedFiles)) && file_exists($cacheFile)) {
692  $parsedFiles[] = __FILE__;
693  if (!$this->checkFileDate($parsedFiles, $cacheFile)) {
694  $vars = unserialize(file_get_contents($cacheFile));
695 
696  // check if included ini files were updated since last cache time
697  $includes = $vars['containedFiles'];
698  if (is_array($includes)) {
699  if ($this->checkFileDate($includes, $cacheFile)) {
700  return false;
701  }
702  }
703 
704  // everything is up-to-date
705  foreach($vars as $key => $val) {
706  $this->$key = $val;
707  }
708  return true;
709  }
710  }
711  return false;
712  }
713 
714  /**
715  * Get the filename for the serialized data that correspond to the the given ini file sequence.
716  * NOTE: The method returns null, if no cache path is configured
717  * @param $parsedFiles An array of parsed filenames
718  * @return String
719  */
720  protected function getSerializeFilename($parsedFiles) {
721  if (!$this->cachePath) {
722  return null;
723  }
724  $path = $this->fileUtil->realpath($this->cachePath).'/';
725  $filename = $path.'wcmf_config_'.hash('sha256', $this->fileUtil->realpath($this->configPath).'/'.join('_', $parsedFiles));
726  return $filename;
727  }
728 
729  /**
730  * Check if one file in fileList is newer than the referenceFile.
731  * @param $fileList An array of files
732  * @param $referenceFile The file to check against
733  * @return True, if one of the files is newer, false else
734  */
735  protected function checkFileDate($fileList, $referenceFile) {
736  foreach ($fileList as $file) {
737  if (@filemtime($file) > @filemtime($referenceFile)) {
738  return true;
739  }
740  }
741  return false;
742  }
743 
744  /**
745  * Notify configuration change listeners
746  */
747  protected function configChanged() {
748  if (self::$logger->isDebugEnabled()) {
749  self::$logger->debug("Configuration is changed");
750  }
751  if (ObjectFactory::isConfigured()) {
752  if (self::$logger->isDebugEnabled()) {
753  self::$logger->debug("Emitting change event");
754  }
755  ObjectFactory::getInstance('eventManager')->dispatch(ConfigChangeEvent::NAME,
756  new ConfigChangeEvent());
757  }
758  }
759 
760  /**
761  * Build the internal lookup table
762  */
763  protected function buildLookupTable() {
764  $this->lookupTable = [];
765  foreach ($this->configArray as $section => $entry) {
766  // create section entry
767  $lookupSectionKey = strtolower($section.':');
768  $this->lookupTable[$lookupSectionKey] = [$section];
769  // create key entries
770  foreach ($entry as $key => $value) {
771  $lookupKey = strtolower($lookupSectionKey.$key);
772  $this->lookupTable[$lookupKey] = [$section, $key];
773  }
774  }
775  }
776 
777  /**
778  * Lookup section and key.
779  * @param $section The section to lookup
780  * @param $key The key to lookup (optional)
781  * @return Array with section as first entry and key as second or null if not found
782  */
783  protected function lookup($section, $key=null) {
784  $lookupKey = strtolower($section).':'.strtolower($key);
785  if (isset($this->lookupTable[$lookupKey])) {
786  return $this->lookupTable[$lookupKey];
787  }
788  return null;
789  }
790 }
791 ?>
Implementations of WritableConfiguration allow to change the whole or parts of the configuration and ...
__construct($configPath, $cachePath=null)
Constructor.
getConfigIncludes($array)
Search the given value for a 'include' key in a section named 'config' (case-insensivite)
buildLookupTable()
Build the internal lookup table.
processFile($filename, $configArray=[], $parsedFiles=[])
Process the given file recursively.
getConfigPath()
Get the file system path to the configuration files.
getSerializeFilename($parsedFiles)
Get the filename for the serialized data that correspond to the the given ini file sequence.
processValues()
Process the values in the ini array.
IllegalArgumentException signals an exception in method arguments.
lookup($section, $key=null)
Lookup section and key.
InifileConfiguration reads the application configuration from ini files.
configMerge($array1, $array2, $override)
Merge the second array into the first, preserving entries of the first array unless the second array ...
StringUtil provides support for string manipulation.
Definition: StringUtil.php:18
Implementations of Configuration give access to the application configuration.
ConfigurationException signals an exception in the configuration.
processValue(&$value)
Process the values in the ini array.
checkFileDate($fileList, $referenceFile)
Check if one file in fileList is newer than the referenceFile.
unserialize($parsedFiles)
Retrieve parsed ini data from the file system and update the current instance.
FileUtil provides basic support for file functionality like HTTP file upload.
Definition: FileUtil.php:22
static getLogger($name)
Get the logger with the given name.
Definition: LogManager.php:37
IOException signals an exception in i/o operations.
Definition: IOException.php:18
serialize()
Store the instance in the file system.
LogManager is used to retrieve Logger instances.
Definition: LogManager.php:20
ConfigChangeEvent signals a change of the application configuration.
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...
configChanged()
Notify configuration change listeners.