PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/include/library/Zend/Gdata/App.php

http://becontent.googlecode.com/
PHP | 1146 lines | 539 code | 93 blank | 514 comment | 100 complexity | 143fddce00e647265e838b1f55351ec9 MD5 | raw file
Possible License(s): LGPL-2.1
  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. * Zend_Gdata_Feed
  23. */
  24. require_once 'Zend/Gdata/Feed.php';
  25. /**
  26. * Zend_Gdata_Http_Client
  27. */
  28. require_once 'Zend/Http/Client.php';
  29. /**
  30. * Zend_Version
  31. */
  32. require_once 'Zend/Version.php';
  33. /**
  34. * Zend_Gdata_App_MediaSource
  35. */
  36. require_once 'Zend/Gdata/App/MediaSource.php';
  37. /**
  38. * Provides Atom Publishing Protocol (APP) functionality. This class and all
  39. * other components of Zend_Gdata_App are designed to work independently from
  40. * other Zend_Gdata components in order to interact with generic APP services.
  41. *
  42. * @category Zend
  43. * @package Zend_Gdata
  44. * @subpackage App
  45. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  46. * @license http://framework.zend.com/license/new-bsd New BSD License
  47. */
  48. class Zend_Gdata_App
  49. {
  50. /** Default major protocol version.
  51. *
  52. * @see _majorProtocolVersion
  53. */
  54. const DEFAULT_MAJOR_PROTOCOL_VERSION = 1;
  55. /** Default minor protocol version.
  56. *
  57. * @see _minorProtocolVersion
  58. */
  59. const DEFAULT_MINOR_PROTOCOL_VERSION = null;
  60. /**
  61. * Client object used to communicate
  62. *
  63. * @var Zend_Http_Client
  64. */
  65. protected $_httpClient;
  66. /**
  67. * Client object used to communicate in static context
  68. *
  69. * @var Zend_Http_Client
  70. */
  71. protected static $_staticHttpClient = null;
  72. /**
  73. * Override HTTP PUT and DELETE request methods?
  74. *
  75. * @var boolean
  76. */
  77. protected static $_httpMethodOverride = false;
  78. /**
  79. * Enable gzipped responses?
  80. *
  81. * @var boolean
  82. */
  83. protected static $_gzipEnabled = false;
  84. /**
  85. * Use verbose exception messages. In the case of HTTP errors,
  86. * use the body of the HTTP response in the exception message.
  87. *
  88. * @var boolean
  89. */
  90. protected static $_verboseExceptionMessages = true;
  91. /**
  92. * Default URI to which to POST.
  93. *
  94. * @var string
  95. */
  96. protected $_defaultPostUri = null;
  97. /**
  98. * Packages to search for classes when using magic __call method, in order.
  99. *
  100. * @var array
  101. */
  102. protected $_registeredPackages = array(
  103. 'Zend_Gdata_App_Extension',
  104. 'Zend_Gdata_App');
  105. /**
  106. * Maximum number of redirects to follow during HTTP operations
  107. *
  108. * @var int
  109. */
  110. protected static $_maxRedirects = 5;
  111. /**
  112. * Indicates the major protocol version that should be used.
  113. * At present, recognized values are either 1 or 2. However, any integer
  114. * value >= 1 is considered valid.
  115. *
  116. * Under most circumtances, this will be automatically set by
  117. * Zend_Gdata_App subclasses.
  118. *
  119. * @see setMajorProtocolVersion()
  120. * @see getMajorProtocolVersion()
  121. */
  122. protected $_majorProtocolVersion;
  123. /**
  124. * Indicates the minor protocol version that should be used. Can be set
  125. * to either an integer >= 0, or NULL if no minor version should be sent
  126. * to the server.
  127. *
  128. * At present, this field is not used by any Google services, but may be
  129. * used in the future.
  130. *
  131. * Under most circumtances, this will be automatically set by
  132. * Zend_Gdata_App subclasses.
  133. *
  134. * @see setMinorProtocolVersion()
  135. * @see getMinorProtocolVersion()
  136. */
  137. protected $_minorProtocolVersion;
  138. /**
  139. * Whether we want to use XML to object mapping when fetching data.
  140. *
  141. * @var boolean
  142. */
  143. protected $_useObjectMapping = true;
  144. /**
  145. * Create Gdata object
  146. *
  147. * @param Zend_Http_Client $client
  148. * @param string $applicationId
  149. */
  150. public function __construct($client = null, $applicationId = 'MyCompany-MyApp-1.0')
  151. {
  152. $this->setHttpClient($client, $applicationId);
  153. // Set default protocol version. Subclasses should override this as
  154. // needed once a given service supports a new version.
  155. $this->setMajorProtocolVersion(self::DEFAULT_MAJOR_PROTOCOL_VERSION);
  156. $this->setMinorProtocolVersion(self::DEFAULT_MINOR_PROTOCOL_VERSION);
  157. }
  158. /**
  159. * Adds a Zend Framework package to the $_registeredPackages array.
  160. * This array is searched when using the magic __call method below
  161. * to instantiante new objects.
  162. *
  163. * @param string $name The name of the package (eg Zend_Gdata_App)
  164. * @return void
  165. */
  166. public function registerPackage($name)
  167. {
  168. array_unshift($this->_registeredPackages, $name);
  169. }
  170. /**
  171. * Retrieve feed as string or object
  172. *
  173. * @param string $uri The uri from which to retrieve the feed
  174. * @param string $className The class which is used as the return type
  175. * @return string|Zend_Gdata_App_Feed Returns string only if the object
  176. * mapping has been disabled explicitly
  177. * by passing false to the
  178. * useObjectMapping() function.
  179. */
  180. public function getFeed($uri, $className='Zend_Gdata_App_Feed')
  181. {
  182. return $this->importUrl($uri, $className, null);
  183. }
  184. /**
  185. * Retrieve entry as string or object
  186. *
  187. * @param string $uri
  188. * @param string $className The class which is used as the return type
  189. * @return string|Zend_Gdata_App_Entry Returns string only if the object
  190. * mapping has been disabled explicitly
  191. * by passing false to the
  192. * useObjectMapping() function.
  193. */
  194. public function getEntry($uri, $className='Zend_Gdata_App_Entry')
  195. {
  196. return $this->importUrl($uri, $className, null);
  197. }
  198. /**
  199. * Get the Zend_Http_Client object used for communication
  200. *
  201. * @return Zend_Http_Client
  202. */
  203. public function getHttpClient()
  204. {
  205. return $this->_httpClient;
  206. }
  207. /**
  208. * Set the Zend_Http_Client object used for communication
  209. *
  210. * @param Zend_Http_Client $client The client to use for communication
  211. * @throws Zend_Gdata_App_HttpException
  212. * @return Zend_Gdata_App Provides a fluent interface
  213. */
  214. public function setHttpClient($client, $applicationId = 'MyCompany-MyApp-1.0')
  215. {
  216. if ($client === null) {
  217. $client = new Zend_Http_Client();
  218. }
  219. if (!$client instanceof Zend_Http_Client) {
  220. require_once 'Zend/Gdata/App/HttpException.php';
  221. throw new Zend_Gdata_App_HttpException('Argument is not an instance of Zend_Http_Client.');
  222. }
  223. $userAgent = $applicationId . ' Zend_Framework_Gdata/' . Zend_Version::VERSION;
  224. $client->setHeaders('User-Agent', $userAgent);
  225. $client->setConfig(array(
  226. 'strictredirects' => true
  227. )
  228. );
  229. $this->_httpClient = $client;
  230. Zend_Gdata::setStaticHttpClient($client);
  231. return $this;
  232. }
  233. /**
  234. * Set the static HTTP client instance
  235. *
  236. * Sets the static HTTP client object to use for retrieving the feed.
  237. *
  238. * @param Zend_Http_Client $httpClient
  239. * @return void
  240. */
  241. public static function setStaticHttpClient(Zend_Http_Client $httpClient)
  242. {
  243. self::$_staticHttpClient = $httpClient;
  244. }
  245. /**
  246. * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used.
  247. *
  248. * @return Zend_Http_Client
  249. */
  250. public static function getStaticHttpClient()
  251. {
  252. if (!self::$_staticHttpClient instanceof Zend_Http_Client) {
  253. $client = new Zend_Http_Client();
  254. $userAgent = 'Zend_Framework_Gdata/' . Zend_Version::VERSION;
  255. $client->setHeaders('User-Agent', $userAgent);
  256. $client->setConfig(array(
  257. 'strictredirects' => true
  258. )
  259. );
  260. self::$_staticHttpClient = $client;
  261. }
  262. return self::$_staticHttpClient;
  263. }
  264. /**
  265. * Toggle using POST instead of PUT and DELETE HTTP methods
  266. *
  267. * Some feed implementations do not accept PUT and DELETE HTTP
  268. * methods, or they can't be used because of proxies or other
  269. * measures. This allows turning on using POST where PUT and
  270. * DELETE would normally be used; in addition, an
  271. * X-Method-Override header will be sent with a value of PUT or
  272. * DELETE as appropriate.
  273. *
  274. * @param boolean $override Whether to override PUT and DELETE with POST.
  275. * @return void
  276. */
  277. public static function setHttpMethodOverride($override = true)
  278. {
  279. self::$_httpMethodOverride = $override;
  280. }
  281. /**
  282. * Get the HTTP override state
  283. *
  284. * @return boolean
  285. */
  286. public static function getHttpMethodOverride()
  287. {
  288. return self::$_httpMethodOverride;
  289. }
  290. /**
  291. * Toggle requesting gzip encoded responses
  292. *
  293. * @param boolean $enabled Whether or not to enable gzipped responses
  294. * @return void
  295. */
  296. public static function setGzipEnabled($enabled = false)
  297. {
  298. if ($enabled && !function_exists('gzinflate')) {
  299. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  300. throw new Zend_Gdata_App_InvalidArgumentException(
  301. 'You cannot enable gzipped responses if the zlib module ' .
  302. 'is not enabled in your PHP installation.');
  303. }
  304. self::$_gzipEnabled = $enabled;
  305. }
  306. /**
  307. * Get the HTTP override state
  308. *
  309. * @return boolean
  310. */
  311. public static function getGzipEnabled()
  312. {
  313. return self::$_gzipEnabled;
  314. }
  315. /**
  316. * Get whether to use verbose exception messages
  317. *
  318. * In the case of HTTP errors, use the body of the HTTP response
  319. * in the exception message.
  320. *
  321. * @return boolean
  322. */
  323. public static function getVerboseExceptionMessages()
  324. {
  325. return self::$_verboseExceptionMessages;
  326. }
  327. /**
  328. * Set whether to use verbose exception messages
  329. *
  330. * In the case of HTTP errors, use the body of the HTTP response
  331. * in the exception message.
  332. *
  333. * @param boolean $verbose Whether to use verbose exception messages
  334. */
  335. public static function setVerboseExceptionMessages($verbose)
  336. {
  337. self::$_verboseExceptionMessages = $verbose;
  338. }
  339. /**
  340. * Set the maximum number of redirects to follow during HTTP operations
  341. *
  342. * @param int $maxRedirects Maximum number of redirects to follow
  343. * @return void
  344. */
  345. public static function setMaxRedirects($maxRedirects)
  346. {
  347. self::$_maxRedirects = $maxRedirects;
  348. }
  349. /**
  350. * Get the maximum number of redirects to follow during HTTP operations
  351. *
  352. * @return int Maximum number of redirects to follow
  353. */
  354. public static function getMaxRedirects()
  355. {
  356. return self::$_maxRedirects;
  357. }
  358. /**
  359. * Set the major protocol version that should be used. Values < 1 will
  360. * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
  361. *
  362. * @see _majorProtocolVersion
  363. * @param int $value The major protocol version to use.
  364. * @throws Zend_Gdata_App_InvalidArgumentException
  365. */
  366. public function setMajorProtocolVersion($value)
  367. {
  368. if (!($value >= 1)) {
  369. require_once('Zend/Gdata/App/InvalidArgumentException.php');
  370. throw new Zend_Gdata_App_InvalidArgumentException(
  371. 'Major protocol version must be >= 1');
  372. }
  373. $this->_majorProtocolVersion = $value;
  374. }
  375. /**
  376. * Get the major protocol version that is in use.
  377. *
  378. * @see _majorProtocolVersion
  379. * @return int The major protocol version in use.
  380. */
  381. public function getMajorProtocolVersion()
  382. {
  383. return $this->_majorProtocolVersion;
  384. }
  385. /**
  386. * Set the minor protocol version that should be used. If set to NULL, no
  387. * minor protocol version will be sent to the server. Values < 0 will
  388. * cause a Zend_Gdata_App_InvalidArgumentException to be thrown.
  389. *
  390. * @see _minorProtocolVersion
  391. * @param (int|NULL) $value The minor protocol version to use.
  392. * @throws Zend_Gdata_App_InvalidArgumentException
  393. */
  394. public function setMinorProtocolVersion($value)
  395. {
  396. if (!($value >= 0)) {
  397. require_once('Zend/Gdata/App/InvalidArgumentException.php');
  398. throw new Zend_Gdata_App_InvalidArgumentException(
  399. 'Minor protocol version must be >= 0');
  400. }
  401. $this->_minorProtocolVersion = $value;
  402. }
  403. /**
  404. * Get the minor protocol version that is in use.
  405. *
  406. * @see _minorProtocolVersion
  407. * @return (int|NULL) The major protocol version in use, or NULL if no
  408. * minor version is specified.
  409. */
  410. public function getMinorProtocolVersion()
  411. {
  412. return $this->_minorProtocolVersion;
  413. }
  414. /**
  415. * Provides pre-processing for HTTP requests to APP services.
  416. *
  417. * 1. Checks the $data element and, if it's an entry, extracts the XML,
  418. * multipart data, edit link (PUT,DELETE), etc.
  419. * 2. If $data is a string, sets the default content-type header as
  420. * 'application/atom+xml' if it's not already been set.
  421. * 3. Adds a x-http-method override header and changes the HTTP method
  422. * to 'POST' if necessary as per getHttpMethodOverride()
  423. *
  424. * @param string $method The HTTP method for the request - 'GET', 'POST',
  425. * 'PUT', 'DELETE'
  426. * @param string $url The URL to which this request is being performed,
  427. * or null if found in $data
  428. * @param array $headers An associative array of HTTP headers for this
  429. * request
  430. * @param mixed $data The Zend_Gdata_App_Entry or XML for the
  431. * body of the request
  432. * @param string $contentTypeOverride The override value for the
  433. * content type of the request body
  434. * @return array An associative array containing the determined
  435. * 'method', 'url', 'data', 'headers', 'contentType'
  436. */
  437. public function prepareRequest($method,
  438. $url = null,
  439. $headers = array(),
  440. $data = null,
  441. $contentTypeOverride = null)
  442. {
  443. // As a convenience, if $headers is null, we'll convert it back to
  444. // an empty array.
  445. if (is_null($headers)) {
  446. $headers = array();
  447. }
  448. $rawData = null;
  449. $finalContentType = null;
  450. if ($url == null) {
  451. $url = $this->_defaultPostUri;
  452. }
  453. if (is_string($data)) {
  454. $rawData = $data;
  455. if ($contentTypeOverride === null) {
  456. $finalContentType = 'application/atom+xml';
  457. }
  458. } elseif ($data instanceof Zend_Gdata_App_MediaEntry) {
  459. $rawData = $data->encode();
  460. if ($data->getMediaSource() !== null) {
  461. $finalContentType = 'multipart/related; boundary="' .
  462. $data->getBoundary() . '"';
  463. $headers['MIME-version'] = '1.0';
  464. $headers['Slug'] = $data->getMediaSource()->getSlug();
  465. } else {
  466. $finalContentType = 'application/atom+xml';
  467. }
  468. if ($method == 'PUT' || $method == 'DELETE') {
  469. $editLink = $data->getEditLink();
  470. if ($editLink != null) {
  471. $url = $editLink->getHref();
  472. }
  473. }
  474. } elseif ($data instanceof Zend_Gdata_App_Entry) {
  475. $rawData = $data->saveXML();
  476. $finalContentType = 'application/atom+xml';
  477. if ($method == 'PUT' || $method == 'DELETE') {
  478. $editLink = $data->getEditLink();
  479. if ($editLink != null) {
  480. $url = $editLink->getHref();
  481. }
  482. }
  483. } elseif ($data instanceof Zend_Gdata_App_MediaSource) {
  484. $rawData = $data->encode();
  485. if ($data->getSlug() !== null) {
  486. $headers['Slug'] = $data->getSlug();
  487. }
  488. $finalContentType = $data->getContentType();
  489. }
  490. if ($method == 'DELETE') {
  491. $rawData = null;
  492. }
  493. // Set an If-Match header if:
  494. // - This isn't a DELETE
  495. // - If this isn't a GET, the Etag isn't weak
  496. // - A similar header (If-Match/If-None-Match) hasn't already been
  497. // set.
  498. if ($method != 'DELETE' && (
  499. !array_key_exists('If-Match', $headers) &&
  500. !array_key_exists('If-None-Match', $headers)
  501. ) ) {
  502. $allowWeak = $method == 'GET';
  503. if ($ifMatchHeader = $this->generateIfMatchHeaderData(
  504. $data, $allowWeak)) {
  505. $headers['If-Match'] = $ifMatchHeader;
  506. }
  507. }
  508. if ($method != 'POST' && $method != 'GET' && Zend_Gdata_App::getHttpMethodOverride()) {
  509. $headers['x-http-method-override'] = $method;
  510. $method = 'POST';
  511. } else {
  512. $headers['x-http-method-override'] = null;
  513. }
  514. if ($contentTypeOverride != null) {
  515. $finalContentType = $contentTypeOverride;
  516. }
  517. return array('method' => $method, 'url' => $url,
  518. 'data' => $rawData, 'headers' => $headers,
  519. 'contentType' => $finalContentType);
  520. }
  521. /**
  522. * Performs a HTTP request using the specified method
  523. *
  524. * @param string $method The HTTP method for the request - 'GET', 'POST',
  525. * 'PUT', 'DELETE'
  526. * @param string $url The URL to which this request is being performed
  527. * @param array $headers An associative array of HTTP headers
  528. * for this request
  529. * @param string $body The body of the HTTP request
  530. * @param string $contentType The value for the content type
  531. * of the request body
  532. * @param int $remainingRedirects Number of redirects to follow if request
  533. * s results in one
  534. * @return Zend_Http_Response The response object
  535. */
  536. public function performHttpRequest($method, $url, $headers = null,
  537. $body = null, $contentType = null, $remainingRedirects = null)
  538. {
  539. require_once 'Zend/Http/Client/Exception.php';
  540. if ($remainingRedirects === null) {
  541. $remainingRedirects = self::getMaxRedirects();
  542. }
  543. if ($headers === null) {
  544. $headers = array();
  545. }
  546. // Append a Gdata version header if protocol v2 or higher is in use.
  547. // (Protocol v1 does not use this header.)
  548. $major = $this->getMajorProtocolVersion();
  549. $minor = $this->getMinorProtocolVersion();
  550. if ($major >= 2) {
  551. $headers['GData-Version'] = $major +
  552. (is_null($minor) ? '.' + $minor : '');
  553. }
  554. // check the overridden method
  555. if (($method == 'POST' || $method == 'PUT') && $body === null && $headers['x-http-method-override'] != 'DELETE') {
  556. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  557. throw new Zend_Gdata_App_InvalidArgumentException(
  558. 'You must specify the data to post as either a ' .
  559. 'string or a child of Zend_Gdata_App_Entry');
  560. }
  561. if ($url === null) {
  562. require_once 'Zend/Gdata/App/InvalidArgumentException.php';
  563. throw new Zend_Gdata_App_InvalidArgumentException('You must specify an URI to which to post.');
  564. }
  565. $headers['Content-Type'] = $contentType;
  566. if (Zend_Gdata_App::getGzipEnabled()) {
  567. // some services require the word 'gzip' to be in the user-agent header
  568. // in addition to the accept-encoding header
  569. if (strpos($this->_httpClient->getHeader('User-Agent'), 'gzip') === false) {
  570. $headers['User-Agent'] = $this->_httpClient->getHeader('User-Agent') . ' (gzip)';
  571. }
  572. $headers['Accept-encoding'] = 'gzip, deflate';
  573. } else {
  574. $headers['Accept-encoding'] = 'identity';
  575. }
  576. // Make sure the HTTP client object is 'clean' before making a request
  577. // In addition to standard headers to reset via resetParameters(),
  578. // also reset the Slug header
  579. $this->_httpClient->resetParameters();
  580. $this->_httpClient->setHeaders('Slug', null);
  581. // Set the params for the new request to be performed
  582. $this->_httpClient->setHeaders($headers);
  583. $this->_httpClient->setUri($url);
  584. $this->_httpClient->setConfig(array('maxredirects' => 0));
  585. $this->_httpClient->setRawData($body, $contentType);
  586. try {
  587. $response = $this->_httpClient->request($method);
  588. } catch (Zend_Http_Client_Exception $e) {
  589. require_once 'Zend/Gdata/App/HttpException.php';
  590. throw new Zend_Gdata_App_HttpException($e->getMessage(), $e);
  591. }
  592. if ($response->isRedirect() && $response->getStatus() != '304') {
  593. if ($remainingRedirects > 0) {
  594. $newUrl = $response->getHeader('Location');
  595. $response = $this->performHttpRequest($method, $newUrl, $headers, $body, $contentType, $remainingRedirects);
  596. } else {
  597. require_once 'Zend/Gdata/App/HttpException.php';
  598. throw new Zend_Gdata_App_HttpException(
  599. 'Number of redirects exceeds maximum', null, $response);
  600. }
  601. }
  602. if (!$response->isSuccessful()) {
  603. require_once 'Zend/Gdata/App/HttpException.php';
  604. $exceptionMessage = 'Expected response code 200, got ' . $response->getStatus();
  605. if (self::getVerboseExceptionMessages()) {
  606. $exceptionMessage .= "\n" . $response->getBody();
  607. }
  608. $exception = new Zend_Gdata_App_HttpException($exceptionMessage);
  609. $exception->setResponse($response);
  610. throw $exception;
  611. }
  612. return $response;
  613. }
  614. /**
  615. * Imports a feed located at $uri.
  616. *
  617. * @param string $uri
  618. * @param Zend_Http_Client $client The client used for communication
  619. * @param string $className The class which is used as the return type
  620. * @throws Zend_Gdata_App_Exception
  621. * @return string|Zend_Gdata_App_Feed Returns string only if the object
  622. * mapping has been disabled explicitly
  623. * by passing false to the
  624. * useObjectMapping() function.
  625. */
  626. public static function import($uri, $client = null,
  627. $className='Zend_Gdata_App_Feed')
  628. {
  629. $app = new Zend_Gdata_App($client);
  630. $requestData = $app->prepareRequest('GET', $uri);
  631. $response = $app->performHttpRequest($requestData['method'], $requestData['url']);
  632. $feedContent = $response->getBody();
  633. if (!$this->_useObjectMapping) {
  634. return $feedContent;
  635. }
  636. $feed = self::importString($feedContent, $className);
  637. if ($client != null) {
  638. $feed->setHttpClient($client);
  639. }
  640. return $feed;
  641. }
  642. /**
  643. * Imports the specified URL (non-statically).
  644. *
  645. * @param string $url The URL to import
  646. * @param string $className The class which is used as the return type
  647. * @param array $extraHeaders Extra headers to add to the request, as an
  648. * array of string-based key/value pairs.
  649. * @throws Zend_Gdata_App_Exception
  650. * @return string|Zend_Gdata_App_Feed Returns string only if the object
  651. * mapping has been disabled explicitly
  652. * by passing false to the
  653. * useObjectMapping() function.
  654. */
  655. public function importUrl($url, $className='Zend_Gdata_App_Feed',
  656. $extraHeaders = array())
  657. {
  658. $response = $this->get($url, $extraHeaders);
  659. $feedContent = $response->getBody();
  660. if (!$this->_useObjectMapping) {
  661. return $feedContent;
  662. }
  663. $feed = self::importString($feedContent, $className);
  664. $etag = $response->getHeader('ETag');
  665. if (!is_null($etag)) {
  666. $feed->setEtag($etag);
  667. }
  668. $protocolVersionStr = $response->getHeader('GData-Version');
  669. if (!is_null($protocolVersionStr)) {
  670. // Extract protocol major and minor version from header
  671. $delimiterPos = strpos($protocolVersionStr, '.');
  672. $length = strlen($protocolVersionStr);
  673. $major = substr($protocolVersionStr,
  674. 0,
  675. $delimiterPos);
  676. $minor = substr($protocolVersionStr,
  677. $delimiterPos + 1,
  678. $length);
  679. $feed->setMajorProtocolVersion($major);
  680. $feed->setMinorProtocolVersion($minor);
  681. } else {
  682. $feed->setMajorProtocolVersion(null);
  683. $feed->setMinorProtocolVersion(null);
  684. }
  685. if ($this->getHttpClient() != null) {
  686. $feed->setHttpClient($this->getHttpClient());
  687. }
  688. return $feed;
  689. }
  690. /**
  691. * Imports a feed represented by $string.
  692. *
  693. * @param string $string
  694. * @param string $className The class which is used as the return type
  695. * @throws Zend_Gdata_App_Exception
  696. * @return Zend_Gdata_App_Feed
  697. */
  698. public static function importString($string,
  699. $className='Zend_Gdata_App_Feed')
  700. {
  701. // Load the feed as an XML DOMDocument object
  702. @ini_set('track_errors', 1);
  703. $doc = new DOMDocument();
  704. $success = @$doc->loadXML($string);
  705. @ini_restore('track_errors');
  706. if (!$success) {
  707. require_once 'Zend/Gdata/App/Exception.php';
  708. throw new Zend_Gdata_App_Exception("DOMDocument cannot parse XML: $php_errormsg");
  709. }
  710. $feed = new $className($string);
  711. $feed->setHttpClient(self::getstaticHttpClient());
  712. return $feed;
  713. }
  714. /**
  715. * Imports a feed from a file located at $filename.
  716. *
  717. * @param string $filename
  718. * @param string $className The class which is used as the return type
  719. * @param string $useIncludePath Whether the include_path should be searched
  720. * @throws Zend_Gdata_App_Exception
  721. * @return Zend_Gdata_Feed
  722. */
  723. public static function importFile($filename,
  724. $className='Zend_Gdata_App_Feed', $useIncludePath = false)
  725. {
  726. @ini_set('track_errors', 1);
  727. $feed = @file_get_contents($filename, $useIncludePath);
  728. @ini_restore('track_errors');
  729. if ($feed === false) {
  730. require_once 'Zend/Gdata/App/Exception.php';
  731. throw new Zend_Gdata_App_Exception("File could not be loaded: $php_errormsg");
  732. }
  733. return self::importString($feed, $className);
  734. }
  735. /**
  736. * GET a URI using client object.
  737. *
  738. * @param string $uri GET URI
  739. * @param array $extraHeaders Extra headers to add to the request, as an
  740. * array of string-based key/value pairs.
  741. * @throws Zend_Gdata_App_HttpException
  742. * @return Zend_Http_Response
  743. */
  744. public function get($uri, $extraHeaders = array())
  745. {
  746. $requestData = $this->prepareRequest('GET', $uri, $extraHeaders);
  747. return $this->performHttpRequest($requestData['method'], $requestData['url'], $requestData['headers']);
  748. }
  749. /**
  750. * POST data with client object
  751. *
  752. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  753. * @param string $uri POST URI
  754. * @param array $headers Additional HTTP headers to insert.
  755. * @param string $contentType Content-type of the data
  756. * @param array $extraHeaders Extra headers to add to the request, as an
  757. * array of string-based key/value pairs.
  758. * @return Zend_Http_Response
  759. * @throws Zend_Gdata_App_Exception
  760. * @throws Zend_Gdata_App_HttpException
  761. * @throws Zend_Gdata_App_InvalidArgumentException
  762. */
  763. public function post($data, $uri = null, $remainingRedirects = null,
  764. $contentType = null, $extraHeaders = null)
  765. {
  766. $requestData = $this->prepareRequest('POST', $uri, $extraHeaders,
  767. $data, $contentType);
  768. return $this->performHttpRequest(
  769. $requestData['method'], $requestData['url'],
  770. $requestData['headers'], $requestData['data'],
  771. $requestData['contentType']);
  772. }
  773. /**
  774. * PUT data with client object
  775. *
  776. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  777. * @param string $uri PUT URI
  778. * @param array $headers Additional HTTP headers to insert.
  779. * @param string $contentType Content-type of the data
  780. * @param array $extraHeaders Extra headers to add to the request, as an
  781. * array of string-based key/value pairs.
  782. * @return Zend_Http_Response
  783. * @throws Zend_Gdata_App_Exception
  784. * @throws Zend_Gdata_App_HttpException
  785. * @throws Zend_Gdata_App_InvalidArgumentException
  786. */
  787. public function put($data, $uri = null, $remainingRedirects = null,
  788. $contentType = null, $extraHeaders = null)
  789. {
  790. $requestData = $this->prepareRequest('PUT', $uri, $extraHeaders, $data, $contentType);
  791. return $this->performHttpRequest(
  792. $requestData['method'], $requestData['url'],
  793. $requestData['headers'], $requestData['data'],
  794. $requestData['contentType']);
  795. }
  796. /**
  797. * DELETE entry with client object
  798. *
  799. * @param mixed $data The Zend_Gdata_App_Entry or URL to delete
  800. * @return void
  801. * @throws Zend_Gdata_App_Exception
  802. * @throws Zend_Gdata_App_HttpException
  803. * @throws Zend_Gdata_App_InvalidArgumentException
  804. */
  805. public function delete($data, $remainingRedirects = null)
  806. {
  807. if (is_string($data)) {
  808. $requestData = $this->prepareRequest('DELETE', $data);
  809. } else {
  810. $headers = array();
  811. $requestData = $this->prepareRequest('DELETE', null, $headers, $data);
  812. }
  813. return $this->performHttpRequest($requestData['method'],
  814. $requestData['url'],
  815. $requestData['headers'],
  816. '',
  817. $requestData['contentType'],
  818. $remainingRedirects);
  819. }
  820. /**
  821. * Inserts an entry to a given URI and returns the response as a fully formed Entry.
  822. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  823. * @param string $uri POST URI
  824. * @param string $className The class of entry to be returned.
  825. * @param array $extraHeaders Extra headers to add to the request, as an
  826. * array of string-based key/value pairs.
  827. * @return Zend_Gdata_App_Entry The entry returned by the service after insertion.
  828. */
  829. public function insertEntry($data, $uri, $className='Zend_Gdata_App_Entry', $extraHeaders = array())
  830. {
  831. $response = $this->post($data, $uri, null, null, $extraHeaders);
  832. $returnEntry = new $className($response->getBody());
  833. $returnEntry->setHttpClient(self::getstaticHttpClient());
  834. $etag = $response->getHeader('ETag');
  835. if (!is_null($etag)) {
  836. $returnEntry->setEtag($etag);
  837. }
  838. return $returnEntry;
  839. }
  840. /**
  841. * Update an entry
  842. *
  843. * @param mixed $data Zend_Gdata_App_Entry or XML (w/ID and link rel='edit')
  844. * @param string|null The URI to send requests to, or null if $data
  845. * contains the URI.
  846. * @param string|null The name of the class that should be deserialized
  847. * from the server response. If null, then 'Zend_Gdata_App_Entry'
  848. * will be used.
  849. * @param array $extraHeaders Extra headers to add to the request, as an
  850. * array of string-based key/value pairs.
  851. * @return Zend_Gdata_App_Entry The entry returned from the server
  852. * @throws Zend_Gdata_App_Exception
  853. */
  854. public function updateEntry($data, $uri = null, $className = null, $extraHeaders = array())
  855. {
  856. if ($className === null && $data instanceof Zend_Gdata_App_Entry) {
  857. $className = get_class($data);
  858. } elseif ($className === null) {
  859. $className = 'Zend_Gdata_App_Entry';
  860. }
  861. $response = $this->put($data, $uri, null, null, $extraHeaders);
  862. $returnEntry = new $className($response->getBody());
  863. $returnEntry->setHttpClient(self::getstaticHttpClient());
  864. $etag = $response->getHeader('ETag');
  865. if (!is_null($etag)) {
  866. $returnEntry->setEtag($etag);
  867. }
  868. return $returnEntry;
  869. }
  870. /**
  871. * Provides a magic factory method to instantiate new objects with
  872. * shorter syntax than would otherwise be required by the Zend Framework
  873. * naming conventions. For instance, to construct a new
  874. * Zend_Gdata_Calendar_Extension_Color, a developer simply needs to do
  875. * $gCal->newColor(). For this magic constructor, packages are searched
  876. * in the same order as which they appear in the $_registeredPackages
  877. * array
  878. *
  879. * @param string $method The method name being called
  880. * @param array $args The arguments passed to the call
  881. * @throws Zend_Gdata_App_Exception
  882. */
  883. public function __call($method, $args)
  884. {
  885. if (preg_match('/^new(\w+)/', $method, $matches)) {
  886. $class = $matches[1];
  887. $foundClassName = null;
  888. foreach ($this->_registeredPackages as $name) {
  889. try {
  890. @Zend_Loader::loadClass("${name}_${class}");
  891. $foundClassName = "${name}_${class}";
  892. break;
  893. } catch (Zend_Exception $e) {
  894. // package wasn't here- continue searching
  895. }
  896. }
  897. if ($foundClassName != null) {
  898. $reflectionObj = new ReflectionClass($foundClassName);
  899. $instance = $reflectionObj->newInstanceArgs($args);
  900. if ($instance instanceof Zend_Gdata_App_FeedEntryParent) {
  901. $instance->setHttpClient($this->_httpClient);
  902. }
  903. return $instance;
  904. } else {
  905. require_once 'Zend/Gdata/App/Exception.php';
  906. throw new Zend_Gdata_App_Exception(
  907. "Unable to find '${class}' in registered packages");
  908. }
  909. } else {
  910. require_once 'Zend/Gdata/App/Exception.php';
  911. throw new Zend_Gdata_App_Exception("No such method ${method}");
  912. }
  913. }
  914. /**
  915. * Retrieve all entries for a feed, iterating through pages as necessary.
  916. * Be aware that calling this function on a large dataset will take a
  917. * significant amount of time to complete. In some cases this may cause
  918. * execution to timeout without proper precautions in place.
  919. *
  920. * @param $feed The feed to iterate through.
  921. * @return mixed A new feed of the same type as the one originally
  922. * passed in, containing all relevent entries.
  923. */
  924. public function retrieveAllEntriesForFeed($feed) {
  925. $feedClass = get_class($feed);
  926. $reflectionObj = new ReflectionClass($feedClass);
  927. $result = $reflectionObj->newInstance();
  928. do {
  929. foreach ($feed as $entry) {
  930. $result->addEntry($entry);
  931. }
  932. $next = $feed->getLink('next');
  933. if ($next !== null) {
  934. $feed = $this->getFeed($next->href, $feedClass);
  935. } else {
  936. $feed = null;
  937. }
  938. }
  939. while ($feed != null);
  940. return $result;
  941. }
  942. /**
  943. * This method enables logging of requests by changing the
  944. * Zend_Http_Client_Adapter used for performing the requests.
  945. * NOTE: This will not work if you have customized the adapter
  946. * already to use a proxy server or other interface.
  947. *
  948. * @param $logfile The logfile to use when logging the requests
  949. */
  950. public function enableRequestDebugLogging($logfile)
  951. {
  952. $this->_httpClient->setConfig(array(
  953. 'adapter' => 'Zend_Gdata_App_LoggingHttpClientAdapterSocket',
  954. 'logfile' => $logfile
  955. ));
  956. }
  957. /**
  958. * Retrieve next set of results based on a given feed.
  959. *
  960. * @param Zend_Gdata_App_Feed $feed The feed from which to
  961. * retreive the next set of results.
  962. * @param string $className (optional) The class of feed to be returned.
  963. * If null, the next feed (if found) will be the same class as
  964. * the feed that was given as the first argument.
  965. * @return Zend_Gdata_App_Feed|null Returns a
  966. * Zend_Gdata_App_Feed or null if no next set of results
  967. * exists.
  968. */
  969. public function getNextFeed($feed, $className = null)
  970. {
  971. $nextLink = $feed->getNextLink();
  972. if (!$nextLink) {
  973. return null;
  974. }
  975. $nextLinkHref = $nextLink->getHref();
  976. if (is_null($className)) {
  977. $className = get_class($feed);
  978. }
  979. return $this->getFeed($nextLinkHref, $className);
  980. }
  981. /**
  982. * Retrieve previous set of results based on a given feed.
  983. *
  984. * @param Zend_Gdata_App_Feed $feed The feed from which to
  985. * retreive the previous set of results.
  986. * @param string $className (optional) The class of feed to be returned.
  987. * If null, the previous feed (if found) will be the same class as
  988. * the feed that was given as the first argument.
  989. * @return Zend_Gdata_App_Feed|null Returns a
  990. * Zend_Gdata_App_Feed or null if no previous set of results
  991. * exists.
  992. */
  993. public function getPreviousFeed($feed, $className = null)
  994. {
  995. $previousLink = $feed->getPreviousLink();
  996. if (!$previousLink) {
  997. return null;
  998. }
  999. $previousLinkHref = $previousLink->getHref();
  1000. if (is_null($className)) {
  1001. $className = get_class($feed);
  1002. }
  1003. return $this->getFeed($previousLinkHref, $className);
  1004. }
  1005. /**
  1006. * Returns the data for an If-Match header based on the current Etag
  1007. * property. If Etags are not supported by the server or cannot be
  1008. * extracted from the data, then null will be returned.
  1009. *
  1010. * @param boolean $allowWeak If false, then if a weak Etag is detected,
  1011. * then return null rather than the Etag.
  1012. * @return string|null $data
  1013. */
  1014. public function generateIfMatchHeaderData($data, $allowWeek)
  1015. {
  1016. $result = '';
  1017. // Set an If-Match header if an ETag has been set (version >= 2 only)
  1018. if ($this->_majorProtocolVersion >= 2 &&
  1019. $data instanceof Zend_Gdata_App_Entry) {
  1020. $etag = $data->getEtag();
  1021. if (!is_null($etag) &&
  1022. ($allowWeek || substr($etag, 0, 2) != 'W/')) {
  1023. $result = $data->getEtag();
  1024. }
  1025. }
  1026. return $result;
  1027. }
  1028. /**
  1029. * Determine whether service object is using XML to object mapping.
  1030. *
  1031. * @return boolean True if service object is using XML to object mapping,
  1032. * false otherwise.
  1033. */
  1034. public function usingObjectMapping()
  1035. {
  1036. return $this->_useObjectMapping;
  1037. }
  1038. /**
  1039. * Enable/disable the use of XML to object mapping.
  1040. *
  1041. * @param boolean $value Pass in true to use the XML to object mapping.
  1042. * Pass in false or null to disable it.
  1043. * @return void
  1044. */
  1045. public function useObjectMapping($value)
  1046. {
  1047. if ($value === True) {
  1048. $this->_useObjectMapping = true;
  1049. } else {
  1050. $this->_useObjectMapping = false;
  1051. }
  1052. }
  1053. }