Base.php 19 KB

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