/library/Zend/Gdata/App.php

https://bitbucket.org/hamidrezas/melobit · PHP · 1240 lines · 606 code · 103 blank · 531 comment · 110 complexity · c8d9f478df287fccaad0b1de66ce19bb MD5 · raw file

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