App.php 44 KB

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