App.php 42 KB

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