File indexing completed on 2025-03-02 05:29:26
0001 <?php 0002 0003 /** 0004 * Zend Framework 0005 * 0006 * LICENSE 0007 * 0008 * This source file is subject to the new BSD license that is bundled 0009 * with this package in the file LICENSE.txt. 0010 * It is also available through the world-wide-web at this URL: 0011 * http://framework.zend.com/license/new-bsd 0012 * If you did not receive a copy of the license and are unable to 0013 * obtain it through the world-wide-web, please send an email 0014 * to license@zend.com so we can send you a copy immediately. 0015 * 0016 * @category Zend 0017 * @package Zend_Gdata 0018 * @subpackage App 0019 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0020 * @license http://framework.zend.com/license/new-bsd New BSD License 0021 * @version $Id$ 0022 */ 0023 0024 /** 0025 * @see Zend_Gdata_App_Util 0026 */ 0027 // require_once 'Zend/Gdata/App/Util.php'; 0028 0029 /** @see Zend_Xml_Security */ 0030 // require_once 'Zend/Xml/Security.php'; 0031 0032 /** 0033 * Abstract class for all XML elements 0034 * 0035 * @category Zend 0036 * @package Zend_Gdata 0037 * @subpackage App 0038 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0039 * @license http://framework.zend.com/license/new-bsd New BSD License 0040 */ 0041 abstract class Zend_Gdata_App_Base 0042 { 0043 0044 /** 0045 * @var string The XML element name, including prefix if desired 0046 */ 0047 protected $_rootElement = null; 0048 0049 /** 0050 * @var string The XML namespace prefix 0051 */ 0052 protected $_rootNamespace = 'atom'; 0053 0054 /** 0055 * @var string The XML namespace URI - takes precedence over lookup up the 0056 * corresponding URI for $_rootNamespace 0057 */ 0058 protected $_rootNamespaceURI = null; 0059 0060 /** 0061 * @var array Leftover elements which were not handled 0062 */ 0063 protected $_extensionElements = array(); 0064 0065 /** 0066 * @var array Leftover attributes which were not handled 0067 */ 0068 protected $_extensionAttributes = array(); 0069 0070 /** 0071 * @var string XML child text node content 0072 */ 0073 protected $_text = null; 0074 0075 /** 0076 * @var array Memoized results from calls to lookupNamespace() to avoid 0077 * expensive calls to getGreatestBoundedValue(). The key is in the 0078 * form 'prefix-majorVersion-minorVersion', and the value is the 0079 * output from getGreatestBoundedValue(). 0080 */ 0081 protected static $_namespaceLookupCache = array(); 0082 0083 /** 0084 * List of namespaces, as a three-dimensional array. The first dimension 0085 * represents the namespace prefix, the second dimension represents the 0086 * minimum major protocol version, and the third dimension is the minimum 0087 * minor protocol version. Null keys are NOT allowed. 0088 * 0089 * When looking up a namespace for a given prefix, the greatest version 0090 * number (both major and minor) which is less than the effective version 0091 * should be used. 0092 * 0093 * @see lookupNamespace() 0094 * @see registerNamespace() 0095 * @see registerAllNamespaces() 0096 * @var array 0097 */ 0098 protected $_namespaces = array( 0099 'atom' => array( 0100 1 => array( 0101 0 => 'http://www.w3.org/2005/Atom' 0102 ) 0103 ), 0104 'app' => array( 0105 1 => array( 0106 0 => 'http://purl.org/atom/app#' 0107 ), 0108 2 => array( 0109 0 => 'http://www.w3.org/2007/app' 0110 ) 0111 ) 0112 ); 0113 0114 public function __construct() 0115 { 0116 } 0117 0118 /** 0119 * Returns the child text node of this element 0120 * This represents any raw text contained within the XML element 0121 * 0122 * @return string Child text node 0123 */ 0124 public function getText($trim = true) 0125 { 0126 if ($trim) { 0127 return trim($this->_text); 0128 } else { 0129 return $this->_text; 0130 } 0131 } 0132 0133 /** 0134 * Sets the child text node of this element 0135 * This represents any raw text contained within the XML element 0136 * 0137 * @param string $value Child text node 0138 * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. 0139 */ 0140 public function setText($value) 0141 { 0142 $this->_text = $value; 0143 return $this; 0144 } 0145 0146 /** 0147 * Returns an array of all elements not matched to data model classes 0148 * during the parsing of the XML 0149 * 0150 * @return array All elements not matched to data model classes during parsing 0151 */ 0152 public function getExtensionElements() 0153 { 0154 return $this->_extensionElements; 0155 } 0156 0157 /** 0158 * Sets an array of all elements not matched to data model classes 0159 * during the parsing of the XML. This method can be used to add arbitrary 0160 * child XML elements to any data model class. 0161 * 0162 * @param array $value All extension elements 0163 * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. 0164 */ 0165 public function setExtensionElements($value) 0166 { 0167 $this->_extensionElements = $value; 0168 return $this; 0169 } 0170 0171 /** 0172 * Returns an array of all extension attributes not transformed into data 0173 * model properties during parsing of the XML. Each element of the array 0174 * is a hashed array of the format: 0175 * array('namespaceUri' => string, 'name' => string, 'value' => string); 0176 * 0177 * @return array All extension attributes 0178 */ 0179 public function getExtensionAttributes() 0180 { 0181 return $this->_extensionAttributes; 0182 } 0183 0184 /** 0185 * Sets an array of all extension attributes not transformed into data 0186 * model properties during parsing of the XML. Each element of the array 0187 * is a hashed array of the format: 0188 * array('namespaceUri' => string, 'name' => string, 'value' => string); 0189 * This can be used to add arbitrary attributes to any data model element 0190 * 0191 * @param array $value All extension attributes 0192 * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface. 0193 */ 0194 public function setExtensionAttributes($value) 0195 { 0196 $this->_extensionAttributes = $value; 0197 return $this; 0198 } 0199 0200 /** 0201 * Retrieves a DOMElement which corresponds to this element and all 0202 * child properties. This is used to build an entry back into a DOM 0203 * and eventually XML text for sending to the server upon updates, or 0204 * for application storage/persistence. 0205 * 0206 * @param DOMDocument $doc The DOMDocument used to construct DOMElements 0207 * @return DOMElement The DOMElement representing this element and all 0208 * child properties. 0209 */ 0210 public function getDOM($doc = null, $majorVersion = 1, $minorVersion = null) 0211 { 0212 if ($doc === null) { 0213 $doc = new DOMDocument('1.0', 'utf-8'); 0214 } 0215 if ($this->_rootNamespaceURI != null) { 0216 $element = $doc->createElementNS($this->_rootNamespaceURI, $this->_rootElement); 0217 } elseif ($this->_rootNamespace !== null) { 0218 if (strpos($this->_rootElement, ':') === false) { 0219 $elementName = $this->_rootNamespace . ':' . $this->_rootElement; 0220 } else { 0221 $elementName = $this->_rootElement; 0222 } 0223 $element = $doc->createElementNS($this->lookupNamespace($this->_rootNamespace), $elementName); 0224 } else { 0225 $element = $doc->createElement($this->_rootElement); 0226 } 0227 if ($this->_text != null) { 0228 $element->appendChild($element->ownerDocument->createTextNode($this->_text)); 0229 } 0230 foreach ($this->_extensionElements as $extensionElement) { 0231 $element->appendChild($extensionElement->getDOM($element->ownerDocument)); 0232 } 0233 foreach ($this->_extensionAttributes as $attribute) { 0234 $element->setAttribute($attribute['name'], $attribute['value']); 0235 } 0236 return $element; 0237 } 0238 0239 /** 0240 * Given a child DOMNode, tries to determine how to map the data into 0241 * object instance members. If no mapping is defined, Extension_Element 0242 * objects are created and stored in an array. 0243 * 0244 * @param DOMNode $child The DOMNode needed to be handled 0245 */ 0246 protected function takeChildFromDOM($child) 0247 { 0248 if ($child->nodeType == XML_TEXT_NODE) { 0249 $this->_text = $child->nodeValue; 0250 } else { 0251 $extensionElement = new Zend_Gdata_App_Extension_Element(); 0252 $extensionElement->transferFromDOM($child); 0253 $this->_extensionElements[] = $extensionElement; 0254 } 0255 } 0256 0257 /** 0258 * Given a DOMNode representing an attribute, tries to map the data into 0259 * instance members. If no mapping is defined, the name and value are 0260 * stored in an array. 0261 * 0262 * @param DOMNode $attribute The DOMNode attribute needed to be handled 0263 */ 0264 protected function takeAttributeFromDOM($attribute) 0265 { 0266 $arrayIndex = ($attribute->namespaceURI != '')?( 0267 $attribute->namespaceURI . ':' . $attribute->name): 0268 $attribute->name; 0269 $this->_extensionAttributes[$arrayIndex] = 0270 array('namespaceUri' => $attribute->namespaceURI, 0271 'name' => $attribute->localName, 0272 'value' => $attribute->nodeValue); 0273 } 0274 0275 /** 0276 * Transfers each child and attribute into member variables. 0277 * This is called when XML is received over the wire and the data 0278 * model needs to be built to represent this XML. 0279 * 0280 * @param DOMNode $node The DOMNode that represents this object's data 0281 */ 0282 public function transferFromDOM($node) 0283 { 0284 foreach ($node->childNodes as $child) { 0285 $this->takeChildFromDOM($child); 0286 } 0287 foreach ($node->attributes as $attribute) { 0288 $this->takeAttributeFromDOM($attribute); 0289 } 0290 } 0291 0292 /** 0293 * Parses the provided XML text and generates data model classes for 0294 * each know element by turning the XML text into a DOM tree and calling 0295 * transferFromDOM($element). The first data model element with the same 0296 * name as $this->_rootElement is used and the child elements are 0297 * recursively parsed. 0298 * 0299 * @param string $xml The XML text to parse 0300 */ 0301 public function transferFromXML($xml) 0302 { 0303 if ($xml) { 0304 // Load the feed as an XML DOMDocument object 0305 @ini_set('track_errors', 1); 0306 $doc = new DOMDocument(); 0307 $doc = @Zend_Xml_Security::scan($xml, $doc); 0308 @ini_restore('track_errors'); 0309 if (!$doc) { 0310 // require_once 'Zend/Gdata/App/Exception.php'; 0311 throw new Zend_Gdata_App_Exception("DOMDocument cannot parse XML: $php_errormsg"); 0312 } 0313 $element = $doc->getElementsByTagName($this->_rootElement)->item(0); 0314 if (!$element) { 0315 // require_once 'Zend/Gdata/App/Exception.php'; 0316 throw new Zend_Gdata_App_Exception('No root <' . $this->_rootElement . '> element'); 0317 } 0318 $this->transferFromDOM($element); 0319 } else { 0320 // require_once 'Zend/Gdata/App/Exception.php'; 0321 throw new Zend_Gdata_App_Exception('XML passed to transferFromXML cannot be null'); 0322 } 0323 } 0324 0325 /** 0326 * Converts this element and all children into XML text using getDOM() 0327 * 0328 * @return string XML content 0329 */ 0330 public function saveXML() 0331 { 0332 $element = $this->getDOM(); 0333 return $element->ownerDocument->saveXML($element); 0334 } 0335 0336 /** 0337 * Alias for saveXML() returns XML content for this element and all 0338 * children 0339 * 0340 * @return string XML content 0341 */ 0342 public function getXML() 0343 { 0344 return $this->saveXML(); 0345 } 0346 0347 /** 0348 * Alias for saveXML() 0349 * 0350 * Can be overridden by children to provide more complex representations 0351 * of entries. 0352 * 0353 * @return string Encoded string content 0354 */ 0355 public function encode() 0356 { 0357 return $this->saveXML(); 0358 } 0359 0360 /** 0361 * Get the full version of a namespace prefix 0362 * 0363 * Looks up a prefix (atom:, etc.) in the list of registered 0364 * namespaces and returns the full namespace URI if 0365 * available. Returns the prefix, unmodified, if it's not 0366 * registered. 0367 * 0368 * @param string $prefix The namespace prefix to lookup. 0369 * @param integer $majorVersion The major protocol version in effect. 0370 * Defaults to '1'. 0371 * @param integer $minorVersion The minor protocol version in effect. 0372 * Defaults to null (use latest). 0373 * @return string 0374 */ 0375 public function lookupNamespace($prefix, 0376 $majorVersion = 1, 0377 $minorVersion = null) 0378 { 0379 // Check for a memoized result 0380 $key = $prefix . ' ' . 0381 ($majorVersion === null ? 'NULL' : $majorVersion) . 0382 ' '. ($minorVersion === null ? 'NULL' : $minorVersion); 0383 if (array_key_exists($key, self::$_namespaceLookupCache)) 0384 return self::$_namespaceLookupCache[$key]; 0385 // If no match, return the prefix by default 0386 $result = $prefix; 0387 0388 // Find tuple of keys that correspond to the namespace we should use 0389 if (isset($this->_namespaces[$prefix])) { 0390 // Major version search 0391 $nsData = $this->_namespaces[$prefix]; 0392 $foundMajorV = Zend_Gdata_App_Util::findGreatestBoundedValue( 0393 $majorVersion, $nsData); 0394 // Minor version search 0395 $nsData = $nsData[$foundMajorV]; 0396 $foundMinorV = Zend_Gdata_App_Util::findGreatestBoundedValue( 0397 $minorVersion, $nsData); 0398 // Extract NS 0399 $result = $nsData[$foundMinorV]; 0400 } 0401 0402 // Memoize result 0403 self::$_namespaceLookupCache[$key] = $result; 0404 0405 return $result; 0406 } 0407 0408 /** 0409 * Add a namespace and prefix to the registered list 0410 * 0411 * Takes a prefix and a full namespace URI and adds them to the 0412 * list of registered namespaces for use by 0413 * $this->lookupNamespace(). 0414 * 0415 * WARNING: Currently, registering a namespace will NOT invalidate any 0416 * memoized data stored in $_namespaceLookupCache. Under normal 0417 * use, this behavior is acceptable. If you are adding 0418 * contradictory data to the namespace lookup table, you must 0419 * call flushNamespaceLookupCache(). 0420 * 0421 * @param string $prefix The namespace prefix 0422 * @param string $namespaceUri The full namespace URI 0423 * @param integer $majorVersion The major protocol version in effect. 0424 * Defaults to '1'. 0425 * @param integer $minorVersion The minor protocol version in effect. 0426 * Defaults to null (use latest). 0427 * @return void 0428 */ 0429 public function registerNamespace($prefix, 0430 $namespaceUri, 0431 $majorVersion = 1, 0432 $minorVersion = 0) 0433 { 0434 $this->_namespaces[$prefix][$majorVersion][$minorVersion] = 0435 $namespaceUri; 0436 } 0437 0438 /** 0439 * Flush namespace lookup cache. 0440 * 0441 * Empties the namespace lookup cache. Call this function if you have 0442 * added data to the namespace lookup table that contradicts values that 0443 * may have been cached during a previous call to lookupNamespace(). 0444 */ 0445 public static function flushNamespaceLookupCache() 0446 { 0447 self::$_namespaceLookupCache = array(); 0448 } 0449 0450 /** 0451 * Add an array of namespaces to the registered list. 0452 * 0453 * Takes an array in the format of: 0454 * namespace prefix, namespace URI, major protocol version, 0455 * minor protocol version and adds them with calls to ->registerNamespace() 0456 * 0457 * @param array $namespaceArray An array of namespaces. 0458 * @return void 0459 */ 0460 public function registerAllNamespaces($namespaceArray) 0461 { 0462 foreach($namespaceArray as $namespace) { 0463 $this->registerNamespace( 0464 $namespace[0], $namespace[1], $namespace[2], $namespace[3]); 0465 } 0466 } 0467 0468 0469 /** 0470 * Magic getter to allow access like $entry->foo to call $entry->getFoo() 0471 * Alternatively, if no getFoo() is defined, but a $_foo protected variable 0472 * is defined, this is returned. 0473 * 0474 * TODO Remove ability to bypass getFoo() methods?? 0475 * 0476 * @param string $name The variable name sought 0477 */ 0478 public function __get($name) 0479 { 0480 $method = 'get'.ucfirst($name); 0481 if (method_exists($this, $method)) { 0482 return call_user_func(array(&$this, $method)); 0483 } else if (property_exists($this, "_${name}")) { 0484 return $this->{'_' . $name}; 0485 } else { 0486 // require_once 'Zend/Gdata/App/InvalidArgumentException.php'; 0487 throw new Zend_Gdata_App_InvalidArgumentException( 0488 'Property ' . $name . ' does not exist'); 0489 } 0490 } 0491 0492 /** 0493 * Magic setter to allow acces like $entry->foo='bar' to call 0494 * $entry->setFoo('bar') automatically. 0495 * 0496 * Alternatively, if no setFoo() is defined, but a $_foo protected variable 0497 * is defined, this is returned. 0498 * 0499 * TODO Remove ability to bypass getFoo() methods?? 0500 * 0501 * @param string $name 0502 * @param string $value 0503 */ 0504 public function __set($name, $val) 0505 { 0506 $method = 'set'.ucfirst($name); 0507 if (method_exists($this, $method)) { 0508 return call_user_func(array(&$this, $method), $val); 0509 } else if (isset($this->{'_' . $name}) || ($this->{'_' . $name} === null)) { 0510 $this->{'_' . $name} = $val; 0511 } else { 0512 // require_once 'Zend/Gdata/App/InvalidArgumentException.php'; 0513 throw new Zend_Gdata_App_InvalidArgumentException( 0514 'Property ' . $name . ' does not exist'); 0515 } 0516 } 0517 0518 /** 0519 * Magic __isset method 0520 * 0521 * @param string $name 0522 */ 0523 public function __isset($name) 0524 { 0525 $rc = new ReflectionClass(get_class($this)); 0526 $privName = '_' . $name; 0527 if (!($rc->hasProperty($privName))) { 0528 // require_once 'Zend/Gdata/App/InvalidArgumentException.php'; 0529 throw new Zend_Gdata_App_InvalidArgumentException( 0530 'Property ' . $name . ' does not exist'); 0531 } else { 0532 if (isset($this->{$privName})) { 0533 if (is_array($this->{$privName})) { 0534 if (count($this->{$privName}) > 0) { 0535 return true; 0536 } else { 0537 return false; 0538 } 0539 } else { 0540 return true; 0541 } 0542 } else { 0543 return false; 0544 } 0545 } 0546 } 0547 0548 /** 0549 * Magic __unset method 0550 * 0551 * @param string $name 0552 */ 0553 public function __unset($name) 0554 { 0555 if (isset($this->{'_' . $name})) { 0556 if (is_array($this->{'_' . $name})) { 0557 $this->{'_' . $name} = array(); 0558 } else { 0559 $this->{'_' . $name} = null; 0560 } 0561 } 0562 } 0563 0564 /** 0565 * Magic toString method allows using this directly via echo 0566 * Works best in PHP >= 4.2.0 0567 * 0568 * @return string The text representation of this object 0569 */ 0570 public function __toString() 0571 { 0572 return $this->getText(); 0573 } 0574 0575 }