2
0

App.php 42 KB

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