<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Config * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * @see Zend_Config */ require_once 'Zend/Config.php'; /** @see Zend_Xml_Security */ require_once 'Zend/Xml/Security.php'; /** @see Zend_Xml_Exception */ require_once 'Zend/Xml/Exception.php'; /** * XML Adapter for Zend_Config * * @category Zend * @package Zend_Config * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Config_Xml extends Zend_Config { /** * XML namespace for ZF-related tags and attributes */ const XML_NAMESPACE = 'http://framework.zend.com/xml/zend-config-xml/1.0/'; /** * Whether to skip extends or not * * @var boolean */ protected $_skipExtends = false; /** * Loads the section $section from the config file (or string $xml for * access facilitated by nested object properties. * * Sections are defined in the XML as children of the root element. * * In order to extend another section, a section defines the "extends" * attribute having a value of the section name from which the extending * section inherits values. * * Note that the keys in $section will override any keys of the same * name in the sections that have been included via "extends". * * The $options parameter may be provided as either a boolean or an array. * If provided as a boolean, this sets the $allowModifications option of * Zend_Config. If provided as an array, there are two configuration * directives that may be set. For example: * * $options = array( * 'allowModifications' => false, * 'skipExtends' => false * ); * * @param string $xml XML file or string to process * @param mixed $section Section to process * @param array|boolean $options * @throws Zend_Config_Exception When xml is not set or cannot be loaded * @throws Zend_Config_Exception When section $sectionName cannot be found in $xml */ public function __construct($xml, $section = null, $options = false) { if (empty($xml)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception('Filename is not set'); } $allowModifications = false; if (is_bool($options)) { $allowModifications = $options; } elseif (is_array($options)) { if (isset($options['allowModifications'])) { $allowModifications = (bool) $options['allowModifications']; } if (isset($options['skipExtends'])) { $this->_skipExtends = (bool) $options['skipExtends']; } } set_error_handler(array($this, '_loadFileErrorHandler')); // Warnings and errors are suppressed if (strstr($xml, '<?xml')) { $config = Zend_Xml_Security::scan($xml); } else { try { if (!$config = Zend_Xml_Security::scanFile($xml)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception( "Error failed to load $xml file" ); } } catch (Zend_Xml_Exception $e) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception( $e->getMessage() ); } } restore_error_handler(); // Check if there was a error while loading file if ($this->_loadFileErrorStr !== null) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception($this->_loadFileErrorStr); } if ($section === null) { $dataArray = array(); foreach ($config as $sectionName => $sectionData) { $dataArray[$sectionName] = $this->_processExtends($config, $sectionName); } parent::__construct($dataArray, $allowModifications); } else if (is_array($section)) { $dataArray = array(); foreach ($section as $sectionName) { if (!isset($config->$sectionName)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $xml"); } $dataArray = array_merge($this->_processExtends($config, $sectionName), $dataArray); } parent::__construct($dataArray, $allowModifications); } else { if (!isset($config->$section)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Section '$section' cannot be found in $xml"); } $dataArray = $this->_processExtends($config, $section); if (!is_array($dataArray)) { // Section in the XML file contains just one top level string $dataArray = array($section => $dataArray); } parent::__construct($dataArray, $allowModifications); } $this->_loadedSection = $section; } /** * Helper function to process each element in the section and handle * the "extends" inheritance attribute. * * @param SimpleXMLElement $element XML Element to process * @param string $section Section to process * @param array $config Configuration which was parsed yet * @throws Zend_Config_Exception When $section cannot be found * @return array */ protected function _processExtends(SimpleXMLElement $element, $section, array $config = array()) { if (!isset($element->$section)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Section '$section' cannot be found"); } $thisSection = $element->$section; $nsAttributes = $thisSection->attributes(self::XML_NAMESPACE); if (isset($thisSection['extends']) || isset($nsAttributes['extends'])) { $extendedSection = (string) (isset($nsAttributes['extends']) ? $nsAttributes['extends'] : $thisSection['extends']); $this->_assertValidExtend($section, $extendedSection); if (!$this->_skipExtends) { $config = $this->_processExtends($element, $extendedSection, $config); } } $config = $this->_arrayMergeRecursive($config, $this->_toArray($thisSection)); return $config; } /** * Returns a string or an associative and possibly multidimensional array from * a SimpleXMLElement. * * @param SimpleXMLElement $xmlObject Convert a SimpleXMLElement into an array * @return array|string */ protected function _toArray(SimpleXMLElement $xmlObject) { $config = array(); $nsAttributes = $xmlObject->attributes(self::XML_NAMESPACE); // Search for parent node values if (count($xmlObject->attributes()) > 0) { foreach ($xmlObject->attributes() as $key => $value) { if ($key === 'extends') { continue; } $value = (string) $value; if (array_key_exists($key, $config)) { if (!is_array($config[$key])) { $config[$key] = array($config[$key]); } $config[$key][] = $value; } else { $config[$key] = $value; } } } // Search for local 'const' nodes and replace them if (count($xmlObject->children(self::XML_NAMESPACE)) > 0) { if (count($xmlObject->children()) > 0) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("A node with a 'const' childnode may not have any other children"); } $dom = dom_import_simplexml($xmlObject); $namespaceChildNodes = array(); // We have to store them in an array, as replacing nodes will // confuse the DOMNodeList later foreach ($dom->childNodes as $node) { if ($node instanceof DOMElement && $node->namespaceURI === self::XML_NAMESPACE) { $namespaceChildNodes[] = $node; } } foreach ($namespaceChildNodes as $node) { switch ($node->localName) { case 'const': if (!$node->hasAttributeNS(self::XML_NAMESPACE, 'name')) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Misssing 'name' attribute in 'const' node"); } $constantName = $node->getAttributeNS(self::XML_NAMESPACE, 'name'); if (!defined($constantName)) { require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Constant with name '$constantName' was not defined"); } $constantValue = constant($constantName); $dom->replaceChild($dom->ownerDocument->createTextNode($constantValue), $node); break; default: require_once 'Zend/Config/Exception.php'; throw new Zend_Config_Exception("Unknown node with name '$node->localName' found"); } } return (string) simplexml_import_dom($dom); } // Search for children if (count($xmlObject->children()) > 0) { foreach ($xmlObject->children() as $key => $value) { if (count($value->children()) > 0 || count($value->children(self::XML_NAMESPACE)) > 0) { $value = $this->_toArray($value); } else if (count($value->attributes()) > 0) { $attributes = $value->attributes(); if (isset($attributes['value'])) { $value = (string) $attributes['value']; } else { $value = $this->_toArray($value); } } else { $value = (string) $value; } if (array_key_exists($key, $config)) { if (!is_array($config[$key]) || !array_key_exists(0, $config[$key])) { $config[$key] = array($config[$key]); } $config[$key][] = $value; } else { $config[$key] = $value; } } } else if (!isset($xmlObject['extends']) && !isset($nsAttributes['extends']) && (count($config) === 0)) { // Object has no children nor attributes and doesn't use the extends // attribute: it's a string $config = (string) $xmlObject; } return $config; } }