<?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_Gdata * @subpackage App * @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_Gdata_App_Util */ require_once 'Zend/Gdata/App/Util.php'; /** @see Zend_Xml_Security */ require_once 'Zend/Xml/Security.php'; /** * Abstract class for all XML elements * * @category Zend * @package Zend_Gdata * @subpackage App * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ abstract class Zend_Gdata_App_Base { /** * @var string The XML element name, including prefix if desired */ protected $_rootElement = null; /** * @var string The XML namespace prefix */ protected $_rootNamespace = 'atom'; /** * @var string The XML namespace URI - takes precedence over lookup up the * corresponding URI for $_rootNamespace */ protected $_rootNamespaceURI = null; /** * @var array Leftover elements which were not handled */ protected $_extensionElements = array(); /** * @var array Leftover attributes which were not handled */ protected $_extensionAttributes = array(); /** * @var string XML child text node content */ protected $_text = null; /** * @var array Memoized results from calls to lookupNamespace() to avoid * expensive calls to getGreatestBoundedValue(). The key is in the * form 'prefix-majorVersion-minorVersion', and the value is the * output from getGreatestBoundedValue(). */ protected static $_namespaceLookupCache = array(); /** * List of namespaces, as a three-dimensional array. The first dimension * represents the namespace prefix, the second dimension represents the * minimum major protocol version, and the third dimension is the minimum * minor protocol version. Null keys are NOT allowed. * * When looking up a namespace for a given prefix, the greatest version * number (both major and minor) which is less than the effective version * should be used. * * @see lookupNamespace() * @see registerNamespace() * @see registerAllNamespaces() * @var array */ protected $_namespaces = array( 'atom' => array( 1 => array( 0 => 'http://www.w3.org/2005/Atom' ) ), 'app' => array( 1 => array( 0 => 'http://purl.org/atom/app#' ), 2 => array( 0 => 'http://www.w3.org/2007/app' ) ) ); public function __construct() { } /** * Returns the child text node of this element * This represents any raw text contained within the XML element * * @return string Child text node */ public function getText($trim = true) { if ($trim) { return trim($this->_text); } else { return $this->_text; } } /** * Sets the child text node of this element * This represents any raw text contained within the XML element * * @param string $value Child text node * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. */ public function setText($value) { $this->_text = $value; return $this; } /** * Returns an array of all elements not matched to data model classes * during the parsing of the XML * * @return array All elements not matched to data model classes during parsing */ public function getExtensionElements() { return $this->_extensionElements; } /** * Sets an array of all elements not matched to data model classes * during the parsing of the XML. This method can be used to add arbitrary * child XML elements to any data model class. * * @param array $value All extension elements * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. */ public function setExtensionElements($value) { $this->_extensionElements = $value; return $this; } /** * Returns an array of all extension attributes not transformed into data * model properties during parsing of the XML. Each element of the array * is a hashed array of the format: * array('namespaceUri' => string, 'name' => string, 'value' => string); * * @return array All extension attributes */ public function getExtensionAttributes() { return $this->_extensionAttributes; } /** * Sets an array of all extension attributes not transformed into data * model properties during parsing of the XML. Each element of the array * is a hashed array of the format: * array('namespaceUri' => string, 'name' => string, 'value' => string); * This can be used to add arbitrary attributes to any data model element * * @param array $value All extension attributes * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. */ public function setExtensionAttributes($value) { $this->_extensionAttributes = $value; return $this; } /** * Retrieves a DOMElement which corresponds to this element and all * child properties. This is used to build an entry back into a DOM * and eventually XML text for sending to the server upon updates, or * for application storage/persistence. * * @param DOMDocument $doc The DOMDocument used to construct DOMElements * @return DOMElement The DOMElement representing this element and all * child properties. */ public function getDOM($doc = null, $majorVersion = 1, $minorVersion = null) { if ($doc === null) { $doc = new DOMDocument('1.0', 'utf-8'); } if ($this->_rootNamespaceURI != null) { $element = $doc->createElementNS($this->_rootNamespaceURI, $this->_rootElement); } elseif ($this->_rootNamespace !== null) { if (strpos($this->_rootElement, ':') === false) { $elementName = $this->_rootNamespace . ':' . $this->_rootElement; } else { $elementName = $this->_rootElement; } $element = $doc->createElementNS($this->lookupNamespace($this->_rootNamespace), $elementName); } else { $element = $doc->createElement($this->_rootElement); } if ($this->_text != null) { $element->appendChild($element->ownerDocument->createTextNode($this->_text)); } foreach ($this->_extensionElements as $extensionElement) { $element->appendChild($extensionElement->getDOM($element->ownerDocument)); } foreach ($this->_extensionAttributes as $attribute) { $element->setAttribute($attribute['name'], $attribute['value']); } return $element; } /** * Given a child DOMNode, tries to determine how to map the data into * object instance members. If no mapping is defined, Extension_Element * objects are created and stored in an array. * * @param DOMNode $child The DOMNode needed to be handled */ protected function takeChildFromDOM($child) { if ($child->nodeType == XML_TEXT_NODE) { $this->_text = $child->nodeValue; } else { $extensionElement = new Zend_Gdata_App_Extension_Element(); $extensionElement->transferFromDOM($child); $this->_extensionElements[] = $extensionElement; } } /** * Given a DOMNode representing an attribute, tries to map the data into * instance members. If no mapping is defined, the name and value are * stored in an array. * * @param DOMNode $attribute The DOMNode attribute needed to be handled */ protected function takeAttributeFromDOM($attribute) { $arrayIndex = ($attribute->namespaceURI != '')?( $attribute->namespaceURI . ':' . $attribute->name): $attribute->name; $this->_extensionAttributes[$arrayIndex] = array('namespaceUri' => $attribute->namespaceURI, 'name' => $attribute->localName, 'value' => $attribute->nodeValue); } /** * Transfers each child and attribute into member variables. * This is called when XML is received over the wire and the data * model needs to be built to represent this XML. * * @param DOMNode $node The DOMNode that represents this object's data */ public function transferFromDOM($node) { foreach ($node->childNodes as $child) { $this->takeChildFromDOM($child); } foreach ($node->attributes as $attribute) { $this->takeAttributeFromDOM($attribute); } } /** * Parses the provided XML text and generates data model classes for * each know element by turning the XML text into a DOM tree and calling * transferFromDOM($element). The first data model element with the same * name as $this->_rootElement is used and the child elements are * recursively parsed. * * @param string $xml The XML text to parse */ public function transferFromXML($xml) { if ($xml) { // Load the feed as an XML DOMDocument object @ini_set('track_errors', 1); $doc = new DOMDocument(); $doc = @Zend_Xml_Security::scan($xml, $doc); @ini_restore('track_errors'); if (!$doc) { require_once 'Zend/Gdata/App/Exception.php'; throw new Zend_Gdata_App_Exception("DOMDocument cannot parse XML: $php_errormsg"); } $element = $doc->getElementsByTagName($this->_rootElement)->item(0); if (!$element) { require_once 'Zend/Gdata/App/Exception.php'; throw new Zend_Gdata_App_Exception('No root <' . $this->_rootElement . '> element'); } $this->transferFromDOM($element); } else { require_once 'Zend/Gdata/App/Exception.php'; throw new Zend_Gdata_App_Exception('XML passed to transferFromXML cannot be null'); } } /** * Converts this element and all children into XML text using getDOM() * * @return string XML content */ public function saveXML() { $element = $this->getDOM(); return $element->ownerDocument->saveXML($element); } /** * Alias for saveXML() returns XML content for this element and all * children * * @return string XML content */ public function getXML() { return $this->saveXML(); } /** * Alias for saveXML() * * Can be overridden by children to provide more complex representations * of entries. * * @return string Encoded string content */ public function encode() { return $this->saveXML(); } /** * Get the full version of a namespace prefix * * Looks up a prefix (atom:, etc.) in the list of registered * namespaces and returns the full namespace URI if * available. Returns the prefix, unmodified, if it's not * registered. * * @param string $prefix The namespace prefix to lookup. * @param integer $majorVersion The major protocol version in effect. * Defaults to '1'. * @param integer $minorVersion The minor protocol version in effect. * Defaults to null (use latest). * @return string */ public function lookupNamespace($prefix, $majorVersion = 1, $minorVersion = null) { // Check for a memoized result $key = $prefix . ' ' . ($majorVersion === null ? 'NULL' : $majorVersion) . ' '. ($minorVersion === null ? 'NULL' : $minorVersion); if (array_key_exists($key, self::$_namespaceLookupCache)) return self::$_namespaceLookupCache[$key]; // If no match, return the prefix by default $result = $prefix; // Find tuple of keys that correspond to the namespace we should use if (isset($this->_namespaces[$prefix])) { // Major version search $nsData = $this->_namespaces[$prefix]; $foundMajorV = Zend_Gdata_App_Util::findGreatestBoundedValue( $majorVersion, $nsData); // Minor version search $nsData = $nsData[$foundMajorV]; $foundMinorV = Zend_Gdata_App_Util::findGreatestBoundedValue( $minorVersion, $nsData); // Extract NS $result = $nsData[$foundMinorV]; } // Memoize result self::$_namespaceLookupCache[$key] = $result; return $result; } /** * Add a namespace and prefix to the registered list * * Takes a prefix and a full namespace URI and adds them to the * list of registered namespaces for use by * $this->lookupNamespace(). * * WARNING: Currently, registering a namespace will NOT invalidate any * memoized data stored in $_namespaceLookupCache. Under normal * use, this behavior is acceptable. If you are adding * contradictory data to the namespace lookup table, you must * call flushNamespaceLookupCache(). * * @param string $prefix The namespace prefix * @param string $namespaceUri The full namespace URI * @param integer $majorVersion The major protocol version in effect. * Defaults to '1'. * @param integer $minorVersion The minor protocol version in effect. * Defaults to null (use latest). * @return void */ public function registerNamespace($prefix, $namespaceUri, $majorVersion = 1, $minorVersion = 0) { $this->_namespaces[$prefix][$majorVersion][$minorVersion] = $namespaceUri; } /** * Flush namespace lookup cache. * * Empties the namespace lookup cache. Call this function if you have * added data to the namespace lookup table that contradicts values that * may have been cached during a previous call to lookupNamespace(). */ public static function flushNamespaceLookupCache() { self::$_namespaceLookupCache = array(); } /** * Add an array of namespaces to the registered list. * * Takes an array in the format of: * namespace prefix, namespace URI, major protocol version, * minor protocol version and adds them with calls to ->registerNamespace() * * @param array $namespaceArray An array of namespaces. * @return void */ public function registerAllNamespaces($namespaceArray) { foreach($namespaceArray as $namespace) { $this->registerNamespace( $namespace[0], $namespace[1], $namespace[2], $namespace[3]); } } /** * Magic getter to allow access like $entry->foo to call $entry->getFoo() * Alternatively, if no getFoo() is defined, but a $_foo protected variable * is defined, this is returned. * * TODO Remove ability to bypass getFoo() methods?? * * @param string $name The variable name sought */ public function __get($name) { $method = 'get'.ucfirst($name); if (method_exists($this, $method)) { return call_user_func(array(&$this, $method)); } else if (property_exists($this, "_${name}")) { return $this->{'_' . $name}; } else { require_once 'Zend/Gdata/App/InvalidArgumentException.php'; throw new Zend_Gdata_App_InvalidArgumentException( 'Property ' . $name . ' does not exist'); } } /** * Magic setter to allow acces like $entry->foo='bar' to call * $entry->setFoo('bar') automatically. * * Alternatively, if no setFoo() is defined, but a $_foo protected variable * is defined, this is returned. * * TODO Remove ability to bypass getFoo() methods?? * * @param string $name * @param string $value */ public function __set($name, $val) { $method = 'set'.ucfirst($name); if (method_exists($this, $method)) { return call_user_func(array(&$this, $method), $val); } else if (isset($this->{'_' . $name}) || ($this->{'_' . $name} === null)) { $this->{'_' . $name} = $val; } else { require_once 'Zend/Gdata/App/InvalidArgumentException.php'; throw new Zend_Gdata_App_InvalidArgumentException( 'Property ' . $name . ' does not exist'); } } /** * Magic __isset method * * @param string $name */ public function __isset($name) { $rc = new ReflectionClass(get_class($this)); $privName = '_' . $name; if (!($rc->hasProperty($privName))) { require_once 'Zend/Gdata/App/InvalidArgumentException.php'; throw new Zend_Gdata_App_InvalidArgumentException( 'Property ' . $name . ' does not exist'); } else { if (isset($this->{$privName})) { if (is_array($this->{$privName})) { if (count($this->{$privName}) > 0) { return true; } else { return false; } } else { return true; } } else { return false; } } } /** * Magic __unset method * * @param string $name */ public function __unset($name) { if (isset($this->{'_' . $name})) { if (is_array($this->{'_' . $name})) { $this->{'_' . $name} = array(); } else { $this->{'_' . $name} = null; } } } /** * Magic toString method allows using this directly via echo * Works best in PHP >= 4.2.0 * * @return string The text representation of this object */ public function __toString() { return $this->getText(); } }