App.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  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-2010 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-2010 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. self::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 and If-Match headers
  586. $this->_httpClient->resetParameters();
  587. $this->_httpClient->setHeaders(array('Slug', 'If-Match'));
  588. // Set the params for the new request to be performed
  589. $this->_httpClient->setHeaders($headers);
  590. require_once 'Zend/Uri/Http.php';
  591. $uri = Zend_Uri_Http::fromString($url);
  592. preg_match("/^(.*?)(\?.*)?$/", $url, $matches);
  593. $this->_httpClient->setUri($matches[1]);
  594. $queryArray = $uri->getQueryAsArray();
  595. foreach ($queryArray as $name => $value) {
  596. $this->_httpClient->setParameterGet($name, $value);
  597. }
  598. $this->_httpClient->setConfig(array('maxredirects' => 0));
  599. // Set the proper adapter if we are handling a streaming upload
  600. $usingMimeStream = false;
  601. $oldHttpAdapter = null;
  602. if ($body instanceof Zend_Gdata_MediaMimeStream) {
  603. $usingMimeStream = true;
  604. $this->_httpClient->setRawDataStream($body, $contentType);
  605. $oldHttpAdapter = $this->_httpClient->getAdapter();
  606. if ($oldHttpAdapter instanceof Zend_Http_Client_Adapter_Proxy) {
  607. require_once 'Zend/Gdata/HttpAdapterStreamingProxy.php';
  608. $newAdapter = new Zend_Gdata_HttpAdapterStreamingProxy();
  609. } else {
  610. require_once 'Zend/Gdata/HttpAdapterStreamingSocket.php';
  611. $newAdapter = new Zend_Gdata_HttpAdapterStreamingSocket();
  612. }
  613. $this->_httpClient->setAdapter($newAdapter);
  614. } else {
  615. $this->_httpClient->setRawData($body, $contentType);
  616. }
  617. try {
  618. $response = $this->_httpClient->request($method);
  619. // reset adapter
  620. if ($usingMimeStream) {
  621. $this->_httpClient->setAdapter($oldHttpAdapter);
  622. }
  623. } catch (Zend_Http_Client_Exception $e) {
  624. // reset adapter
  625. if ($usingMimeStream) {
  626. $this->_httpClient->setAdapter($oldHttpAdapter);
  627. }
  628. require_once 'Zend/Gdata/App/HttpException.php';
  629. throw new Zend_Gdata_App_HttpException($e->getMessage(), $e);
  630. }
  631. if ($response->isRedirect() && $response->getStatus() != '304') {
  632. if ($remainingRedirects > 0) {
  633. $newUrl = $response->getHeader('Location');
  634. $response = $this->performHttpRequest(
  635. $method, $newUrl, $headers, $body,
  636. $contentType, $remainingRedirects);
  637. } else {
  638. require_once 'Zend/Gdata/App/HttpException.php';
  639. throw new Zend_Gdata_App_HttpException(
  640. 'Number of redirects exceeds maximum', null, $response);
  641. }
  642. }
  643. if (!$response->isSuccessful()) {
  644. require_once 'Zend/Gdata/App/HttpException.php';
  645. $exceptionMessage = 'Expected response code 200, got ' .
  646. $response->getStatus();
  647. if (self::getVerboseExceptionMessages()) {
  648. $exceptionMessage .= "\n" . $response->getBody();
  649. }
  650. $exception = new Zend_Gdata_App_HttpException($exceptionMessage);
  651. $exception->setResponse($response);
  652. throw $exception;
  653. }
  654. return $response;
  655. }
  656. /**
  657. * Imports a feed located at $uri.
  658. *
  659. * @param string $uri
  660. * @param Zend_Http_Client $client The client used for communication
  661. * @param string $className The class which is used as the return type
  662. * @throws Zend_Gdata_App_Exception
  663. * @return string|Zend_Gdata_App_Feed Returns string only if the object
  664. * mapping has been disabled explicitly
  665. * by passing false to the
  666. * useObjectMapping() function.
  667. */
  668. public static function import($uri, $client = null,
  669. $className='Zend_Gdata_App_Feed')
  670. {
  671. $app = new Zend_Gdata_App($client);
  672. $requestData = $app->prepareRequest('GET', $uri);
  673. $response = $app->performHttpRequest(
  674. $requestData['method'], $requestData['url']);
  675. $feedContent = $response->getBody();
  676. if (!$this->_useObjectMapping) {
  677. return $feedContent;
  678. }
  679. $feed = self::importString($feedContent, $className);
  680. if ($client != null) {
  681. $feed->setHttpClient($client);
  682. }
  683. return $feed;
  684. }
  685. /**
  686. * Imports the specified URL (non-statically).
  687. *
  688. * @param string $url The URL to import
  689. * @param string $className The class which is used as the return type
  690. * @param array $extraHeaders Extra headers to add to the request, as an
  691. * array of string-based key/value pairs.
  692. * @throws Zend_Gdata_App_Exception
  693. * @return string|Zend_Gdata_App_Feed Returns string only if the object
  694. * mapping has been disabled explicitly
  695. * by passing false to the
  696. * useObjectMapping() function.
  697. */
  698. public function importUrl($url, $className='Zend_Gdata_App_Feed',
  699. $extraHeaders = array())
  700. {
  701. $response = $this->get($url, $extraHeaders);
  702. $feedContent = $response->getBody();
  703. if (!$this->_useObjectMapping) {
  704. return $feedContent;
  705. }
  706. $protocolVersionStr = $response->getHeader('GData-Version');
  707. $majorProtocolVersion = null;
  708. $minorProtocolVersion = null;
  709. if ($protocolVersionStr !== null) {
  710. // Extract protocol major and minor version from header
  711. $delimiterPos = strpos($protocolVersionStr, '.');
  712. $length = strlen($protocolVersionStr);
  713. $major = substr($protocolVersionStr, 0, $delimiterPos);
  714. $minor = substr($protocolVersionStr, $delimiterPos + 1, $length);
  715. $majorProtocolVersion = $major;
  716. $minorProtocolVersion = $minor;
  717. }
  718. $feed = self::importString($feedContent, $className,
  719. $majorProtocolVersion, $minorProtocolVersion);
  720. if ($this->getHttpClient() != null) {
  721. $feed->setHttpClient($this->getHttpClient());
  722. }
  723. $etag = $response->getHeader('ETag');
  724. if ($etag !== null) {
  725. $feed->setEtag($etag);
  726. }
  727. return $feed;
  728. }
  729. /**
  730. * Imports a feed represented by $string.
  731. *
  732. * @param string $string
  733. * @param string $className The class which is used as the return type
  734. * @param integer $majorProcolVersion (optional) The major protocol version
  735. * of the data model object that is to be created.
  736. * @param integer $minorProcolVersion (optional) The minor protocol version
  737. * of the data model object that is to be created.
  738. * @throws Zend_Gdata_App_Exception
  739. * @return Zend_Gdata_App_Feed
  740. */
  741. public static function importString($string,
  742. $className='Zend_Gdata_App_Feed', $majorProtocolVersion = null,
  743. $minorProtocolVersion = null)
  744. {
  745. if (!class_exists($className, false)) {
  746. require_once 'Zend/Loader.php';
  747. @Zend_Loader::loadClass($className);
  748. }
  749. // Load the feed as an XML DOMDocument object
  750. @ini_set('track_errors', 1);
  751. $doc = new DOMDocument();
  752. $success = @$doc->loadXML($string);
  753. @ini_restore('track_errors');
  754. if (!$success) {
  755. require_once 'Zend/Gdata/App/Exception.php';
  756. throw new Zend_Gdata_App_Exception(
  757. "DOMDocument cannot parse XML: $php_errormsg");
  758. }
  759. $feed = new $className();
  760. $feed->setMajorProtocolVersion($majorProtocolVersion);
  761. $feed->setMinorProtocolVersion($minorProtocolVersion);
  762. $feed->transferFromXML($string);
  763. $feed->setHttpClient(self::getstaticHttpClient());
  764. return $feed;
  765. }
  766. /**
  767. * Imports a feed from a file located at $filename.
  768. *
  769. * @param string $filename
  770. * @param string $className The class which is used as the return type
  771. * @param string $useIncludePath Whether the include_path should be searched
  772. * @throws Zend_Gdata_App_Exception
  773. * @return Zend_Gdata_App_Feed
  774. */
  775. public static function importFile($filename,
  776. $className='Zend_Gdata_App_Feed', $useIncludePath = false)
  777. {
  778. @ini_set('track_errors', 1);
  779. $feed = @file_get_contents($filename, $useIncludePath);
  780. @ini_restore('track_errors');
  781. if ($feed === false) {
  782. require_once 'Zend/Gdata/App/Exception.php';
  783. throw new Zend_Gdata_App_Exception(
  784. "File could not be loaded: $php_errormsg");
  785. }
  786. return self::importString($feed, $className);
  787. }
  788. /**
  789. * GET a URI using client object.
  790. *
  791. * @param string $uri GET URI
  792. * @param array $extraHeaders Extra headers to add to the request, as an
  793. * array of string-based key/value pairs.
  794. * @throws Zend_Gdata_App_HttpException
  795. * @return Zend_Http_Response
  796. */
  797. public function get($uri, $extraHeaders = array())
  798. {
  799. $requestData = $this->prepareRequest('GET', $uri, $extraHeaders);
  800. return $this->performHttpRequest(
  801. $requestData['method'], $requestData['url'],
  802. $requestData['headers']);
  803. }
  804. /**
  805. * POST data with client object
  806. *
  807. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  808. * @param string $uri POST URI
  809. * @param array $headers Additional HTTP headers to insert.
  810. * @param string $contentType Content-type of the data
  811. * @param array $extraHeaders Extra headers to add to the request, as an
  812. * array of string-based key/value pairs.
  813. * @return Zend_Http_Response
  814. * @throws Zend_Gdata_App_Exception
  815. * @throws Zend_Gdata_App_HttpException
  816. * @throws Zend_Gdata_App_InvalidArgumentException
  817. */
  818. public function post($data, $uri = null, $remainingRedirects = null,
  819. $contentType = null, $extraHeaders = null)
  820. {
  821. $requestData = $this->prepareRequest(
  822. 'POST', $uri, $extraHeaders, $data, $contentType);
  823. return $this->performHttpRequest(
  824. $requestData['method'], $requestData['url'],
  825. $requestData['headers'], $requestData['data'],
  826. $requestData['contentType']);
  827. }
  828. /**
  829. * PUT data with client object
  830. *
  831. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  832. * @param string $uri PUT URI
  833. * @param array $headers Additional HTTP headers to insert.
  834. * @param string $contentType Content-type of the data
  835. * @param array $extraHeaders Extra headers to add to the request, as an
  836. * array of string-based key/value pairs.
  837. * @return Zend_Http_Response
  838. * @throws Zend_Gdata_App_Exception
  839. * @throws Zend_Gdata_App_HttpException
  840. * @throws Zend_Gdata_App_InvalidArgumentException
  841. */
  842. public function put($data, $uri = null, $remainingRedirects = null,
  843. $contentType = null, $extraHeaders = null)
  844. {
  845. $requestData = $this->prepareRequest(
  846. 'PUT', $uri, $extraHeaders, $data, $contentType);
  847. return $this->performHttpRequest(
  848. $requestData['method'], $requestData['url'],
  849. $requestData['headers'], $requestData['data'],
  850. $requestData['contentType']);
  851. }
  852. /**
  853. * DELETE entry with client object
  854. *
  855. * @param mixed $data The Zend_Gdata_App_Entry or URL to delete
  856. * @return void
  857. * @throws Zend_Gdata_App_Exception
  858. * @throws Zend_Gdata_App_HttpException
  859. * @throws Zend_Gdata_App_InvalidArgumentException
  860. */
  861. public function delete($data, $remainingRedirects = null)
  862. {
  863. if (is_string($data)) {
  864. $requestData = $this->prepareRequest('DELETE', $data);
  865. } else {
  866. $headers = array();
  867. $requestData = $this->prepareRequest(
  868. 'DELETE', null, $headers, $data);
  869. }
  870. return $this->performHttpRequest($requestData['method'],
  871. $requestData['url'],
  872. $requestData['headers'],
  873. '',
  874. $requestData['contentType'],
  875. $remainingRedirects);
  876. }
  877. /**
  878. * Inserts an entry to a given URI and returns the response as a
  879. * fully formed Entry.
  880. *
  881. * @param mixed $data The Zend_Gdata_App_Entry or XML to post
  882. * @param string $uri POST URI
  883. * @param string $className The class of entry to be returned.
  884. * @param array $extraHeaders Extra headers to add to the request, as an
  885. * array of string-based key/value pairs.
  886. * @return Zend_Gdata_App_Entry The entry returned by the service after
  887. * insertion.
  888. */
  889. public function insertEntry($data, $uri, $className='Zend_Gdata_App_Entry',
  890. $extraHeaders = array())
  891. {
  892. if (!class_exists($className, false)) {
  893. require_once 'Zend/Loader.php';
  894. @Zend_Loader::loadClass($className);
  895. }
  896. $response = $this->post($data, $uri, null, null, $extraHeaders);
  897. $returnEntry = new $className($response->getBody());
  898. $returnEntry->setHttpClient(self::getstaticHttpClient());
  899. $etag = $response->getHeader('ETag');
  900. if ($etag !== null) {
  901. $returnEntry->setEtag($etag);
  902. }
  903. return $returnEntry;
  904. }
  905. /**
  906. * Update an entry
  907. *
  908. * @param mixed $data Zend_Gdata_App_Entry or XML (w/ID and link rel='edit')
  909. * @param string|null The URI to send requests to, or null if $data
  910. * contains the URI.
  911. * @param string|null The name of the class that should be deserialized
  912. * from the server response. If null, then 'Zend_Gdata_App_Entry'
  913. * will be used.
  914. * @param array $extraHeaders Extra headers to add to the request, as an
  915. * array of string-based key/value pairs.
  916. * @return Zend_Gdata_App_Entry The entry returned from the server
  917. * @throws Zend_Gdata_App_Exception
  918. */
  919. public function updateEntry($data, $uri = null, $className = null,
  920. $extraHeaders = array())
  921. {
  922. if ($className === null && $data instanceof Zend_Gdata_App_Entry) {
  923. $className = get_class($data);
  924. } elseif ($className === null) {
  925. $className = 'Zend_Gdata_App_Entry';
  926. }
  927. if (!class_exists($className, false)) {
  928. require_once 'Zend/Loader.php';
  929. @Zend_Loader::loadClass($className);
  930. }
  931. $response = $this->put($data, $uri, null, null, $extraHeaders);
  932. $returnEntry = new $className($response->getBody());
  933. $returnEntry->setHttpClient(self::getstaticHttpClient());
  934. $etag = $response->getHeader('ETag');
  935. if ($etag !== null) {
  936. $returnEntry->setEtag($etag);
  937. }
  938. return $returnEntry;
  939. }
  940. /**
  941. * Provides a magic factory method to instantiate new objects with
  942. * shorter syntax than would otherwise be required by the Zend Framework
  943. * naming conventions. For instance, to construct a new
  944. * Zend_Gdata_Calendar_Extension_Color, a developer simply needs to do
  945. * $gCal->newColor(). For this magic constructor, packages are searched
  946. * in the same order as which they appear in the $_registeredPackages
  947. * array
  948. *
  949. * @param string $method The method name being called
  950. * @param array $args The arguments passed to the call
  951. * @throws Zend_Gdata_App_Exception
  952. */
  953. public function __call($method, $args)
  954. {
  955. if (preg_match('/^new(\w+)/', $method, $matches)) {
  956. $class = $matches[1];
  957. $foundClassName = null;
  958. foreach ($this->_registeredPackages as $name) {
  959. try {
  960. // Autoloading disabled on next line for compatibility
  961. // with magic factories. See ZF-6660.
  962. if (!class_exists($name . '_' . $class, false)) {
  963. require_once 'Zend/Loader.php';
  964. @Zend_Loader::loadClass($name . '_' . $class);
  965. }
  966. $foundClassName = $name . '_' . $class;
  967. break;
  968. } catch (Zend_Exception $e) {
  969. // package wasn't here- continue searching
  970. }
  971. }
  972. if ($foundClassName != null) {
  973. $reflectionObj = new ReflectionClass($foundClassName);
  974. $instance = $reflectionObj->newInstanceArgs($args);
  975. if ($instance instanceof Zend_Gdata_App_FeedEntryParent) {
  976. $instance->setHttpClient($this->_httpClient);
  977. // Propogate version data
  978. $instance->setMajorProtocolVersion(
  979. $this->_majorProtocolVersion);
  980. $instance->setMinorProtocolVersion(
  981. $this->_minorProtocolVersion);
  982. }
  983. return $instance;
  984. } else {
  985. require_once 'Zend/Gdata/App/Exception.php';
  986. throw new Zend_Gdata_App_Exception(
  987. "Unable to find '${class}' in registered packages");
  988. }
  989. } else {
  990. require_once 'Zend/Gdata/App/Exception.php';
  991. throw new Zend_Gdata_App_Exception("No such method ${method}");
  992. }
  993. }
  994. /**
  995. * Retrieve all entries for a feed, iterating through pages as necessary.
  996. * Be aware that calling this function on a large dataset will take a
  997. * significant amount of time to complete. In some cases this may cause
  998. * execution to timeout without proper precautions in place.
  999. *
  1000. * @param $feed The feed to iterate through.
  1001. * @return mixed A new feed of the same type as the one originally
  1002. * passed in, containing all relevent entries.
  1003. */
  1004. public function retrieveAllEntriesForFeed($feed) {
  1005. $feedClass = get_class($feed);
  1006. $reflectionObj = new ReflectionClass($feedClass);
  1007. $result = $reflectionObj->newInstance();
  1008. do {
  1009. foreach ($feed as $entry) {
  1010. $result->addEntry($entry);
  1011. }
  1012. $next = $feed->getLink('next');
  1013. if ($next !== null) {
  1014. $feed = $this->getFeed($next->href, $feedClass);
  1015. } else {
  1016. $feed = null;
  1017. }
  1018. }
  1019. while ($feed != null);
  1020. return $result;
  1021. }
  1022. /**
  1023. * This method enables logging of requests by changing the
  1024. * Zend_Http_Client_Adapter used for performing the requests.
  1025. * NOTE: This will not work if you have customized the adapter
  1026. * already to use a proxy server or other interface.
  1027. *
  1028. * @param $logfile The logfile to use when logging the requests
  1029. */
  1030. public function enableRequestDebugLogging($logfile)
  1031. {
  1032. $this->_httpClient->setConfig(array(
  1033. 'adapter' => 'Zend_Gdata_App_LoggingHttpClientAdapterSocket',
  1034. 'logfile' => $logfile
  1035. ));
  1036. }
  1037. /**
  1038. * Retrieve next set of results based on a given feed.
  1039. *
  1040. * @param Zend_Gdata_App_Feed $feed The feed from which to
  1041. * retreive the next set of results.
  1042. * @param string $className (optional) The class of feed to be returned.
  1043. * If null, the next feed (if found) will be the same class as
  1044. * the feed that was given as the first argument.
  1045. * @return Zend_Gdata_App_Feed|null Returns a
  1046. * Zend_Gdata_App_Feed or null if no next set of results
  1047. * exists.
  1048. */
  1049. public function getNextFeed($feed, $className = null)
  1050. {
  1051. $nextLink = $feed->getNextLink();
  1052. if (!$nextLink) {
  1053. return null;
  1054. }
  1055. $nextLinkHref = $nextLink->getHref();
  1056. if ($className === null) {
  1057. $className = get_class($feed);
  1058. }
  1059. return $this->getFeed($nextLinkHref, $className);
  1060. }
  1061. /**
  1062. * Retrieve previous set of results based on a given feed.
  1063. *
  1064. * @param Zend_Gdata_App_Feed $feed The feed from which to
  1065. * retreive the previous set of results.
  1066. * @param string $className (optional) The class of feed to be returned.
  1067. * If null, the previous feed (if found) will be the same class as
  1068. * the feed that was given as the first argument.
  1069. * @return Zend_Gdata_App_Feed|null Returns a
  1070. * Zend_Gdata_App_Feed or null if no previous set of results
  1071. * exists.
  1072. */
  1073. public function getPreviousFeed($feed, $className = null)
  1074. {
  1075. $previousLink = $feed->getPreviousLink();
  1076. if (!$previousLink) {
  1077. return null;
  1078. }
  1079. $previousLinkHref = $previousLink->getHref();
  1080. if ($className === null) {
  1081. $className = get_class($feed);
  1082. }
  1083. return $this->getFeed($previousLinkHref, $className);
  1084. }
  1085. /**
  1086. * Returns the data for an If-Match header based on the current Etag
  1087. * property. If Etags are not supported by the server or cannot be
  1088. * extracted from the data, then null will be returned.
  1089. *
  1090. * @param boolean $allowWeak If false, then if a weak Etag is detected,
  1091. * then return null rather than the Etag.
  1092. * @return string|null $data
  1093. */
  1094. public function generateIfMatchHeaderData($data, $allowWeek)
  1095. {
  1096. $result = '';
  1097. // Set an If-Match header if an ETag has been set (version >= 2 only)
  1098. if ($this->_majorProtocolVersion >= 2 &&
  1099. $data instanceof Zend_Gdata_App_Entry) {
  1100. $etag = $data->getEtag();
  1101. if (($etag !== null) &&
  1102. ($allowWeek || substr($etag, 0, 2) != 'W/')) {
  1103. $result = $data->getEtag();
  1104. }
  1105. }
  1106. return $result;
  1107. }
  1108. /**
  1109. * Determine whether service object is using XML to object mapping.
  1110. *
  1111. * @return boolean True if service object is using XML to object mapping,
  1112. * false otherwise.
  1113. */
  1114. public function usingObjectMapping()
  1115. {
  1116. return $this->_useObjectMapping;
  1117. }
  1118. /**
  1119. * Enable/disable the use of XML to object mapping.
  1120. *
  1121. * @param boolean $value Pass in true to use the XML to object mapping.
  1122. * Pass in false or null to disable it.
  1123. * @return void
  1124. */
  1125. public function useObjectMapping($value)
  1126. {
  1127. if ($value === True) {
  1128. $this->_useObjectMapping = true;
  1129. } else {
  1130. $this->_useObjectMapping = false;
  1131. }
  1132. }
  1133. }