PageRenderTime 57ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Core/Model/Config.php

https://bitbucket.org/sunil_nextbits/magento2
PHP | 1635 lines | 903 code | 147 blank | 585 comment | 143 complexity | 63a979dfe245a76e8381463912a79a2a MD5 | raw file
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Core
  23. * @copyright Copyright (c) 2012 X.commerce, Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Core configuration class
  28. *
  29. * @category Mage
  30. * @package Mage_Core
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Core_Model_Config extends Mage_Core_Model_Config_Base
  34. {
  35. const CACHE_TAG = 'CONFIG';
  36. /**
  37. * Flag which allow use cache logic
  38. *
  39. * @var bool
  40. */
  41. protected $_useCache = false;
  42. /**
  43. * Instructions for spitting config cache
  44. * array(
  45. * $sectionName => $recursionLevel
  46. * )
  47. * Recursion level provide availability cache subnodes separatly
  48. *
  49. * @var array
  50. */
  51. protected $_cacheSections = array(
  52. 'admin' => 0,
  53. 'adminhtml' => 0,
  54. 'crontab' => 0,
  55. 'install' => 0,
  56. 'stores' => 1,
  57. 'websites' => 0
  58. );
  59. /**
  60. * Loaded Configuration by cached sections
  61. *
  62. * @var array
  63. */
  64. protected $_cacheLoadedSections = array();
  65. /**
  66. * Configuration options
  67. *
  68. * @var Mage_Core_Model_Config_Options
  69. */
  70. protected $_options;
  71. /**
  72. * Storage for generated class names
  73. *
  74. * @var array
  75. */
  76. protected $_classNameCache = array();
  77. /**
  78. * Storage for generated block class names
  79. *
  80. * @var unknown_type
  81. */
  82. protected $_blockClassNameCache = array();
  83. /**
  84. * Storage of validated secure urls
  85. *
  86. * @var array
  87. */
  88. protected $_secureUrlCache = array();
  89. /**
  90. * System environment server variables
  91. *
  92. * @var array
  93. */
  94. protected $_distroServerVars;
  95. /**
  96. * Array which is using for replace placeholders of server variables
  97. *
  98. * @var array
  99. */
  100. protected $_substServerVars;
  101. /**
  102. * Resource model
  103. * Used for operations with DB
  104. *
  105. * @var Mage_Core_Model_Resource_Config
  106. */
  107. protected $_resourceModel;
  108. /**
  109. * Configuration for events by area
  110. *
  111. * @var array
  112. */
  113. protected $_eventAreas;
  114. /**
  115. * Flag cache for existing or already created directories
  116. *
  117. * @var array
  118. */
  119. protected $_dirExists = array();
  120. /**
  121. * Flach which allow using cache for config initialization
  122. *
  123. * @var bool
  124. */
  125. protected $_allowCacheForInit = true;
  126. /**
  127. * Property used during cache save process
  128. *
  129. * @var array
  130. */
  131. protected $_cachePartsForSave = array();
  132. /**
  133. * Empty configuration object for loading and megring configuration parts
  134. *
  135. * @var Mage_Core_Model_Config_Base
  136. */
  137. protected $_prototype;
  138. /**
  139. * Flag which identify what local configuration is loaded
  140. *
  141. * @var bool
  142. */
  143. protected $_isLocalConfigLoaded = false;
  144. /**
  145. * Active modules array per namespace
  146. * @var array
  147. */
  148. private $_moduleNamespaces = null;
  149. /**
  150. * Modules allowed to load
  151. * If empty - all modules are allowed
  152. *
  153. * @var array
  154. */
  155. protected $_allowedModules = array();
  156. /**
  157. * Areas allowed to use
  158. *
  159. * @var array
  160. */
  161. protected $_allowedAreas = null;
  162. /**
  163. * Paths to module's directories (etc, sql, locale etc)
  164. *
  165. * @var array
  166. */
  167. protected $_moduleDirs = array();
  168. /*
  169. * Cache for declared modules to prevent loading modules' config twice
  170. *
  171. * @var array
  172. */
  173. protected $_modulesCache = array();
  174. /**
  175. * Current area code
  176. *
  177. * @var string
  178. */
  179. protected $_currentAreaCode = null;
  180. /**
  181. * Object manager
  182. *
  183. * @var Magento_ObjectManager
  184. */
  185. protected $_objectManager;
  186. /**
  187. * Class construct
  188. *
  189. * @param Magento_ObjectManager $objectManager
  190. * @param mixed $sourceData
  191. */
  192. public function __construct(Magento_ObjectManager $objectManager, $sourceData=null)
  193. {
  194. $this->_objectManager = $objectManager;
  195. $this->setCacheId('config_global');
  196. $options = $sourceData;
  197. if (!is_array($options)) {
  198. $options = array($options);
  199. }
  200. $this->_options = $this->_objectManager->create('Mage_Core_Model_Config_Options', array('data' => $options));
  201. $this->_prototype = $this->_objectManager->create('Mage_Core_Model_Config_Base');
  202. $this->_cacheChecksum = null;
  203. parent::__construct($sourceData);
  204. }
  205. /**
  206. * Get config resource model
  207. *
  208. * @return Mage_Core_Model_Resource_Config
  209. */
  210. public function getResourceModel()
  211. {
  212. if (is_null($this->_resourceModel)) {
  213. $this->_resourceModel = Mage::getResourceModel('Mage_Core_Model_Resource_Config');
  214. }
  215. return $this->_resourceModel;
  216. }
  217. /**
  218. * Get configuration options object
  219. *
  220. * @return Mage_Core_Model_Config_Options
  221. */
  222. public function getOptions()
  223. {
  224. return $this->_options;
  225. }
  226. /**
  227. * Set configuration options
  228. *
  229. * @param array $options
  230. * @return Mage_Core_Model_Config
  231. */
  232. public function setOptions($options)
  233. {
  234. if (is_array($options)) {
  235. $this->getOptions()->addData($options);
  236. }
  237. return $this;
  238. }
  239. /**
  240. * Initialization of core configuration
  241. *
  242. * @return Mage_Core_Model_Config
  243. */
  244. public function init($options=array())
  245. {
  246. $this->setCacheChecksum(null);
  247. $this->_cacheLoadedSections = array();
  248. $this->setOptions($options);
  249. $this->loadBase();
  250. $cacheLoad = $this->loadModulesCache();
  251. if ($cacheLoad) {
  252. return $this;
  253. }
  254. $this->loadModules();
  255. $this->loadDb();
  256. $this->loadLocales();
  257. $this->saveCache();
  258. return $this;
  259. }
  260. /**
  261. * Load base system configuration (config.xml and local.xml files)
  262. *
  263. * @return Mage_Core_Model_Config
  264. */
  265. public function loadBase()
  266. {
  267. $etcDir = $this->getOptions()->getEtcDir();
  268. $files = array();
  269. $deferred = array();
  270. foreach (scandir($etcDir) as $filename) {
  271. if ('.' == $filename || '..' == $filename || '.xml' != substr($filename, -4)) {
  272. continue;
  273. }
  274. $file = "{$etcDir}/{$filename}";
  275. if ('local.xml' === $filename) {
  276. $deferred[] = $file;
  277. $this->_isLocalConfigLoaded = true;
  278. $localConfig = $this->getOptions()->getData('local_config');
  279. if (preg_match('/^[a-z\d_-]+\/[a-z\d_-]+\.xml$/', $localConfig)) {
  280. $deferred[] = "{$etcDir}/$localConfig";
  281. }
  282. } else {
  283. $files[] = $file;
  284. }
  285. }
  286. $files = array_merge($files, $deferred);
  287. $this->loadFile(current($files));
  288. array_shift($files);
  289. foreach ($files as $file) {
  290. $merge = clone $this->_prototype;
  291. $merge->loadFile($file);
  292. $this->extend($merge);
  293. }
  294. return $this;
  295. }
  296. /**
  297. * Load locale configuration from locale configuration files
  298. *
  299. * @return Mage_Core_Model_Config
  300. */
  301. public function loadLocales()
  302. {
  303. $localeDir = $this->getOptions()->getLocaleDir();
  304. $files = glob($localeDir . DS . '*' . DS . 'config.xml');
  305. if (is_array($files) && !empty($files)) {
  306. foreach ($files as $file) {
  307. $merge = clone $this->_prototype;
  308. $merge->loadFile($file);
  309. $this->extend($merge);
  310. }
  311. }
  312. return $this;
  313. }
  314. /**
  315. * Load cached modules and locale configuration
  316. *
  317. * @return bool
  318. */
  319. public function loadModulesCache()
  320. {
  321. if (Mage::isInstalled(array('etc_dir' => $this->getOptions()->getEtcDir()))) {
  322. if ($this->_canUseCacheForInit()) {
  323. Magento_Profiler::start('init_modules_config_cache');
  324. $loaded = $this->loadCache();
  325. Magento_Profiler::stop('init_modules_config_cache');
  326. if ($loaded) {
  327. $this->_useCache = true;
  328. return true;
  329. }
  330. }
  331. }
  332. return false;
  333. }
  334. /**
  335. * Load modules configuration
  336. *
  337. * @return Mage_Core_Model_Config
  338. */
  339. public function loadModules()
  340. {
  341. Magento_Profiler::start('config');
  342. Magento_Profiler::start('load_modules');
  343. $this->_loadDeclaredModules();
  344. Magento_Profiler::start('load_modules_configuration');
  345. $resourceConfig = sprintf('config.%s.xml', $this->getResourceConnectionModel('core'));
  346. $this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
  347. Magento_Profiler::stop('load_modules_configuration');
  348. /**
  349. * Prevent local.xml directives overwriting
  350. */
  351. $mergeConfig = clone $this->_prototype;
  352. $this->_isLocalConfigLoaded = $mergeConfig->loadFile($this->getOptions()->getEtcDir() . DS . 'local.xml');
  353. if ($this->_isLocalConfigLoaded) {
  354. $this->extend($mergeConfig);
  355. }
  356. $this->applyExtends();
  357. Magento_Profiler::stop('load_modules');
  358. Magento_Profiler::stop('config');
  359. return $this;
  360. }
  361. /**
  362. * Check if local configuration (DB connection, etc) is loaded
  363. *
  364. * @return bool
  365. */
  366. public function isLocalConfigLoaded()
  367. {
  368. return $this->_isLocalConfigLoaded;
  369. }
  370. /**
  371. * Load config data from DB
  372. *
  373. * @return Mage_Core_Model_Config
  374. */
  375. public function loadDb()
  376. {
  377. Magento_Profiler::start('config');
  378. if ($this->_isLocalConfigLoaded && Mage::isInstalled()) {
  379. Magento_Profiler::start('load_db');
  380. $dbConf = $this->getResourceModel();
  381. $dbConf->loadToXml($this);
  382. Magento_Profiler::stop('load_db');
  383. }
  384. Magento_Profiler::stop('config');
  385. return $this;
  386. }
  387. /**
  388. * Reinitialize configuration
  389. *
  390. * @param array $options
  391. * @return Mage_Core_Model_Config
  392. */
  393. public function reinit($options = array())
  394. {
  395. $this->_allowCacheForInit = false;
  396. $this->_useCache = false;
  397. return $this->init($options);
  398. }
  399. /**
  400. * Check if cache can be used for config initialization
  401. *
  402. * @return bool
  403. */
  404. protected function _canUseCacheForInit()
  405. {
  406. return Mage::app()->useCache('config') && $this->_allowCacheForInit
  407. && !$this->_loadCache($this->_getCacheLockId());
  408. }
  409. /**
  410. * Retrieve cache object
  411. *
  412. * @return Zend_Cache_Frontend_File
  413. */
  414. public function getCache()
  415. {
  416. return Mage::app()->getCache();
  417. }
  418. /**
  419. * Get lock flag cache identifier
  420. *
  421. * @return string
  422. */
  423. protected function _getCacheLockId()
  424. {
  425. return $this->getCacheId().'.lock';
  426. }
  427. /**
  428. * Save configuration cache
  429. *
  430. * @param array $tags cache tags
  431. * @return Mage_Core_Model_Config
  432. */
  433. public function saveCache($tags=array())
  434. {
  435. if (!Mage::app()->useCache('config')) {
  436. return $this;
  437. }
  438. if (!in_array(self::CACHE_TAG, $tags)) {
  439. $tags[] = self::CACHE_TAG;
  440. }
  441. $cacheLockId = $this->_getCacheLockId();
  442. if ($this->_loadCache($cacheLockId)) {
  443. return $this;
  444. }
  445. if (!empty($this->_cacheSections)) {
  446. $xml = clone $this->_xml;
  447. foreach ($this->_cacheSections as $sectionName => $level) {
  448. $this->_saveSectionCache($this->getCacheId(), $sectionName, $xml, $level, $tags);
  449. unset($xml->$sectionName);
  450. }
  451. $this->_cachePartsForSave[$this->getCacheId()] = $xml->asNiceXml('', false);
  452. } else {
  453. return parent::saveCache($tags);
  454. }
  455. $this->_saveCache(time(), $cacheLockId, array(), 60);
  456. $this->removeCache();
  457. foreach ($this->_cachePartsForSave as $cacheId => $cacheData) {
  458. $this->_saveCache($cacheData, $cacheId, $tags, $this->getCacheLifetime());
  459. }
  460. unset($this->_cachePartsForSave);
  461. $this->_removeCache($cacheLockId);
  462. return $this;
  463. }
  464. /**
  465. * Save cache of specified
  466. *
  467. * @param string $idPrefix cache id prefix
  468. * @param string $sectionName
  469. * @param Varien_Simplexml_Element $source
  470. * @param int $recursionLevel
  471. * @return Mage_Core_Model_Config
  472. */
  473. protected function _saveSectionCache($idPrefix, $sectionName, $source, $recursionLevel=0, $tags=array())
  474. {
  475. if ($source && $source->$sectionName) {
  476. $cacheId = $idPrefix . '_' . $sectionName;
  477. if ($recursionLevel > 0) {
  478. foreach ($source->$sectionName->children() as $subSectionName => $node) {
  479. $this->_saveSectionCache(
  480. $cacheId, $subSectionName, $source->$sectionName, $recursionLevel-1, $tags
  481. );
  482. }
  483. }
  484. $this->_cachePartsForSave[$cacheId] = $source->$sectionName->asNiceXml('', false);
  485. }
  486. return $this;
  487. }
  488. /**
  489. * Load config section cached data
  490. *
  491. * @param string $sectionName
  492. * @return Varien_Simplexml_Element
  493. */
  494. protected function _loadSectionCache($sectionName)
  495. {
  496. $cacheId = $this->getCacheId() . '_' . $sectionName;
  497. $xmlString = $this->_loadCache($cacheId);
  498. /**
  499. * If we can't load section cache (problems with cache storage)
  500. */
  501. if (!$xmlString) {
  502. $this->_useCache = false;
  503. $this->reinit($this->_options);
  504. return false;
  505. } else {
  506. $xml = simplexml_load_string($xmlString, $this->_elementClass);
  507. return $xml;
  508. }
  509. }
  510. /**
  511. * Load cached data by identifier
  512. *
  513. * @param string $id
  514. * @return string
  515. */
  516. protected function _loadCache($id)
  517. {
  518. return Mage::app()->loadCache($id);
  519. }
  520. /**
  521. * Save cache data
  522. *
  523. * @param string $data
  524. * @param string $id
  525. * @param array $tags
  526. * @param false|int $lifetime
  527. * @return Mage_Core_Model_Config
  528. */
  529. protected function _saveCache($data, $id, $tags=array(), $lifetime=false)
  530. {
  531. return Mage::app()->saveCache($data, $id, $tags, $lifetime);
  532. }
  533. /**
  534. * Clear cache data by id
  535. *
  536. * @param string $id
  537. * @return Mage_Core_Model_Config
  538. */
  539. protected function _removeCache($id)
  540. {
  541. return Mage::app()->removeCache($id);
  542. }
  543. /**
  544. * Remove configuration cache
  545. *
  546. * @return Mage_Core_Model_Config
  547. */
  548. public function removeCache()
  549. {
  550. Mage::app()->cleanCache(array(self::CACHE_TAG));
  551. return parent::removeCache();
  552. }
  553. /**
  554. * Configuration cache clean process
  555. *
  556. * @return Mage_Core_Model_Config
  557. */
  558. public function cleanCache()
  559. {
  560. return $this->reinit();
  561. }
  562. /**
  563. * Getter for section configuration object
  564. *
  565. * @param array $path
  566. * @return Mage_Core_Model_Config_Element
  567. */
  568. protected function _getSectionConfig($path)
  569. {
  570. $section = $path[0];
  571. if (!isset($this->_cacheSections[$section])) {
  572. return false;
  573. }
  574. $sectionPath = array_slice($path, 0, $this->_cacheSections[$section]+1);
  575. $sectionKey = implode('_', $sectionPath);
  576. if (!isset($this->_cacheLoadedSections[$sectionKey])) {
  577. Magento_Profiler::start('init_config_section:' . $sectionKey);
  578. $this->_cacheLoadedSections[$sectionKey] = $this->_loadSectionCache($sectionKey);
  579. Magento_Profiler::stop('init_config_section:' . $sectionKey);
  580. }
  581. if ($this->_cacheLoadedSections[$sectionKey] === false) {
  582. return false;
  583. }
  584. return $this->_cacheLoadedSections[$sectionKey];
  585. }
  586. /**
  587. * Get node value from cached section data
  588. *
  589. * @param array $path
  590. * @return Mage_Core_Model_Config
  591. */
  592. public function getSectionNode($path)
  593. {
  594. $section = $path[0];
  595. $config = $this->_getSectionConfig($path);
  596. $path = array_slice($path, $this->_cacheSections[$section] + 1);
  597. if ($config) {
  598. return $config->descend($path);
  599. }
  600. return false;
  601. }
  602. /**
  603. * Returns node found by the $path and scope info
  604. *
  605. * @param string $path
  606. * @param string $scope
  607. * @param string|int $scopeCode
  608. * @return Mage_Core_Model_Config_Element
  609. */
  610. public function getNode($path=null, $scope='', $scopeCode=null)
  611. {
  612. if ($scope !== '') {
  613. if (('store' === $scope) || ('website' === $scope)) {
  614. $scope .= 's';
  615. }
  616. if (('default' !== $scope) && is_int($scopeCode)) {
  617. if ('stores' == $scope) {
  618. $scopeCode = Mage::app()->getStore($scopeCode)->getCode();
  619. } elseif ('websites' == $scope) {
  620. $scopeCode = Mage::app()->getWebsite($scopeCode)->getCode();
  621. } else {
  622. Mage::throwException(Mage::helper('Mage_Core_Helper_Data')->__('Unknown scope "%s".', $scope));
  623. }
  624. }
  625. $path = $scope . ($scopeCode ? '/' . $scopeCode : '' ) . (empty($path) ? '' : '/' . $path);
  626. }
  627. /**
  628. * Check path cache loading
  629. */
  630. if ($this->_useCache && ($path !== null)) {
  631. $path = explode('/', $path);
  632. $section= $path[0];
  633. if (isset($this->_cacheSections[$section])) {
  634. $res = $this->getSectionNode($path);
  635. if ($res !== false) {
  636. return $res;
  637. }
  638. }
  639. }
  640. return parent::getNode($path);
  641. }
  642. /**
  643. * Create node by $path and set its value.
  644. *
  645. * @param string $path separated by slashes
  646. * @param string $value
  647. * @param bool $overwrite
  648. * @return Varien_Simplexml_Config
  649. */
  650. public function setNode($path, $value, $overwrite = true)
  651. {
  652. if ($this->_useCache && ($path !== null)) {
  653. $sectionPath = explode('/', $path);
  654. $config = $this->_getSectionConfig($sectionPath);
  655. if ($config) {
  656. $sectionPath = array_slice($sectionPath, $this->_cacheSections[$sectionPath[0]]+1);
  657. $sectionPath = implode('/', $sectionPath);
  658. $config->setNode($sectionPath, $value, $overwrite);
  659. }
  660. }
  661. return parent::setNode($path, $value, $overwrite);
  662. }
  663. /**
  664. * Retrive Declared Module file list
  665. *
  666. * @return array
  667. */
  668. protected function _getDeclaredModuleFiles()
  669. {
  670. $codeDir = $this->getOptions()->getCodeDir();
  671. $moduleFiles = glob($codeDir . DS . '*' . DS . '*' . DS . '*' . DS . 'etc' . DS . 'config.xml');
  672. if (!$moduleFiles) {
  673. return false;
  674. }
  675. $collectModuleFiles = array(
  676. 'base' => array(),
  677. 'mage' => array(),
  678. 'custom' => array()
  679. );
  680. foreach ($moduleFiles as $v) {
  681. $name = explode(DIRECTORY_SEPARATOR, $v);
  682. $collection = $name[count($name) - 4];
  683. if ($collection == 'Mage') {
  684. $collectModuleFiles['mage'][] = $v;
  685. } else {
  686. $collectModuleFiles['custom'][] = $v;
  687. }
  688. }
  689. $etcDir = $this->getOptions()->getEtcDir();
  690. $additionalFiles = glob($etcDir . DS . 'modules' . DS . '*.xml');
  691. foreach ($additionalFiles as $v) {
  692. $collectModuleFiles['base'][] = $v;
  693. }
  694. return array_merge(
  695. $collectModuleFiles['mage'],
  696. $collectModuleFiles['custom'],
  697. $collectModuleFiles['base']
  698. );
  699. }
  700. /**
  701. * Add module(s) to allowed list
  702. *
  703. * @param strung|array $module
  704. * @return Mage_Core_Model_Config
  705. */
  706. public function addAllowedModules($module)
  707. {
  708. if (is_array($module)) {
  709. foreach ($module as $moduleName) {
  710. $this->addAllowedModules($moduleName);
  711. }
  712. } elseif (!in_array($module, $this->_allowedModules)) {
  713. $this->_allowedModules[] = $module;
  714. }
  715. return $this;
  716. }
  717. /**
  718. * Load declared modules configuration
  719. *
  720. * @return Mage_Core_Model_Config
  721. */
  722. protected function _loadDeclaredModules()
  723. {
  724. Magento_Profiler::start('load_modules_files');
  725. $moduleFiles = $this->_getDeclaredModuleFiles();
  726. if (!$moduleFiles) {
  727. return $this;
  728. }
  729. Magento_Profiler::stop('load_modules_files');
  730. Magento_Profiler::start('load_modules_declaration');
  731. $unsortedConfig = new Mage_Core_Model_Config_Base('<config/>');
  732. $emptyConfig = new Mage_Core_Model_Config_Element('<config><modules/></config>');
  733. $declaredModules = array();
  734. foreach ($moduleFiles as $oneConfigFile) {
  735. $path = explode(DIRECTORY_SEPARATOR, $oneConfigFile);
  736. $moduleConfig = new Mage_Core_Model_Config_Base($oneConfigFile);
  737. $modules = $moduleConfig->getXpath('modules/*');
  738. if (!$modules) {
  739. continue;
  740. }
  741. $cPath = count($path);
  742. if ($cPath > 4) {
  743. $moduleName = $path[$cPath - 4] . '_' . $path[$cPath - 3];
  744. $this->_modulesCache[$moduleName] = $moduleConfig;
  745. }
  746. foreach ($modules as $module) {
  747. $moduleName = $module->getName();
  748. $isActive = (string)$module->active;
  749. if (isset($declaredModules[$moduleName])) {
  750. $declaredModules[$moduleName]['active'] = $isActive;
  751. continue;
  752. }
  753. $newModule = clone $emptyConfig;
  754. $newModule->modules->appendChild($module);
  755. $declaredModules[$moduleName] = array(
  756. 'active' => $isActive,
  757. 'module' => $newModule,
  758. );
  759. }
  760. }
  761. foreach ($declaredModules as $moduleName => $module) {
  762. if ($module['active'] == 'true') {
  763. $module['module']->modules->{$moduleName}->active = 'true';
  764. $unsortedConfig->extend(new Mage_Core_Model_Config_Base($module['module']));
  765. }
  766. }
  767. $sortedConfig = new Mage_Core_Model_Config_Module($unsortedConfig, $this->_allowedModules);
  768. $this->extend($sortedConfig);
  769. Magento_Profiler::stop('load_modules_declaration');
  770. return $this;
  771. }
  772. /**
  773. * Determine whether provided name begins from any available modules, according to namespaces priority
  774. * If matched, returns as the matched module "factory" name or a fully qualified module name
  775. *
  776. * @param string $name
  777. * @param bool $asFullModuleName
  778. * @return string
  779. */
  780. public function determineOmittedNamespace($name, $asFullModuleName = false)
  781. {
  782. if (null === $this->_moduleNamespaces) {
  783. $this->_moduleNamespaces = array();
  784. foreach ($this->_xml->xpath('modules/*') as $m) {
  785. if ((string)$m->active == 'true') {
  786. $moduleName = $m->getName();
  787. $module = strtolower($moduleName);
  788. $this->_moduleNamespaces[substr($module, 0, strpos($module, '_'))][$module] = $moduleName;
  789. }
  790. }
  791. }
  792. $name = explode('_', strtolower($name));
  793. $partsNum = count($name);
  794. $defaultNamespaceFlag = false;
  795. foreach ($this->_moduleNamespaces as $namespaceName => $namespace) {
  796. // assume the namespace is omitted (default namespace only, which comes first)
  797. if ($defaultNamespaceFlag === false) {
  798. $defaultNamespaceFlag = true;
  799. $defaultNS = $namespaceName . '_' . $name[0];
  800. if (isset($namespace[$defaultNS])) {
  801. return $asFullModuleName ? $namespace[$defaultNS] : $name[0]; // return omitted as well
  802. }
  803. }
  804. // assume namespace is qualified
  805. if(isset($name[1])) {
  806. $fullNS = $name[0] . '_' . $name[1];
  807. if (2 <= $partsNum && isset($namespace[$fullNS])) {
  808. return $asFullModuleName ? $namespace[$fullNS] : $fullNS;
  809. }
  810. }
  811. }
  812. return '';
  813. }
  814. /**
  815. * Iterate all active modules "etc" folders and combine data from
  816. * specidied xml file name to one object
  817. *
  818. * @param string $fileName
  819. * @param null|Mage_Core_Model_Config_Base $mergeToObject
  820. * @return Mage_Core_Model_Config_Base
  821. */
  822. public function loadModulesConfiguration($fileName, $mergeToObject = null, $mergeModel=null)
  823. {
  824. if ($mergeToObject === null) {
  825. $mergeToObject = clone $this->_prototype;
  826. $mergeToObject->loadString('<config/>');
  827. }
  828. if ($mergeModel === null) {
  829. $mergeModel = clone $this->_prototype;
  830. }
  831. $modules = $this->getNode('modules')->children();
  832. foreach ($modules as $modName=>$module) {
  833. if ($module->is('active')) {
  834. if (!is_array($fileName)) {
  835. $fileName = array($fileName);
  836. }
  837. foreach ($fileName as $configFile) {
  838. if ($configFile == 'config.xml' && isset($this->_modulesCache[$modName])) {
  839. $mergeToObject->extend($this->_modulesCache[$modName], true);
  840. //Prevent overriding <active> node of module if it was redefined in etc/modules
  841. $mergeToObject->extend(new Mage_Core_Model_Config_Base(
  842. "<config><modules><{$modName}><active>true</active></{$modName}></modules></config>"),
  843. true
  844. );
  845. } else {
  846. $configFilePath = $this->getModuleDir('etc', $modName) . DS . $configFile;
  847. if ($mergeModel->loadFile($configFilePath)) {
  848. $mergeToObject->extend($mergeModel, true);
  849. }
  850. }
  851. }
  852. }
  853. }
  854. unset($this->_modulesCache);
  855. return $mergeToObject;
  856. }
  857. /**
  858. * Go through all modules and find configuration files of active modules
  859. *
  860. * @param string $filename
  861. * @return array
  862. */
  863. public function getModuleConfigurationFiles($filename)
  864. {
  865. $result = array();
  866. $modules = $this->getNode('modules')->children();
  867. foreach ($modules as $moduleName => $module) {
  868. if ((!$module->is('active'))) {
  869. continue;
  870. }
  871. $file = $this->getModuleDir('etc', $moduleName) . DIRECTORY_SEPARATOR . $filename;
  872. if (file_exists($file)) {
  873. $result[] = $file;
  874. }
  875. }
  876. return $result;
  877. }
  878. /**
  879. * Retrieve temporary directory path
  880. *
  881. * @return string
  882. */
  883. public function getTempVarDir()
  884. {
  885. return $this->getOptions()->getVarDir();
  886. }
  887. /**
  888. * Get default server variables values
  889. *
  890. * @return array
  891. */
  892. public function getDistroServerVars()
  893. {
  894. if (!$this->_distroServerVars) {
  895. if (isset($_SERVER['SCRIPT_NAME']) && isset($_SERVER['HTTP_HOST'])) {
  896. $secure = (!empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] != 'off'))
  897. || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443');
  898. $scheme = ($secure ? 'https' : 'http') . '://' ;
  899. $hostArr = explode(':', $_SERVER['HTTP_HOST']);
  900. $host = $hostArr[0];
  901. $port = isset(
  902. $hostArr[1]) && (!$secure && $hostArr[1]!=80 || $secure && $hostArr[1]!=443
  903. ) ? ':'.$hostArr[1] : '';
  904. $path = Mage::app()->getRequest()->getBasePath();
  905. $baseUrl = $scheme.$host.$port.rtrim($path, '/').'/';
  906. } else {
  907. $baseUrl = 'http://localhost/';
  908. }
  909. $options = $this->getOptions();
  910. $this->_distroServerVars = array(
  911. 'root_dir' => $options->getBaseDir(),
  912. 'app_dir' => $options->getAppDir(),
  913. 'var_dir' => $options->getVarDir(),
  914. 'base_url' => $baseUrl,
  915. );
  916. foreach ($this->_distroServerVars as $k=>$v) {
  917. $this->_substServerVars['{{'.$k.'}}'] = $v;
  918. }
  919. }
  920. return $this->_distroServerVars;
  921. }
  922. public function substDistroServerVars($data)
  923. {
  924. $this->getDistroServerVars();
  925. return str_replace(
  926. array_keys($this->_substServerVars),
  927. array_values($this->_substServerVars),
  928. $data
  929. );
  930. }
  931. /**
  932. * Get module config node
  933. *
  934. * @param string $moduleName
  935. * @return Varien_Simplexml_Object
  936. */
  937. function getModuleConfig($moduleName='')
  938. {
  939. $modules = $this->getNode('modules');
  940. if (''===$moduleName) {
  941. return $modules;
  942. } else {
  943. return $modules->$moduleName;
  944. }
  945. }
  946. /**
  947. * Get module setup class instance.
  948. *
  949. * Defaults to Mage_Core_Setup
  950. *
  951. * @param string|Varien_Simplexml_Object $module
  952. * @return object
  953. */
  954. function getModuleSetup($module='')
  955. {
  956. $className = 'Mage_Core_Setup';
  957. if (''!==$module) {
  958. if (is_string($module)) {
  959. $module = $this->getModuleConfig($module);
  960. }
  961. if (isset($module->setup)) {
  962. $moduleClassName = $module->setup->getClassName();
  963. if (!empty($moduleClassName)) {
  964. $className = $moduleClassName;
  965. }
  966. }
  967. }
  968. return new $className($module);
  969. }
  970. /**
  971. * Get temporary data directory name
  972. *
  973. * @param string $path
  974. * @param string $type
  975. * @return string
  976. */
  977. public function getVarDir($path=null, $type='var')
  978. {
  979. $dir = Mage::getBaseDir($type).($path!==null ? DS.$path : '');
  980. if (!$this->createDirIfNotExists($dir)) {
  981. return false;
  982. }
  983. return $dir;
  984. }
  985. public function createDirIfNotExists($dir)
  986. {
  987. return $this->getOptions()->createDirIfNotExists($dir);
  988. }
  989. /**
  990. * Get module directory by directory type
  991. *
  992. * @param string $type
  993. * @param string $moduleName
  994. * @return string
  995. */
  996. public function getModuleDir($type, $moduleName)
  997. {
  998. if (isset($this->_moduleDirs[$moduleName][$type])) {
  999. return $this->_moduleDirs[$moduleName][$type];
  1000. }
  1001. $codePool = (string)$this->getModuleConfig($moduleName)->codePool;
  1002. $dir = $this->getOptions()->getCodeDir() . DS . $codePool . DS . uc_words($moduleName, DS);
  1003. switch ($type) {
  1004. case 'etc':
  1005. case 'controllers':
  1006. case 'sql':
  1007. case 'data':
  1008. case 'locale':
  1009. case 'view':
  1010. $dir .= DS . $type;
  1011. break;
  1012. }
  1013. $dir = str_replace('/', DS, $dir);
  1014. return $dir;
  1015. }
  1016. /**
  1017. * Set path to the corresponding module directory
  1018. *
  1019. * @param string $moduleName
  1020. * @param string $type directory type (etc, controllers, locale etc)
  1021. * @param string $path
  1022. * @return Mage_Core_Model_Config
  1023. */
  1024. public function setModuleDir($moduleName, $type, $path)
  1025. {
  1026. if (!isset($this->_moduleDirs[$moduleName])) {
  1027. $this->_moduleDirs[$moduleName] = array();
  1028. }
  1029. $this->_moduleDirs[$moduleName][$type] = $path;
  1030. return $this;
  1031. }
  1032. /**
  1033. * Load event observers for an area (front, admin)
  1034. *
  1035. * @param string $area
  1036. * @return boolean
  1037. */
  1038. public function loadEventObservers($area)
  1039. {
  1040. $events = $this->getNode("$area/events");
  1041. if ($events) {
  1042. $events = $events->children();
  1043. } else {
  1044. return false;
  1045. }
  1046. foreach ($events as $event) {
  1047. $eventName = $event->getName();
  1048. $observers = $event->observers->children();
  1049. foreach ($observers as $observer) {
  1050. switch ((string)$observer->type) {
  1051. case 'singleton':
  1052. $callback = array(
  1053. Mage::getSingleton((string)$observer->class),
  1054. (string)$observer->method
  1055. );
  1056. break;
  1057. case 'object':
  1058. case 'model':
  1059. $callback = array(
  1060. Mage::getModel((string)$observer->class),
  1061. (string)$observer->method
  1062. );
  1063. break;
  1064. default:
  1065. $callback = array($observer->getClassName(), (string)$observer->method);
  1066. break;
  1067. }
  1068. $args = (array)$observer->args;
  1069. $observerClass = $observer->observer_class ? (string)$observer->observer_class : '';
  1070. Mage::addObserver($eventName, $callback, $args, $observer->getName(), $observerClass);
  1071. }
  1072. }
  1073. return true;
  1074. }
  1075. /**
  1076. * Get standard path variables.
  1077. *
  1078. * To be used in blocks, templates, etc.
  1079. *
  1080. * @param array|string $args Module name if string
  1081. * @return array
  1082. */
  1083. public function getPathVars($args=null)
  1084. {
  1085. $path = array();
  1086. $path['baseUrl'] = Mage::getBaseUrl();
  1087. $path['baseSecureUrl'] = Mage::getBaseUrl('link', true);
  1088. return $path;
  1089. }
  1090. /**
  1091. * Check rewrite section and apply rewrites to $className, if any
  1092. *
  1093. * @param string $className
  1094. * @return string
  1095. */
  1096. protected function _applyClassRewrites($className)
  1097. {
  1098. if (!isset($this->_classNameCache[$className])) {
  1099. if (isset($this->_xml->global->rewrites->$className)) {
  1100. $className = (string) $this->_xml->global->rewrites->$className;
  1101. }
  1102. $this->_classNameCache[$className] = $className;
  1103. }
  1104. return $this->_classNameCache[$className];
  1105. }
  1106. /**
  1107. * Retrieve block class name
  1108. *
  1109. * @param string $blockClass
  1110. * @return string
  1111. */
  1112. public function getBlockClassName($blockClass)
  1113. {
  1114. return $this->getModelClassName($blockClass);
  1115. }
  1116. /**
  1117. * Retrieve helper class name
  1118. *
  1119. * @param string $helperClass
  1120. * @return string
  1121. */
  1122. public function getHelperClassName($helperClass)
  1123. {
  1124. return $this->getModelClassName($helperClass);
  1125. }
  1126. /**
  1127. * Retrieve module class name
  1128. *
  1129. * @param string $modelClass
  1130. * @return string
  1131. */
  1132. public function getModelClassName($modelClass)
  1133. {
  1134. return $this->_applyClassRewrites($modelClass);
  1135. }
  1136. /**
  1137. * Get model class instance.
  1138. *
  1139. * Example:
  1140. * $config->getModelInstance('catalog/product')
  1141. *
  1142. * Will instantiate Mage_Catalog_Model_Resource_Product
  1143. *
  1144. * @param string $modelClass
  1145. * @param array|object $constructArguments
  1146. * @return Mage_Core_Model_Abstract|false
  1147. */
  1148. public function getModelInstance($modelClass='', $constructArguments=array())
  1149. {
  1150. $className = $this->getModelClassName($modelClass);
  1151. if (class_exists($className)) {
  1152. Magento_Profiler::start('FACTORY:' . $className);
  1153. $obj = $this->_objectManager->create($className, $constructArguments);
  1154. Magento_Profiler::stop('FACTORY:' . $className);
  1155. return $obj;
  1156. } else {
  1157. return false;
  1158. }
  1159. }
  1160. /**
  1161. * Get resource model object by alias
  1162. *
  1163. * @param string $modelClass
  1164. * @param array $constructArguments
  1165. * @return object
  1166. */
  1167. public function getResourceModelInstance($modelClass='', $constructArguments=array())
  1168. {
  1169. return $this->getModelInstance($modelClass, $constructArguments);
  1170. }
  1171. /**
  1172. * Get resource configuration for resource name
  1173. *
  1174. * @param string $name
  1175. * @return Varien_Simplexml_Object
  1176. */
  1177. public function getResourceConfig($name)
  1178. {
  1179. return $this->_xml->global->resources->{$name};
  1180. }
  1181. /**
  1182. * Get connection configuration
  1183. *
  1184. * @param string $name
  1185. * @return Varien_Simplexml_Element
  1186. */
  1187. public function getResourceConnectionConfig($name)
  1188. {
  1189. $config = $this->getResourceConfig($name);
  1190. if ($config) {
  1191. $conn = $config->connection;
  1192. if ($conn) {
  1193. if (!empty($conn->use)) {
  1194. return $this->getResourceConnectionConfig((string)$conn->use);
  1195. } else {
  1196. return $conn;
  1197. }
  1198. }
  1199. }
  1200. return false;
  1201. }
  1202. /**
  1203. * Retrieve resource type configuration for resource name
  1204. *
  1205. * @param string $type
  1206. * @return Varien_Simplexml_Object
  1207. */
  1208. public function getResourceTypeConfig($type)
  1209. {
  1210. return $this->_xml->global->resource->connection->types->{$type};
  1211. }
  1212. /**
  1213. * Retrieve store Ids for $path with checking
  1214. *
  1215. * if empty $allowValues then retrieve all stores values
  1216. *
  1217. * return array($storeId=>$pathValue)
  1218. *
  1219. * @param string $path
  1220. * @param array $allowValues
  1221. * @return array
  1222. */
  1223. public function getStoresConfigByPath($path, $allowValues = array(), $useAsKey = 'id')
  1224. {
  1225. $storeValues = array();
  1226. $stores = $this->getNode('stores');
  1227. foreach ($stores->children() as $code => $store) {
  1228. switch ($useAsKey) {
  1229. case 'id':
  1230. $key = (int) $store->descend('system/store/id');
  1231. break;
  1232. case 'code':
  1233. $key = $code;
  1234. break;
  1235. case 'name':
  1236. $key = (string) $store->descend('system/store/name');
  1237. }
  1238. if ($key === false) {
  1239. continue;
  1240. }
  1241. $pathValue = (string) $store->descend($path);
  1242. if (empty($allowValues)) {
  1243. $storeValues[$key] = $pathValue;
  1244. } else if (in_array($pathValue, $allowValues)) {
  1245. $storeValues[$key] = $pathValue;
  1246. }
  1247. }
  1248. return $storeValues;
  1249. }
  1250. /**
  1251. * Check whether given path should be secure according to configuration security requirements for URL
  1252. * "Secure" should not be confused with https protocol, it is about web/secure/*_url settings usage only
  1253. *
  1254. * @param string $url
  1255. * @return bool
  1256. */
  1257. public function shouldUrlBeSecure($url)
  1258. {
  1259. if (!Mage::getStoreConfigFlag(Mage_Core_Model_Store::XML_PATH_SECURE_IN_FRONTEND)) {
  1260. return false;
  1261. }
  1262. if (!isset($this->_secureUrlCache[$url])) {
  1263. $this->_secureUrlCache[$url] = false;
  1264. $secureUrls = $this->getNode('frontend/secure_url');
  1265. foreach ($secureUrls->children() as $match) {
  1266. if (strpos($url, (string)$match) === 0) {
  1267. $this->_secureUrlCache[$url] = true;
  1268. break;
  1269. }
  1270. }
  1271. }
  1272. return $this->_secureUrlCache[$url];
  1273. }
  1274. /**
  1275. * Get DB table names prefix
  1276. *
  1277. * @return string
  1278. */
  1279. public function getTablePrefix()
  1280. {
  1281. return $this->_xml->global->resources->db->table_prefix;
  1282. }
  1283. /**
  1284. * Get events configuration
  1285. *
  1286. * @param string $area event area
  1287. * @param string $eventName event name
  1288. * @return Mage_Core_Model_Config_Element
  1289. */
  1290. public function getEventConfig($area, $eventName)
  1291. {
  1292. //return $this->getNode($area)->events->{$eventName};
  1293. if (!isset($this->_eventAreas[$area])) {
  1294. $this->_eventAreas[$area] = $this->getNode($area)->events;
  1295. }
  1296. return $this->_eventAreas[$area]->{$eventName};
  1297. }
  1298. /**
  1299. * Save config value to DB
  1300. *
  1301. * @param string $path
  1302. * @param string $value
  1303. * @param string $scope
  1304. * @param int $scopeId
  1305. * @return Mage_Core_Store_Config
  1306. */
  1307. public function saveConfig($path, $value, $scope = 'default', $scopeId = 0)
  1308. {
  1309. $resource = $this->getResourceModel();
  1310. $resource->saveConfig(rtrim($path, '/'), $value, $scope, $scopeId);
  1311. return $this;
  1312. }
  1313. /**
  1314. * Delete config value from DB
  1315. *
  1316. * @param string $path
  1317. * @param string $scope
  1318. * @param int $scopeId
  1319. * @return Mage_Core_Model_Config
  1320. */
  1321. public function deleteConfig($path, $scope = 'default', $scopeId = 0)
  1322. {
  1323. $resource = $this->getResourceModel();
  1324. $resource->deleteConfig(rtrim($path, '/'), $scope, $scopeId);
  1325. return $this;
  1326. }
  1327. /**
  1328. * Get fieldset from configuration
  1329. *
  1330. * @param string $name fieldset name
  1331. * @param string $root fieldset area, could be 'admin'
  1332. * @return null|array
  1333. */
  1334. public function getFieldset($name, $root = 'global')
  1335. {
  1336. /** @var $config Mage_Core_Model_Config_Base */
  1337. $config = Mage::getSingleton('Mage_Core_Model_Config_Fieldset');
  1338. $rootNode = $config->getNode($root . '/fieldsets');
  1339. if (!$rootNode) {
  1340. return null;
  1341. }
  1342. return $rootNode->$name ? $rootNode->$name->children() : null;
  1343. }
  1344. /**
  1345. * Retrieve resource connection model name
  1346. *
  1347. * @param string $moduleName
  1348. * @return string
  1349. */
  1350. public function getResourceConnectionModel($moduleName = null)
  1351. {
  1352. $config = null;
  1353. if (!is_null($moduleName)) {
  1354. $setupResource = $moduleName . '_setup';
  1355. $config = $this->getResourceConnectionConfig($setupResource);
  1356. }
  1357. if (!$config) {
  1358. $config = $this->getResourceConnectionConfig(Mage_Core_Model_Resource::DEFAULT_SETUP_RESOURCE);
  1359. }
  1360. return (string)$config->model;
  1361. }
  1362. /**
  1363. * Get a resource model class name
  1364. *
  1365. * @param string $modelClass
  1366. * @return string|false
  1367. */
  1368. public function getResourceModelClassName($modelClass)
  1369. {
  1370. return $this->getModelClassName($modelClass);
  1371. }
  1372. /**
  1373. * Get allowed areas
  1374. *
  1375. * @return array
  1376. */
  1377. public function getAreas()
  1378. {
  1379. if (is_null($this->_allowedAreas) ) {
  1380. $this->_loadAreas();
  1381. }
  1382. return $this->_allowedAreas;
  1383. }
  1384. /**
  1385. * Retrieve area config by area code
  1386. *
  1387. * @param string|null $areaCode
  1388. * @return array
  1389. */
  1390. public function getAreaConfig($areaCode = null)
  1391. {
  1392. $areaCode = empty($areaCode) ? $this->getCurrentAreaCode() : $areaCode;
  1393. $areas = $this->getAreas();
  1394. if (!isset($areas[$areaCode])) {
  1395. throw new InvalidArgumentException('Requested area (' . $areaCode . ') doesn\'t exist');
  1396. }
  1397. return $areas[$areaCode];
  1398. }
  1399. /**
  1400. * Load allowed areas from config
  1401. *
  1402. * @return Mage_Core_Model_Config
  1403. */
  1404. protected function _loadAreas()
  1405. {
  1406. $this->_allowedAreas = array();
  1407. $nodeAreas = $this->getNode('global/areas');
  1408. if (is_object($nodeAreas)) {
  1409. foreach ($nodeAreas->asArray() as $areaCode => $areaInfo) {
  1410. if (empty($areaCode)
  1411. || (!isset($areaInfo['base_controller']) || empty($areaInfo['base_controller']))
  1412. || (!isset($areaInfo['routers']) || !is_array($areaInfo['routers']))
  1413. ) {
  1414. continue;
  1415. }
  1416. foreach ($areaInfo['routers'] as $routerKey => $routerInfo) {
  1417. if (empty($routerKey) || !isset($routerInfo['class'])) {
  1418. unset($areaInfo[$routerKey]);
  1419. }
  1420. }
  1421. if (empty($areaInfo['routers'])) {
  1422. continue;
  1423. }
  1424. $this->_allowedAreas[$areaCode] = $areaInfo;
  1425. }
  1426. }
  1427. return $this;
  1428. }
  1429. /**
  1430. * Get routers from config
  1431. *
  1432. * @return array
  1433. */
  1434. public function getRouters()
  1435. {
  1436. $routers = array();
  1437. foreach ($this->getAreas() as $areaCode => $areaInfo) {
  1438. foreach ($areaInfo['routers'] as $routerKey => $routerInfo ) {
  1439. $routerInfo = array_merge($routerInfo, $areaInfo);
  1440. unset($routerInfo['routers']);
  1441. $routerInfo['area'] = $areaCode;
  1442. $routers[$routerKey] = $routerInfo;
  1443. }
  1444. }
  1445. return $routers;
  1446. }
  1447. public function isModuleEnabled($moduleName)
  1448. {
  1449. if (!$this->getNode('modules/' . $moduleName)) {
  1450. return false;
  1451. }
  1452. $isActive = $this->getNode('modules/' . $moduleName . '/active');
  1453. if (!$isActive || !in_array((string)$isActive, array('true', '1'))) {
  1454. return false;
  1455. }
  1456. return true;
  1457. }
  1458. /**
  1459. * Get currently used area code
  1460. * @return string|null
  1461. */
  1462. public function getCurrentAreaCode()
  1463. {
  1464. return $this->_currentAreaCode;
  1465. }
  1466. /**
  1467. * Set currently used area code
  1468. *
  1469. * @param $areaCode
  1470. * @return Mage_Core_Model_Config
  1471. */
  1472. public function setCurrentAreaCode($areaCode)
  1473. {
  1474. $this->_currentAreaCode = $areaCode;
  1475. return $this;
  1476. }
  1477. /**
  1478. * Cleanup circular references
  1479. *
  1480. * Destructor should be called explicitly in order to work around the PHP bug
  1481. * https://bugs.php.net/bug.php?id=62468
  1482. */
  1483. public function __destruct()
  1484. {
  1485. $this->_cacheLoadedSections = array();
  1486. parent::__destruct();
  1487. }
  1488. }