App.php 43 KB

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