PageRenderTime 60ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/administrator/components/com_akeeba/akeeba/configuration.php

https://github.com/CCI-Studios/Wee-Magazine
PHP | 544 lines | 424 code | 34 blank | 86 comment | 47 complexity | b663dc4e246fc7e9e022520c74280dee MD5 | raw file
  1. <?php
  2. /**
  3. * Akeeba Engine
  4. * The modular PHP5 site backup engine
  5. * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
  6. * @license GNU GPL version 3 or, at your option, any later version
  7. * @package akeebaengine
  8. *
  9. */
  10. // Protection against direct access
  11. defined('AKEEBAENGINE') or die();
  12. /**
  13. * The Akeeba Engine configuration registry class
  14. */
  15. class AEConfiguration {
  16. /** @var string Default NameSpace */
  17. private $defaultNameSpace = 'global';
  18. /** @var array Array keys which may contain stock directory definitions */
  19. private $directory_containing_keys = array(
  20. 'akeeba.basic.output_directory'
  21. );
  22. /** @var array Keys whose default values should never be overridden */
  23. private $protected_nodes = array();
  24. /** @var array The registry data */
  25. private $registry = array();
  26. /** @var int The currently loaded profile */
  27. public $activeProfile = null;
  28. /**
  29. * Constructor
  30. */
  31. public function __construct()
  32. {
  33. // Assisted Singleton pattern
  34. if(function_exists('debug_backtrace'))
  35. {
  36. $caller=debug_backtrace();
  37. $caller = $caller[1];
  38. if($caller['class'] != 'AEFactory') trigger_error("You can't create a direct descendant of ".__CLASS__, E_USER_ERROR);
  39. }
  40. // Create the default namespace
  41. $this->makeNameSpace($this->defaultNameSpace);
  42. // Create a default configuration
  43. $this->reset();
  44. }
  45. /**
  46. * Create a namespace
  47. * @param string $namespace Name of the namespace to create
  48. */
  49. public function makeNameSpace($namespace)
  50. {
  51. $this->registry[$namespace] = array('data' => new stdClass());
  52. }
  53. /**
  54. * Get the list of namespaces
  55. * @return array List of namespaces
  56. */
  57. public function getNameSpaces()
  58. {
  59. return array_keys($this->registry);
  60. }
  61. /**
  62. * Get a registry value
  63. * @param string $regpath Registry path (e.g. global.directory.temporary)
  64. * @param mixed $default Optional default value
  65. * @param bool $process_special_vars Optional. If true (default), it processes special variables, e.g. [SITEROOT] in folder names
  66. * @return mixed Value of entry or null
  67. */
  68. public function get($regpath, $default=null, $process_special_vars = true)
  69. {
  70. // Cache the platform-specific stock directories
  71. static $stock_directories = array();
  72. if(empty($stock_directories))
  73. {
  74. $stock_directories = AEPlatform::getInstance()->get_stock_directories();
  75. }
  76. $result = $default;
  77. // Explode the registry path into an array
  78. if ($nodes = explode('.', $regpath))
  79. {
  80. // Get the namespace
  81. $count = count($nodes);
  82. if ($count < 2) {
  83. $namespace = $this->defaultNameSpace;
  84. $nodes[1] = $nodes[0];
  85. } else {
  86. $namespace = $nodes[0];
  87. }
  88. if (isset($this->registry[$namespace])) {
  89. $ns = $this->registry[$namespace]['data'];
  90. $pathNodes = $count - 1;
  91. for ($i = 1; $i < $pathNodes; $i ++) {
  92. if((isset($ns->$nodes[$i]))) $ns = $ns->$nodes[$i];
  93. }
  94. if(isset($ns->$nodes[$i])) {
  95. $result = $ns->$nodes[$i];
  96. }
  97. }
  98. }
  99. // Post-process certain directory-containing variables
  100. if( $process_special_vars && in_array($regpath, $this->directory_containing_keys) )
  101. {
  102. if(!empty($stock_directories))
  103. {
  104. foreach($stock_directories as $tag => $content)
  105. {
  106. $result = str_replace($tag, $content, $result);
  107. }
  108. }
  109. }
  110. return $result;
  111. }
  112. /**
  113. * Set a registry value
  114. * @param string $regpath Registry Path (e.g. global.directory.temporary)
  115. * @param mixed $value Value of entry
  116. * @param bool $process_special_vars Optional. If true (default), it processes special variables, e.g. [SITEROOT] in folder names
  117. * @return mixed Value of old value or boolean false if operation failed
  118. */
  119. public function set($regpath, $value, $process_special_vars = true)
  120. {
  121. // Cache the platform-specific stock directories
  122. static $stock_directories = array();
  123. if(empty($stock_directories))
  124. {
  125. $stock_directories = AEPlatform::getInstance()->get_stock_directories();
  126. }
  127. if(in_array($regpath, $this->protected_nodes)) {
  128. return $this->get($regpath);
  129. }
  130. // Explode the registry path into an array
  131. $nodes = explode('.', $regpath);
  132. // Get the namespace
  133. $count = count($nodes);
  134. if ($count < 2) {
  135. $namespace = $this->defaultNameSpace;
  136. } else {
  137. $namespace = array_shift($nodes);
  138. $count--;
  139. }
  140. if (!isset($this->registry[$namespace])) {
  141. $this->makeNameSpace($namespace);
  142. }
  143. $ns = $this->registry[$namespace]['data'];
  144. $pathNodes = $count - 1;
  145. if ($pathNodes < 0) {
  146. $pathNodes = 0;
  147. }
  148. for ($i = 0; $i < $pathNodes; $i ++)
  149. {
  150. // If any node along the registry path does not exist, create it
  151. if (!isset($ns->$nodes[$i])) {
  152. $ns->$nodes[$i] = new stdClass();
  153. }
  154. $ns = $ns->$nodes[$i];
  155. }
  156. // Set the new values
  157. if(is_string($value))
  158. {
  159. if(substr($value,0,10) == '###json###')
  160. {
  161. $value = json_decode(substr($value,10));
  162. }
  163. }
  164. // Post-process certain directory-containing variables
  165. if( $process_special_vars && in_array($regpath, $this->directory_containing_keys) )
  166. {
  167. if(!empty($stock_directories))
  168. {
  169. $data = $value;
  170. foreach($stock_directories as $tag => $content)
  171. {
  172. $data = str_replace($tag, $content, $data);
  173. }
  174. $ns->$nodes[$i] = $data;
  175. return $ns->$nodes[$i];
  176. }
  177. }
  178. // This is executed if any of the previous two if's is false
  179. $ns->$nodes[$i] = $value;
  180. return $ns->$nodes[$i];
  181. }
  182. /**
  183. * Unset (remove) a registry value
  184. * @param string $regpath Registry Path (e.g. global.directory.temporary)
  185. * @return bool True if the node was removed
  186. */
  187. public function remove($regpath)
  188. {
  189. // Explode the registry path into an array
  190. $nodes = explode('.', $regpath);
  191. // Get the namespace
  192. $count = count($nodes);
  193. if ($count < 2) {
  194. $namespace = $this->defaultNameSpace;
  195. } else {
  196. $namespace = array_shift($nodes);
  197. $count--;
  198. }
  199. if (!isset($this->registry[$namespace])) {
  200. $this->makeNameSpace($namespace);
  201. }
  202. $ns = $this->registry[$namespace]['data'];
  203. $pathNodes = $count - 1;
  204. if ($pathNodes < 0) {
  205. $pathNodes = 0;
  206. }
  207. for ($i = 0; $i < $pathNodes; $i ++)
  208. {
  209. // If any node along the registry path does not exist, return false
  210. if (!isset($ns->$nodes[$i])) {
  211. return false;
  212. }
  213. $ns = $ns->$nodes[$i];
  214. }
  215. unset($ns->$nodes[$i]);
  216. return true;
  217. }
  218. /**
  219. * Resets the registry to the default values
  220. */
  221. public function reset()
  222. {
  223. // Load the Akeeba Engine INI files
  224. $ds = DIRECTORY_SEPARATOR;
  225. $root_path = dirname(__FILE__);
  226. if(defined('AKEEBAROOT')) {
  227. $root_path = AKEEBAROOT;
  228. }
  229. $plugin_path = $root_path.$ds.'plugins';
  230. $paths = array(
  231. $root_path.$ds.'core',
  232. $root_path.$ds.'engines'.$ds.'archiver',
  233. $root_path.$ds.'engines'.$ds.'dump',
  234. $root_path.$ds.'engines'.$ds.'scan',
  235. $root_path.$ds.'engines'.$ds.'writer',
  236. $root_path.$ds.'engines'.$ds.'proc',
  237. $root_path.$ds.'platform'.$ds.'filters'.$ds.'stack',
  238. $root_path.$ds.'filters'.$ds.'stack',
  239. /**/
  240. $plugin_path.$ds.'core',
  241. $plugin_path.$ds.'engines'.$ds.'archiver',
  242. $plugin_path.$ds.'engines'.$ds.'dump',
  243. $plugin_path.$ds.'engines'.$ds.'scan',
  244. $plugin_path.$ds.'engines'.$ds.'writer',
  245. $plugin_path.$ds.'engines'.$ds.'proc',
  246. $plugin_path.$ds.'filters'.$ds.'stack'
  247. /**/
  248. );
  249. $platform_paths = AEPlatform::getInstance()->getPlatformDirectories();
  250. foreach($platform_paths as $p) {
  251. $paths[] = $p.'/filters/stack';
  252. $paths[] = $p.'/config';
  253. }
  254. foreach($paths as $root)
  255. {
  256. $handle = false;
  257. if( is_dir($root) || is_link($root) ) {
  258. if(is_readable($root)) {
  259. $handle = @opendir($root);
  260. }
  261. }
  262. //$handle = @opendir($root);
  263. if($handle !== false)
  264. {
  265. while( false !== ($file = @readdir($handle)) )
  266. {
  267. if(substr($file,-4) == '.ini')
  268. {
  269. $this->mergeEngineINI($root.DIRECTORY_SEPARATOR.$file);
  270. }
  271. }
  272. closedir($handle);
  273. }
  274. }
  275. }
  276. /**
  277. * Merges an associative array of key/value pairs into the registry.
  278. * If noOverride is set, only non set or null values will be applied.
  279. * @param array $array An associative array. Its keys are registry paths.
  280. * @param bool $noOverride [optional] Do not override pre-set values.
  281. * @param bool $process_special_vars Optional. If true (default), it processes special variables, e.g. [SITEROOT] in folder names
  282. */
  283. public function mergeArray($array, $noOverride = false, $process_special_vars = true)
  284. {
  285. if(!$noOverride)
  286. {
  287. foreach($array as $key => $value)
  288. {
  289. $this->set($key, $value, $process_special_vars);
  290. }
  291. }
  292. else
  293. {
  294. foreach($array as $key => $value)
  295. {
  296. if( is_null($this->get($key, null)) )
  297. $this->set($key, $value, $process_special_vars);
  298. }
  299. }
  300. }
  301. /**
  302. * Merges an INI-style file into the registry. Its sections are registry paths,
  303. * keys are appended to the section-defined paths and then set equal to the
  304. * values. If noOverride is set, only non set or null values will be applied.
  305. * Sections beginning with an underscore will be ignored.
  306. * @param string $inifile The full path to the INI file to load
  307. * @param bool $noOverride [optional] Do not override pre-set values.
  308. * @return bool True on success
  309. */
  310. public function mergeINI($inifile, $noOverride = false)
  311. {
  312. if(!file_exists($inifile)) return false;
  313. $inidata = AEUtilINI::parse_ini_file($inifile, true);
  314. foreach($inidata as $rootkey => $rootvalue)
  315. {
  316. if(!is_array($rootvalue))
  317. {
  318. if(!$noOverride)
  319. {
  320. $this->set($rootkey, $rootvalue);
  321. }
  322. elseif( is_null($this->get($rootkey, null)) )
  323. {
  324. $this->set($rootkey, $rootvalue);
  325. }
  326. }
  327. elseif( substr($rootkey,0,1) != '_' )
  328. {
  329. foreach($rootvalue as $key => $value)
  330. {
  331. if(!$noOverride)
  332. {
  333. $this->set($rootkey.'.'.$key, $rootvalue);
  334. }
  335. elseif( is_null($this->get($rootkey.'.'.$key, null)) )
  336. {
  337. $this->set($rootkey.'.'.$key, $rootvalue);
  338. }
  339. }
  340. }
  341. }
  342. return true;
  343. }
  344. /**
  345. * Merges an engine INI file to the configuration. Each section defines a full
  346. * registry path (section.subsection.key). It searches each section for the
  347. * key named "default" and merges its value to the configuration. The other keys
  348. * are simply ignored.
  349. * @param string $inifile The absolute path to an INI file
  350. * @param bool $noOverride [optional] If true, values from the INI will not override the configuration
  351. * @return bool True on success
  352. */
  353. public function mergeEngineINI($inifile, $noOverride = false)
  354. {
  355. if(!file_exists($inifile)) return false;
  356. $inidata = AEUtilINI::parse_ini_file($inifile, true);
  357. foreach($inidata as $section => $nodes) {
  358. if(is_array($nodes)) {
  359. if( substr($section,0,1) != '_' ) {
  360. // Is this a protected node?
  361. $protected = false;
  362. if(array_key_exists('protected', $nodes)) {
  363. $protected = $nodes['protected'];
  364. }
  365. // If overrides are allowed, unprotect until we can set the value
  366. if(!$noOverride) {
  367. if(in_array($section, $this->protected_nodes)) {
  368. $pnk = array_search($section, $this->protected_nodes);
  369. unset($this->protected_nodes[$pnk]);
  370. }
  371. }
  372. if(array_key_exists('remove', $nodes)) {
  373. // Remove a node if it has "remove" set
  374. $this->remove($section);
  375. } elseif(isset($nodes['default'])) {
  376. if(!$noOverride) {
  377. // Update the default value if No Override is set
  378. $this->set($section, $nodes['default']);
  379. } elseif( is_null($this->get($section, null)) ) {
  380. // Set the default value if it does not exist
  381. $this->set($section, $nodes['default']);
  382. }
  383. }
  384. // Finally, if it's a protected node, enable the protection
  385. if($protected) {
  386. $this->protected_nodes[] = $section;
  387. } else {
  388. $idx = array_search($section, $this->protected_nodes);
  389. if($idx !== false) {
  390. unset($this->protected_nodes[$idx]);
  391. }
  392. }
  393. }
  394. }
  395. }
  396. return true;
  397. }
  398. /**
  399. * Exports the current registry snapshot as an INI file. Each namespace is
  400. * placed in a section of its own.
  401. * @param bool $dump_global Set to true to dump the "global" namespace, false to dump everything EXCEPT the [global] namespace
  402. * @return string INI representation of the registry
  403. */
  404. public function exportAsINI()
  405. {
  406. $inidata = '';
  407. $namespaces = $this->getNameSpaces();
  408. foreach($namespaces as $namespace)
  409. {
  410. $inidata .= "[$namespace]\n";
  411. $ns = $this->registry[$namespace]['data'];
  412. $inidata .= $this->dumpObject($ns);
  413. }
  414. return $inidata;
  415. }
  416. /**
  417. * Internal function to dump an object as INI-formatted data
  418. * @param object $object
  419. * @param object $prefix [optional]
  420. * @return
  421. */
  422. private function dumpObject($object, $prefix = '')
  423. {
  424. $data = '';
  425. $vars = get_object_vars($object);
  426. foreach( $vars as $key => $value )
  427. {
  428. if(!is_object($value))
  429. {
  430. if(is_array($value))
  431. {
  432. $value = '###json###'.json_encode($value);
  433. }
  434. $data .= (empty($prefix) ? '' : $prefix.'.').$key.
  435. '="'.addcslashes($value,"\n\r\t\"")."\"\n";
  436. }
  437. else
  438. {
  439. $data .= $this->dumpObject($value, (empty($prefix) ? '' : $prefix.'.').$key );
  440. }
  441. }
  442. return $data;
  443. }
  444. /**
  445. * Sets the protection status for a specific configuration key
  446. * @param string|array $node
  447. * @param bool $protect
  448. */
  449. public function setKeyProtection($node, $protect = false)
  450. {
  451. if(is_array($node)) {
  452. foreach($node as $k) {
  453. $this->setKeyProtection($k, $protect);
  454. }
  455. } elseif(is_string($node)) {
  456. if(is_array($this->protected_nodes)) {
  457. $protected = in_array($node, $this->protected_nodes);
  458. } else {
  459. $this->protected_nodes = array();
  460. $protected = false;
  461. }
  462. if($protect) {
  463. if(!$protected) {
  464. $this->protected_nodes[] = $node;
  465. }
  466. } else {
  467. if($protected) {
  468. $pnk = array_search($section, $this->protected_nodes);
  469. unset($this->protected_nodes[$pnk]);
  470. }
  471. }
  472. }
  473. }
  474. public function getProtectedKeys()
  475. {
  476. return $this->protected_nodes;
  477. }
  478. public function resetProtectedKeys()
  479. {
  480. $this->protected_nodes = array();
  481. }
  482. public function setProtectedKeys($keys)
  483. {
  484. $this->protected_nodes = $keys;
  485. }
  486. }
  487. ?>