PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Zend/Gdata/App.php

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