Base.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  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@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Gdata
  17. * @subpackage App
  18. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Gdata_App_Util
  24. */
  25. require_once 'Zend/Gdata/App/Util.php';
  26. /** @see Zend_Xml_Security */
  27. require_once 'Zend/Xml/Security.php';
  28. /**
  29. * Abstract class for all XML elements
  30. *
  31. * @category Zend
  32. * @package Zend_Gdata
  33. * @subpackage App
  34. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  35. * @license http://framework.zend.com/license/new-bsd New BSD License
  36. */
  37. abstract class Zend_Gdata_App_Base
  38. {
  39. /**
  40. * @var string The XML element name, including prefix if desired
  41. */
  42. protected $_rootElement = null;
  43. /**
  44. * @var string The XML namespace prefix
  45. */
  46. protected $_rootNamespace = 'atom';
  47. /**
  48. * @var string The XML namespace URI - takes precedence over lookup up the
  49. * corresponding URI for $_rootNamespace
  50. */
  51. protected $_rootNamespaceURI = null;
  52. /**
  53. * @var array Leftover elements which were not handled
  54. */
  55. protected $_extensionElements = array();
  56. /**
  57. * @var array Leftover attributes which were not handled
  58. */
  59. protected $_extensionAttributes = array();
  60. /**
  61. * @var string XML child text node content
  62. */
  63. protected $_text = null;
  64. /**
  65. * @var array Memoized results from calls to lookupNamespace() to avoid
  66. * expensive calls to getGreatestBoundedValue(). The key is in the
  67. * form 'prefix-majorVersion-minorVersion', and the value is the
  68. * output from getGreatestBoundedValue().
  69. */
  70. protected static $_namespaceLookupCache = array();
  71. /**
  72. * List of namespaces, as a three-dimensional array. The first dimension
  73. * represents the namespace prefix, the second dimension represents the
  74. * minimum major protocol version, and the third dimension is the minimum
  75. * minor protocol version. Null keys are NOT allowed.
  76. *
  77. * When looking up a namespace for a given prefix, the greatest version
  78. * number (both major and minor) which is less than the effective version
  79. * should be used.
  80. *
  81. * @see lookupNamespace()
  82. * @see registerNamespace()
  83. * @see registerAllNamespaces()
  84. * @var array
  85. */
  86. protected $_namespaces = array(
  87. 'atom' => array(
  88. 1 => array(
  89. 0 => 'http://www.w3.org/2005/Atom'
  90. )
  91. ),
  92. 'app' => array(
  93. 1 => array(
  94. 0 => 'http://purl.org/atom/app#'
  95. ),
  96. 2 => array(
  97. 0 => 'http://www.w3.org/2007/app'
  98. )
  99. )
  100. );
  101. public function __construct()
  102. {
  103. }
  104. /**
  105. * Returns the child text node of this element
  106. * This represents any raw text contained within the XML element
  107. *
  108. * @return string Child text node
  109. */
  110. public function getText($trim = true)
  111. {
  112. if ($trim) {
  113. return trim($this->_text);
  114. } else {
  115. return $this->_text;
  116. }
  117. }
  118. /**
  119. * Sets the child text node of this element
  120. * This represents any raw text contained within the XML element
  121. *
  122. * @param string $value Child text node
  123. * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface.
  124. */
  125. public function setText($value)
  126. {
  127. $this->_text = $value;
  128. return $this;
  129. }
  130. /**
  131. * Returns an array of all elements not matched to data model classes
  132. * during the parsing of the XML
  133. *
  134. * @return array All elements not matched to data model classes during parsing
  135. */
  136. public function getExtensionElements()
  137. {
  138. return $this->_extensionElements;
  139. }
  140. /**
  141. * Sets an array of all elements not matched to data model classes
  142. * during the parsing of the XML. This method can be used to add arbitrary
  143. * child XML elements to any data model class.
  144. *
  145. * @param array $value All extension elements
  146. * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface.
  147. */
  148. public function setExtensionElements($value)
  149. {
  150. $this->_extensionElements = $value;
  151. return $this;
  152. }
  153. /**
  154. * Returns an array of all extension attributes not transformed into data
  155. * model properties during parsing of the XML. Each element of the array
  156. * is a hashed array of the format:
  157. * array('namespaceUri' => string, 'name' => string, 'value' => string);
  158. *
  159. * @return array All extension attributes
  160. */
  161. public function getExtensionAttributes()
  162. {
  163. return $this->_extensionAttributes;
  164. }
  165. /**
  166. * Sets an array of all extension attributes not transformed into data
  167. * model properties during parsing of the XML. Each element of the array
  168. * is a hashed array of the format:
  169. * array('namespaceUri' => string, 'name' => string, 'value' => string);
  170. * This can be used to add arbitrary attributes to any data model element
  171. *
  172. * @param array $value All extension attributes
  173. * @return Zend_Gdata_App_Base Returns an object of the same type as 'this' to provide a fluent interface.
  174. */
  175. public function setExtensionAttributes($value)
  176. {
  177. $this->_extensionAttributes = $value;
  178. return $this;
  179. }
  180. /**
  181. * Retrieves a DOMElement which corresponds to this element and all
  182. * child properties. This is used to build an entry back into a DOM
  183. * and eventually XML text for sending to the server upon updates, or
  184. * for application storage/persistence.
  185. *
  186. * @param DOMDocument $doc The DOMDocument used to construct DOMElements
  187. * @return DOMElement The DOMElement representing this element and all
  188. * child properties.
  189. */
  190. public function getDOM($doc = null, $majorVersion = 1, $minorVersion = null)
  191. {
  192. if ($doc === null) {
  193. $doc = new DOMDocument('1.0', 'utf-8');
  194. }
  195. if ($this->_rootNamespaceURI != null) {
  196. $element = $doc->createElementNS($this->_rootNamespaceURI, $this->_rootElement);
  197. } elseif ($this->_rootNamespace !== null) {
  198. if (strpos($this->_rootElement, ':') === false) {
  199. $elementName = $this->_rootNamespace . ':' . $this->_rootElement;
  200. } else {
  201. $elementName = $this->_rootElement;
  202. }
  203. $element = $doc->createElementNS($this->lookupNamespace($this->_rootNamespace), $elementName);
  204. } else {
  205. $element = $doc->createElement($this->_rootElement);
  206. }
  207. if ($this->_text != null) {
  208. $element->appendChild($element->ownerDocument->createTextNode($this->_text));
  209. }
  210. foreach ($this->_extensionElements as $extensionElement) {
  211. $element->appendChild($extensionElement->getDOM($element->ownerDocument));
  212. }
  213. foreach ($this->_extensionAttributes as $attribute) {
  214. $element->setAttribute($attribute['name'], $attribute['value']);
  215. }
  216. return $element;
  217. }
  218. /**
  219. * Given a child DOMNode, tries to determine how to map the data into
  220. * object instance members. If no mapping is defined, Extension_Element
  221. * objects are created and stored in an array.
  222. *
  223. * @param DOMNode $child The DOMNode needed to be handled
  224. */
  225. protected function takeChildFromDOM($child)
  226. {
  227. if ($child->nodeType == XML_TEXT_NODE) {
  228. $this->_text = $child->nodeValue;
  229. } else {
  230. $extensionElement = new Zend_Gdata_App_Extension_Element();
  231. $extensionElement->transferFromDOM($child);
  232. $this->_extensionElements[] = $extensionElement;
  233. }
  234. }
  235. /**
  236. * Given a DOMNode representing an attribute, tries to map the data into
  237. * instance members. If no mapping is defined, the name and value are
  238. * stored in an array.
  239. *
  240. * @param DOMNode $attribute The DOMNode attribute needed to be handled
  241. */
  242. protected function takeAttributeFromDOM($attribute)
  243. {
  244. $arrayIndex = ($attribute->namespaceURI != '')?(
  245. $attribute->namespaceURI . ':' . $attribute->name):
  246. $attribute->name;
  247. $this->_extensionAttributes[$arrayIndex] =
  248. array('namespaceUri' => $attribute->namespaceURI,
  249. 'name' => $attribute->localName,
  250. 'value' => $attribute->nodeValue);
  251. }
  252. /**
  253. * Transfers each child and attribute into member variables.
  254. * This is called when XML is received over the wire and the data
  255. * model needs to be built to represent this XML.
  256. *
  257. * @param DOMNode $node The DOMNode that represents this object's data
  258. */
  259. public function transferFromDOM($node)
  260. {
  261. foreach ($node->childNodes as $child) {
  262. $this->takeChildFromDOM($child);
  263. }
  264. foreach ($node->attributes as $attribute) {
  265. $this->takeAttributeFromDOM($attribute);
  266. }
  267. }
  268. /**
  269. * Parses the provided XML text and generates data model classes for
  270. * each know element by turning the XML text into a DOM tree and calling
  271. * transferFromDOM($element). The first data model element with the same
  272. * name as $this->_rootElement is used and the child elements are
  273. * recursively parsed.
  274. *
  275. * @param string $xml The XML text to parse
  276. */
  277. public function transferFromXML($xml)
  278. {
  279. if ($xml) {
  280. // Load the feed as an XML DOMDocument object
  281. @ini_set('track_errors', 1);
  282. $doc = new DOMDocument();
  283. $doc = @Zend_Xml_Security::scan($xml, $doc);
  284. @ini_restore('track_errors');
  285. if (!$doc) {
  286. require_once 'Zend/Gdata/App/Exception.php';
  287. throw new Zend_Gdata_App_Exception("DOMDocument cannot parse XML: $php_errormsg");
  288. }
  289. $element = $doc->getElementsByTagName($this->_rootElement)->item(0);
  290. if (!$element) {
  291. require_once 'Zend/Gdata/App/Exception.php';
  292. throw new Zend_Gdata_App_Exception('No root <' . $this->_rootElement . '> element');
  293. }
  294. $this->transferFromDOM($element);
  295. } else {
  296. require_once 'Zend/Gdata/App/Exception.php';
  297. throw new Zend_Gdata_App_Exception('XML passed to transferFromXML cannot be null');
  298. }
  299. }
  300. /**
  301. * Converts this element and all children into XML text using getDOM()
  302. *
  303. * @return string XML content
  304. */
  305. public function saveXML()
  306. {
  307. $element = $this->getDOM();
  308. return $element->ownerDocument->saveXML($element);
  309. }
  310. /**
  311. * Alias for saveXML() returns XML content for this element and all
  312. * children
  313. *
  314. * @return string XML content
  315. */
  316. public function getXML()
  317. {
  318. return $this->saveXML();
  319. }
  320. /**
  321. * Alias for saveXML()
  322. *
  323. * Can be overridden by children to provide more complex representations
  324. * of entries.
  325. *
  326. * @return string Encoded string content
  327. */
  328. public function encode()
  329. {
  330. return $this->saveXML();
  331. }
  332. /**
  333. * Get the full version of a namespace prefix
  334. *
  335. * Looks up a prefix (atom:, etc.) in the list of registered
  336. * namespaces and returns the full namespace URI if
  337. * available. Returns the prefix, unmodified, if it's not
  338. * registered.
  339. *
  340. * @param string $prefix The namespace prefix to lookup.
  341. * @param integer $majorVersion The major protocol version in effect.
  342. * Defaults to '1'.
  343. * @param integer $minorVersion The minor protocol version in effect.
  344. * Defaults to null (use latest).
  345. * @return string
  346. */
  347. public function lookupNamespace($prefix,
  348. $majorVersion = 1,
  349. $minorVersion = null)
  350. {
  351. // Check for a memoized result
  352. $key = $prefix . ' ' .
  353. ($majorVersion === null ? 'NULL' : $majorVersion) .
  354. ' '. ($minorVersion === null ? 'NULL' : $minorVersion);
  355. if (array_key_exists($key, self::$_namespaceLookupCache))
  356. return self::$_namespaceLookupCache[$key];
  357. // If no match, return the prefix by default
  358. $result = $prefix;
  359. // Find tuple of keys that correspond to the namespace we should use
  360. if (isset($this->_namespaces[$prefix])) {
  361. // Major version search
  362. $nsData = $this->_namespaces[$prefix];
  363. $foundMajorV = Zend_Gdata_App_Util::findGreatestBoundedValue(
  364. $majorVersion, $nsData);
  365. // Minor version search
  366. $nsData = $nsData[$foundMajorV];
  367. $foundMinorV = Zend_Gdata_App_Util::findGreatestBoundedValue(
  368. $minorVersion, $nsData);
  369. // Extract NS
  370. $result = $nsData[$foundMinorV];
  371. }
  372. // Memoize result
  373. self::$_namespaceLookupCache[$key] = $result;
  374. return $result;
  375. }
  376. /**
  377. * Add a namespace and prefix to the registered list
  378. *
  379. * Takes a prefix and a full namespace URI and adds them to the
  380. * list of registered namespaces for use by
  381. * $this->lookupNamespace().
  382. *
  383. * WARNING: Currently, registering a namespace will NOT invalidate any
  384. * memoized data stored in $_namespaceLookupCache. Under normal
  385. * use, this behavior is acceptable. If you are adding
  386. * contradictory data to the namespace lookup table, you must
  387. * call flushNamespaceLookupCache().
  388. *
  389. * @param string $prefix The namespace prefix
  390. * @param string $namespaceUri The full namespace URI
  391. * @param integer $majorVersion The major protocol version in effect.
  392. * Defaults to '1'.
  393. * @param integer $minorVersion The minor protocol version in effect.
  394. * Defaults to null (use latest).
  395. * @return void
  396. */
  397. public function registerNamespace($prefix,
  398. $namespaceUri,
  399. $majorVersion = 1,
  400. $minorVersion = 0)
  401. {
  402. $this->_namespaces[$prefix][$majorVersion][$minorVersion] =
  403. $namespaceUri;
  404. }
  405. /**
  406. * Flush namespace lookup cache.
  407. *
  408. * Empties the namespace lookup cache. Call this function if you have
  409. * added data to the namespace lookup table that contradicts values that
  410. * may have been cached during a previous call to lookupNamespace().
  411. */
  412. public static function flushNamespaceLookupCache()
  413. {
  414. self::$_namespaceLookupCache = array();
  415. }
  416. /**
  417. * Add an array of namespaces to the registered list.
  418. *
  419. * Takes an array in the format of:
  420. * namespace prefix, namespace URI, major protocol version,
  421. * minor protocol version and adds them with calls to ->registerNamespace()
  422. *
  423. * @param array $namespaceArray An array of namespaces.
  424. * @return void
  425. */
  426. public function registerAllNamespaces($namespaceArray)
  427. {
  428. foreach($namespaceArray as $namespace) {
  429. $this->registerNamespace(
  430. $namespace[0], $namespace[1], $namespace[2], $namespace[3]);
  431. }
  432. }
  433. /**
  434. * Magic getter to allow access like $entry->foo to call $entry->getFoo()
  435. * Alternatively, if no getFoo() is defined, but a $_foo protected variable
  436. * is defined, this is returned.
  437. *
  438. * TODO Remove ability to bypass getFoo() methods??
  439. *
  440. * @param string $name The variable name sought
  441. */
  442. public function __get($name)
  443. {
  444. $method = 'get'.ucfirst($name);
  445. if (method_exists($this, $method)) {
  446. return call_user_func(array(&$this, $method));
  447. } else if (property_exists($this, "_${name}")) {
  448. return $this->{'_' . $name};
  449. } else {
  450. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  451. throw new Zend_Gdata_App_InvalidArgumentException(
  452. 'Property ' . $name . ' does not exist');
  453. }
  454. }
  455. /**
  456. * Magic setter to allow acces like $entry->foo='bar' to call
  457. * $entry->setFoo('bar') automatically.
  458. *
  459. * Alternatively, if no setFoo() is defined, but a $_foo protected variable
  460. * is defined, this is returned.
  461. *
  462. * TODO Remove ability to bypass getFoo() methods??
  463. *
  464. * @param string $name
  465. * @param string $value
  466. */
  467. public function __set($name, $val)
  468. {
  469. $method = 'set'.ucfirst($name);
  470. if (method_exists($this, $method)) {
  471. return call_user_func(array(&$this, $method), $val);
  472. } else if (isset($this->{'_' . $name}) || ($this->{'_' . $name} === null)) {
  473. $this->{'_' . $name} = $val;
  474. } else {
  475. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  476. throw new Zend_Gdata_App_InvalidArgumentException(
  477. 'Property ' . $name . ' does not exist');
  478. }
  479. }
  480. /**
  481. * Magic __isset method
  482. *
  483. * @param string $name
  484. */
  485. public function __isset($name)
  486. {
  487. $rc = new ReflectionClass(get_class($this));
  488. $privName = '_' . $name;
  489. if (!($rc->hasProperty($privName))) {
  490. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  491. throw new Zend_Gdata_App_InvalidArgumentException(
  492. 'Property ' . $name . ' does not exist');
  493. } else {
  494. if (isset($this->{$privName})) {
  495. if (is_array($this->{$privName})) {
  496. if (count($this->{$privName}) > 0) {
  497. return true;
  498. } else {
  499. return false;
  500. }
  501. } else {
  502. return true;
  503. }
  504. } else {
  505. return false;
  506. }
  507. }
  508. }
  509. /**
  510. * Magic __unset method
  511. *
  512. * @param string $name
  513. */
  514. public function __unset($name)
  515. {
  516. if (isset($this->{'_' . $name})) {
  517. if (is_array($this->{'_' . $name})) {
  518. $this->{'_' . $name} = array();
  519. } else {
  520. $this->{'_' . $name} = null;
  521. }
  522. }
  523. }
  524. /**
  525. * Magic toString method allows using this directly via echo
  526. * Works best in PHP >= 4.2.0
  527. *
  528. * @return string The text representation of this object
  529. */
  530. public function __toString()
  531. {
  532. return $this->getText();
  533. }
  534. }