<?php /** * Akeeba Engine * The modular PHP5 site backup engine * * @copyright Copyright (c)2006-2017 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU GPL version 3 or, at your option, any later version * @package akeebaengine * */ namespace Akeeba\Engine; // Protection against direct access use Akeeba\Engine\Util\ParseIni; defined('AKEEBAENGINE') or die(); /** * The Akeeba Engine configuration registry class */ class Configuration { /** @var string Default NameSpace */ private $defaultNameSpace = 'global'; /** @var array Array keys which may contain stock directory definitions */ private $directory_containing_keys = array( 'akeeba.basic.output_directory' ); /** @var array Keys whose default values should never be overridden */ private $protected_nodes = array(); /** @var array The registry data */ private $registry = array(); /** @var integer The currently loaded profile */ public $activeProfile = null; /** * Constructor * * @return Configuration */ public function __construct() { // Create the default namespace $this->makeNameSpace($this->defaultNameSpace); // Create a default configuration $this->reset(); } /** * Create a namespace * * @param string $namespace Name of the namespace to create * * @return void */ public function makeNameSpace($namespace) { $this->registry[$namespace] = array('data' => new \stdClass()); } /** * Get the list of namespaces * * @return array List of namespaces */ public function getNameSpaces() { return array_keys($this->registry); } /** * Get a registry value * * @param string $regpath Registry path (e.g. global.directory.temporary) * @param mixed $default Optional default value * @param boolean $process_special_vars Optional. If true (default), it processes special variables, e.g. * [SITEROOT] in folder names * * @return mixed Value of entry or null */ public function get($regpath, $default = null, $process_special_vars = true) { // Cache the platform-specific stock directories static $stock_directories = array(); if (empty($stock_directories)) { $stock_directories = Platform::getInstance()->get_stock_directories(); } $result = $default; // Explode the registry path into an array if ($nodes = explode('.', $regpath)) { // Get the namespace $count = count($nodes); if ($count < 2) { $namespace = $this->defaultNameSpace; $nodes[1] = $nodes[0]; } else { $namespace = $nodes[0]; } if (isset($this->registry[$namespace])) { $ns = $this->registry[$namespace]['data']; $pathNodes = $count - 1; for ($i = 1; $i < $pathNodes; $i++) { if ((isset($ns->{$nodes[$i]}))) { $ns = $ns->{$nodes[$i]}; } } if (isset($ns->{$nodes[$i]})) { $result = $ns->{$nodes[$i]}; } } } // Post-process certain directory-containing variables if ($process_special_vars && in_array($regpath, $this->directory_containing_keys)) { if (!empty($stock_directories)) { foreach ($stock_directories as $tag => $content) { $result = str_replace($tag, $content, $result); } } } return $result; } /** * Set a registry value * * @param string $regpath Registry Path (e.g. global.directory.temporary) * @param mixed $value Value of entry * @param bool $process_special_vars Optional. If true (default), it processes special variables, e.g. * [SITEROOT] in folder names * * @return mixed Value of old value or boolean false if operation failed */ public function set($regpath, $value, $process_special_vars = true) { // Cache the platform-specific stock directories static $stock_directories = array(); if (empty($stock_directories)) { $stock_directories = Platform::getInstance()->get_stock_directories(); } if (in_array($regpath, $this->protected_nodes)) { return $this->get($regpath); } // Explode the registry path into an array $nodes = explode('.', $regpath); // Get the namespace $count = count($nodes); if ($count < 2) { $namespace = $this->defaultNameSpace; } else { $namespace = array_shift($nodes); $count--; } if (!isset($this->registry[$namespace])) { $this->makeNameSpace($namespace); } $ns = $this->registry[$namespace]['data']; $pathNodes = $count - 1; if ($pathNodes < 0) { $pathNodes = 0; } for ($i = 0; $i < $pathNodes; $i++) { // If any node along the registry path does not exist, create it if (!isset($ns->{$nodes[$i]})) { $ns->{$nodes[$i]} = new \stdClass(); } $ns = $ns->{$nodes[$i]}; } // Set the new values if (is_string($value)) { if (substr($value, 0, 10) == '###json###') { $value = json_decode(substr($value, 10)); } } // Post-process certain directory-containing variables if ($process_special_vars && in_array($regpath, $this->directory_containing_keys)) { if (!empty($stock_directories)) { $data = $value; foreach ($stock_directories as $tag => $content) { $data = str_replace($tag, $content, $data); } $ns->{$nodes[$i]} = $data; return $ns->{$nodes[$i]}; } } // This is executed if any of the previous two if's is false if (empty($nodes[$i])) { return false; } $ns->{$nodes[$i]} = $value; return $ns->{$nodes[$i]}; } /** * Unset (remove) a registry value * * @param string $regpath Registry Path (e.g. global.directory.temporary) * * @return boolean True if the node was removed */ public function remove($regpath) { // Explode the registry path into an array $nodes = explode('.', $regpath); // Get the namespace $count = count($nodes); if ($count < 2) { $namespace = $this->defaultNameSpace; } else { $namespace = array_shift($nodes); $count--; } if (!isset($this->registry[$namespace])) { $this->makeNameSpace($namespace); } $ns = $this->registry[$namespace]['data']; $pathNodes = $count - 1; if ($pathNodes < 0) { $pathNodes = 0; } for ($i = 0; $i < $pathNodes; $i++) { // If any node along the registry path does not exist, return false if (!isset($ns->{$nodes[$i]})) { return false; } $ns = $ns->{$nodes[$i]}; } unset($ns->{$nodes[$i]}); return true; } /** * Resets the registry to the default values */ public function reset() { // Load the Akeeba Engine INI files $root_path = __DIR__; $paths = array( $root_path . '/Core', $root_path . '/Archiver', $root_path . '/Dump', $root_path . '/Scan', $root_path . '/Writer', $root_path . '/Proc', $root_path . '/Platform/Filter/Stack', $root_path . '/Filter/Stack', ); $platform_paths = Platform::getInstance()->getPlatformDirectories(); foreach ($platform_paths as $p) { $paths[] = $p . '/Filter/Stack'; $paths[] = $p . '/Config'; } foreach ($paths as $root) { if (!(is_dir($root) || is_link($root))) { continue; } if (!is_readable($root)) { continue; } $di = new \DirectoryIterator($root); /** @var \DirectoryIterator $file */ foreach ($di as $file) { if (!$file->isFile()) { continue; } // PHP 5.3.5 and earlier do not support getExtension // if ($file->getExtension() == 'ini') if (substr($file->getBasename(), -4) == '.ini') { $this->mergeEngineINI($file->getRealPath()); } } } } /** * Merges an associative array of key/value pairs into the registry. * If noOverride is set, only non set or null values will be applied. * * @param array $array An associative array. Its keys are registry paths. * @param bool $noOverride [optional] Do not override pre-set values. * @param bool $process_special_vars Optional. If true (default), it processes special variables, e.g. * [SITEROOT] in folder names */ public function mergeArray($array, $noOverride = false, $process_special_vars = true) { if (!$noOverride) { foreach ($array as $key => $value) { $this->set($key, $value, $process_special_vars); } } else { foreach ($array as $key => $value) { if (is_null($this->get($key, null))) { $this->set($key, $value, $process_special_vars); } } } } /** * Merges an INI-style file into the registry. Its sections are registry paths, * keys are appended to the section-defined paths and then set equal to the * values. If noOverride is set, only non set or null values will be applied. * Sections beginning with an underscore will be ignored. * * @param string $inifile The full path to the INI file to load * @param boolean $noOverride [optional] Do not override pre-set values. * * @return boolean True on success */ public function mergeINI($inifile, $noOverride = false) { if (!file_exists($inifile)) { return false; } $inidata = ParseIni::parse_ini_file($inifile, true); foreach ($inidata as $rootkey => $rootvalue) { if (!is_array($rootvalue)) { if (!$noOverride) { $this->set($rootkey, $rootvalue); } elseif (is_null($this->get($rootkey, null))) { $this->set($rootkey, $rootvalue); } } elseif (substr($rootkey, 0, 1) != '_') { foreach ($rootvalue as $key => $value) { if (!$noOverride) { $this->set($rootkey . '.' . $key, $rootvalue); } elseif (is_null($this->get($rootkey . '.' . $key, null))) { $this->set($rootkey . '.' . $key, $rootvalue); } } } } return true; } /** * Merges an engine INI file to the configuration. Each section defines a full * registry path (section.subsection.key). It searches each section for the * key named "default" and merges its value to the configuration. The other keys * are simply ignored. * * @param string $inifile The absolute path to an INI file * @param bool $noOverride [optional] If true, values from the INI will not override the configuration * * @return boolean True on success */ public function mergeEngineINI($inifile, $noOverride = false) { if (!file_exists($inifile)) { return false; } $inidata = ParseIni::parse_ini_file($inifile, true); foreach ($inidata as $section => $nodes) { if (is_array($nodes)) { if (substr($section, 0, 1) != '_') { // Is this a protected node? $protected = false; if (array_key_exists('protected', $nodes)) { $protected = $nodes['protected']; } // If overrides are allowed, unprotect until we can set the value if (!$noOverride) { if (in_array($section, $this->protected_nodes)) { $pnk = array_search($section, $this->protected_nodes); unset($this->protected_nodes[$pnk]); } } if (array_key_exists('remove', $nodes)) { // Remove a node if it has "remove" set $this->remove($section); } elseif (isset($nodes['default'])) { if (!$noOverride) { // Update the default value if No Override is set $this->set($section, $nodes['default']); } elseif (is_null($this->get($section, null))) { // Set the default value if it does not exist $this->set($section, $nodes['default']); } } // Finally, if it's a protected node, enable the protection if ($protected) { $this->protected_nodes[] = $section; } else { $idx = array_search($section, $this->protected_nodes); if ($idx !== false) { unset($this->protected_nodes[$idx]); } } } } } return true; } /** * Exports the current registry snapshot as an INI file. Each namespace is * placed in a section of its own. * * @return string INI representation of the registry */ public function exportAsINI() { $inidata = ''; $namespaces = $this->getNameSpaces(); foreach ($namespaces as $namespace) { if ($namespace == 'volatile') { continue; } $inidata .= "[$namespace]\n"; $ns = $this->registry[$namespace]['data']; $inidata .= $this->dumpObject($ns); } return $inidata; } /** * Internal function to dump an object as INI-formatted data * * @param object $object The object to dump * @param string $prefix [optional] The prefix to use for the exported data * * @return string */ private function dumpObject($object, $prefix = '') { $data = ''; $vars = get_object_vars($object); foreach ($vars as $key => $value) { if (!is_object($value)) { if (is_array($value)) { $value = '###json###' . json_encode($value); } $data .= (empty($prefix) ? '' : $prefix . '.') . $key . '="' . addcslashes($value, "\n\r\t\"") . "\"\n"; } else { $data .= $this->dumpObject($value, (empty($prefix) ? '' : $prefix . '.') . $key); } } return $data; } /** * Sets the protection status for a specific configuration key * * @param string|array $node The node to protect/unprotect * @param boolean $protect True to protect, false to unprotect * * @return void */ public function setKeyProtection($node, $protect = false) { if (is_array($node)) { foreach ($node as $k) { $this->setKeyProtection($k, $protect); } } elseif (is_string($node)) { if (is_array($this->protected_nodes)) { $protected = in_array($node, $this->protected_nodes); } else { $this->protected_nodes = array(); $protected = false; } if ($protect) { if (!$protected) { $this->protected_nodes[] = $node; } } else { if ($protected) { $pnk = array_search($node, $this->protected_nodes); unset($this->protected_nodes[$pnk]); } } } } /** * Returns a list of protected keys * * @return array */ public function getProtectedKeys() { return $this->protected_nodes; } /** * Resets the protected keys * * @return void */ public function resetProtectedKeys() { $this->protected_nodes = array(); } /** * Sets the protected keys * * @param array $keys A list of keys to protect * * @return void */ public function setProtectedKeys($keys) { $this->protected_nodes = $keys; } } ?>