Subscriber.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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_Feed_Pubsubhubbub
  17. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * @see Zend_Feed_Pubsubhubbub
  23. */
  24. require_once 'Zend/Feed/Pubsubhubbub.php';
  25. /**
  26. * @see Zend_Date
  27. */
  28. require_once 'Zend/Date.php';
  29. /**
  30. * @category Zend
  31. * @package Zend_Feed_Pubsubhubbub
  32. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  33. * @license http://framework.zend.com/license/new-bsd New BSD License
  34. */
  35. class Zend_Feed_Pubsubhubbub_Subscriber
  36. {
  37. /**
  38. * An array of URLs for all Hub Servers to subscribe/unsubscribe.
  39. *
  40. * @var array
  41. */
  42. protected $_hubUrls = array();
  43. /**
  44. * An array of optional parameters to be included in any
  45. * (un)subscribe requests.
  46. *
  47. * @var array
  48. */
  49. protected $_parameters = array();
  50. /**
  51. * The URL of the topic (Rss or Atom feed) which is the subject of
  52. * our current intent to subscribe to/unsubscribe from updates from
  53. * the currently configured Hub Servers.
  54. *
  55. * @var string
  56. */
  57. protected $_topicUrl = '';
  58. /**
  59. * The URL Hub Servers must use when communicating with this Subscriber
  60. *
  61. * @var string
  62. */
  63. protected $_callbackUrl = '';
  64. /**
  65. * The number of seconds for which the subscriber would like to have the
  66. * subscription active. Defaults to null, i.e. not sent, to setup a
  67. * permanent subscription if possible.
  68. *
  69. * @var int
  70. */
  71. protected $_leaseSeconds = null;
  72. /**
  73. * The preferred verification mode (sync or async). By default, this
  74. * Subscriber prefers synchronous verification, but is considered
  75. * desireable to support asynchronous verification if possible.
  76. *
  77. * Zend_Feed_Pubsubhubbub_Subscriber will always send both modes, whose
  78. * order of occurance in the parameter list determines this preference.
  79. *
  80. * @var string
  81. */
  82. protected $_preferredVerificationMode
  83. = Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC;
  84. /**
  85. * An array of any errors including keys for 'response', 'hubUrl'.
  86. * The response is the actual Zend_Http_Response object.
  87. *
  88. * @var array
  89. */
  90. protected $_errors = array();
  91. /**
  92. * An array of Hub Server URLs for Hubs operating at this time in
  93. * asynchronous verification mode.
  94. *
  95. * @var array
  96. */
  97. protected $_asyncHubs = array();
  98. /**
  99. * An instance of Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface used to background
  100. * save any verification tokens associated with a subscription or other.
  101. *
  102. * @var Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface
  103. */
  104. protected $_storage = null;
  105. /**
  106. * An array of authentication credentials for HTTP Basic Authentication
  107. * if required by specific Hubs. The array is indexed by Hub Endpoint URI
  108. * and the value is a simple array of the username and password to apply.
  109. *
  110. * @var array
  111. */
  112. protected $_authentications = array();
  113. /**
  114. * Tells the Subscriber to append any subscription identifier to the path
  115. * of the base Callback URL. E.g. an identifier "subkey1" would be added
  116. * to the callback URL "http://www.example.com/callback" to create a subscription
  117. * specific Callback URL of "http://www.example.com/callback/subkey1".
  118. *
  119. * This is required for all Hubs using the Pubsubhubbub 0.1 Specification.
  120. * It should be manually intercepted and passed to the Callback class using
  121. * Zend_Feed_Pubsubhubbub_Subscriber_Callback::setSubscriptionKey(). Will
  122. * require a route in the form "callback/:subkey" to allow the parameter be
  123. * retrieved from an action using the Zend_Controller_Action::_getParam()
  124. * method.
  125. *
  126. * @var string
  127. */
  128. protected $_usePathParameter = false;
  129. /**
  130. * Constructor; accepts an array or Zend_Config instance to preset
  131. * options for the Subscriber without calling all supported setter
  132. * methods in turn.
  133. *
  134. * @param array|Zend_Config|null $config Options array or Zend_Config instance
  135. * @throws Zend_Feed_Pubsubhubbub_Exception
  136. */
  137. public function __construct($config = null)
  138. {
  139. if ($config !== null) {
  140. $this->setConfig($config);
  141. }
  142. }
  143. /**
  144. * Process any injected configuration options
  145. *
  146. * @param array|Zend_Config $config Options array or Zend_Config instance
  147. * @throws Zend_Feed_Pubsubhubbub_Exception
  148. * @return Zend_Feed_Pubsubhubbub_Subscriber
  149. */
  150. public function setConfig($config)
  151. {
  152. if ($config instanceof Zend_Config) {
  153. $config = $config->toArray();
  154. } elseif (!is_array($config)) {
  155. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  156. throw new Zend_Feed_Pubsubhubbub_Exception('Array or Zend_Config object'
  157. . ' expected, got ' . gettype($config));
  158. }
  159. if (array_key_exists('hubUrls', $config)) {
  160. $this->addHubUrls($config['hubUrls']);
  161. }
  162. if (array_key_exists('callbackUrl', $config)) {
  163. $this->setCallbackUrl($config['callbackUrl']);
  164. }
  165. if (array_key_exists('topicUrl', $config)) {
  166. $this->setTopicUrl($config['topicUrl']);
  167. }
  168. if (array_key_exists('storage', $config)) {
  169. $this->setStorage($config['storage']);
  170. }
  171. if (array_key_exists('leaseSeconds', $config)) {
  172. $this->setLeaseSeconds($config['leaseSeconds']);
  173. }
  174. if (array_key_exists('parameters', $config)) {
  175. $this->setParameters($config['parameters']);
  176. }
  177. if (array_key_exists('authentications', $config)) {
  178. $this->addAuthentications($config['authentications']);
  179. }
  180. if (array_key_exists('usePathParameter', $config)) {
  181. $this->usePathParameter($config['usePathParameter']);
  182. }
  183. if (array_key_exists('preferredVerificationMode', $config)) {
  184. $this->setPreferredVerificationMode(
  185. $config['preferredVerificationMode']
  186. );
  187. }
  188. return $this;
  189. }
  190. /**
  191. * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe
  192. * event will relate
  193. *
  194. * @param string $url
  195. * @throws Zend_Feed_Pubsubhubbub_Exception
  196. * @return Zend_Feed_Pubsubhubbub_Subscriber
  197. */
  198. public function setTopicUrl($url)
  199. {
  200. if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) {
  201. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  202. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"'
  203. .' of "' . $url . '" must be a non-empty string and a valid'
  204. .' URL');
  205. }
  206. $this->_topicUrl = $url;
  207. return $this;
  208. }
  209. /**
  210. * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe
  211. * event will relate
  212. *
  213. * @throws Zend_Feed_Pubsubhubbub_Exception
  214. * @return string
  215. */
  216. public function getTopicUrl()
  217. {
  218. if (empty($this->_topicUrl)) {
  219. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  220. throw new Zend_Feed_Pubsubhubbub_Exception('A valid Topic (RSS or Atom'
  221. . ' feed) URL MUST be set before attempting any operation');
  222. }
  223. return $this->_topicUrl;
  224. }
  225. /**
  226. * Set the number of seconds for which any subscription will remain valid
  227. *
  228. * @param int $seconds
  229. * @throws Zend_Feed_Pubsubhubbub_Exception
  230. * @return Zend_Feed_Pubsubhubbub_Subscriber
  231. */
  232. public function setLeaseSeconds($seconds)
  233. {
  234. $seconds = intval($seconds);
  235. if ($seconds <= 0) {
  236. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  237. throw new Zend_Feed_Pubsubhubbub_Exception('Expected lease seconds'
  238. . ' must be an integer greater than zero');
  239. }
  240. $this->_leaseSeconds = $seconds;
  241. return $this;
  242. }
  243. /**
  244. * Get the number of lease seconds on subscriptions
  245. *
  246. * @return int
  247. */
  248. public function getLeaseSeconds()
  249. {
  250. return $this->_leaseSeconds;
  251. }
  252. /**
  253. * Set the callback URL to be used by Hub Servers when communicating with
  254. * this Subscriber
  255. *
  256. * @param string $url
  257. * @throws Zend_Feed_Pubsubhubbub_Exception
  258. * @return Zend_Feed_Pubsubhubbub_Subscriber
  259. */
  260. public function setCallbackUrl($url)
  261. {
  262. if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) {
  263. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  264. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"'
  265. . ' of "' . $url . '" must be a non-empty string and a valid'
  266. . ' URL');
  267. }
  268. $this->_callbackUrl = $url;
  269. return $this;
  270. }
  271. /**
  272. * Get the callback URL to be used by Hub Servers when communicating with
  273. * this Subscriber
  274. *
  275. * @throws Zend_Feed_Pubsubhubbub_Exception
  276. * @return string
  277. */
  278. public function getCallbackUrl()
  279. {
  280. if (empty($this->_callbackUrl)) {
  281. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  282. throw new Zend_Feed_Pubsubhubbub_Exception('A valid Callback URL MUST be'
  283. . ' set before attempting any operation');
  284. }
  285. return $this->_callbackUrl;
  286. }
  287. /**
  288. * Set preferred verification mode (sync or async). By default, this
  289. * Subscriber prefers synchronous verification, but does support
  290. * asynchronous if that's the Hub Server's utilised mode.
  291. *
  292. * Zend_Feed_Pubsubhubbub_Subscriber will always send both modes, whose
  293. * order of occurance in the parameter list determines this preference.
  294. *
  295. * @param string $mode Should be 'sync' or 'async'
  296. * @throws Zend_Feed_Pubsubhubbub_Exception
  297. * @return Zend_Feed_Pubsubhubbub_Subscriber
  298. */
  299. public function setPreferredVerificationMode($mode)
  300. {
  301. if ($mode !== Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC
  302. && $mode !== Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC) {
  303. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  304. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid preferred'
  305. . ' mode specified: "' . $mode . '" but should be one of'
  306. . ' Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC or'
  307. . ' Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC');
  308. }
  309. $this->_preferredVerificationMode = $mode;
  310. return $this;
  311. }
  312. /**
  313. * Get preferred verification mode (sync or async).
  314. *
  315. * @return string
  316. */
  317. public function getPreferredVerificationMode()
  318. {
  319. return $this->_preferredVerificationMode;
  320. }
  321. /**
  322. * Add a Hub Server URL supported by Publisher
  323. *
  324. * @param string $url
  325. * @throws Zend_Feed_Pubsubhubbub_Exception
  326. * @return Zend_Feed_Pubsubhubbub_Subscriber
  327. */
  328. public function addHubUrl($url)
  329. {
  330. if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) {
  331. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  332. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"'
  333. . ' of "' . $url . '" must be a non-empty string and a valid'
  334. . ' URL');
  335. }
  336. $this->_hubUrls[] = $url;
  337. return $this;
  338. }
  339. /**
  340. * Add an array of Hub Server URLs supported by Publisher
  341. *
  342. * @param array $urls
  343. * @return Zend_Feed_Pubsubhubbub_Subscriber
  344. */
  345. public function addHubUrls(array $urls)
  346. {
  347. foreach ($urls as $url) {
  348. $this->addHubUrl($url);
  349. }
  350. return $this;
  351. }
  352. /**
  353. * Remove a Hub Server URL
  354. *
  355. * @param string $url
  356. * @return Zend_Feed_Pubsubhubbub_Subscriber
  357. */
  358. public function removeHubUrl($url)
  359. {
  360. if (!in_array($url, $this->getHubUrls())) {
  361. return $this;
  362. }
  363. $key = array_search($url, $this->_hubUrls);
  364. unset($this->_hubUrls[$key]);
  365. return $this;
  366. }
  367. /**
  368. * Return an array of unique Hub Server URLs currently available
  369. *
  370. * @return array
  371. */
  372. public function getHubUrls()
  373. {
  374. $this->_hubUrls = array_unique($this->_hubUrls);
  375. return $this->_hubUrls;
  376. }
  377. /**
  378. * Add authentication credentials for a given URL
  379. *
  380. * @param string $url
  381. * @param array $authentication
  382. * @throws Zend_Feed_Pubsubhubbub_Exception
  383. * @return Zend_Feed_Pubsubhubbub_Subscriber
  384. */
  385. public function addAuthentication($url, array $authentication)
  386. {
  387. if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) {
  388. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  389. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"'
  390. . ' of "' . $url . '" must be a non-empty string and a valid'
  391. . ' URL');
  392. }
  393. $this->_authentications[$url] = $authentication;
  394. return $this;
  395. }
  396. /**
  397. * Add authentication credentials for hub URLs
  398. *
  399. * @param array $authentications
  400. * @return Zend_Feed_Pubsubhubbub_Subscriber
  401. */
  402. public function addAuthentications(array $authentications)
  403. {
  404. foreach ($authentications as $url => $authentication) {
  405. $this->addAuthentication($url, $authentication);
  406. }
  407. return $this;
  408. }
  409. /**
  410. * Get all hub URL authentication credentials
  411. *
  412. * @return array
  413. */
  414. public function getAuthentications()
  415. {
  416. return $this->_authentications;
  417. }
  418. /**
  419. * Set flag indicating whether or not to use a path parameter
  420. *
  421. * @param bool $bool
  422. * @return Zend_Feed_Pubsubhubbub_Subscriber
  423. */
  424. public function usePathParameter($bool = true)
  425. {
  426. $this->_usePathParameter = $bool;
  427. return $this;
  428. }
  429. /**
  430. * Add an optional parameter to the (un)subscribe requests
  431. *
  432. * @param string $name
  433. * @param string|null $value
  434. * @throws Zend_Feed_Pubsubhubbub_Exception
  435. * @return Zend_Feed_Pubsubhubbub_Subscriber
  436. */
  437. public function setParameter($name, $value = null)
  438. {
  439. if (is_array($name)) {
  440. $this->setParameters($name);
  441. return $this;
  442. }
  443. if (empty($name) || !is_string($name)) {
  444. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  445. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"'
  446. . ' of "' . $name . '" must be a non-empty string');
  447. }
  448. if ($value === null) {
  449. $this->removeParameter($name);
  450. return $this;
  451. }
  452. if (empty($value) || (!is_string($value) && $value !== null)) {
  453. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  454. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "value"'
  455. . ' of "' . $value . '" must be a non-empty string');
  456. }
  457. $this->_parameters[$name] = $value;
  458. return $this;
  459. }
  460. /**
  461. * Add an optional parameter to the (un)subscribe requests
  462. *
  463. * @param array $parameters
  464. * @throws Zend_Feed_Pubsubhubbub_Exception
  465. * @return Zend_Feed_Pubsubhubbub_Subscriber
  466. */
  467. public function setParameters(array $parameters)
  468. {
  469. foreach ($parameters as $name => $value) {
  470. $this->setParameter($name, $value);
  471. }
  472. return $this;
  473. }
  474. /**
  475. * Remove an optional parameter for the (un)subscribe requests
  476. *
  477. * @param string $name
  478. * @throws Zend_Feed_Pubsubhubbub_Exception
  479. * @return Zend_Feed_Pubsubhubbub_Subscriber
  480. */
  481. public function removeParameter($name)
  482. {
  483. if (empty($name) || !is_string($name)) {
  484. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  485. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"'
  486. . ' of "' . $name . '" must be a non-empty string');
  487. }
  488. if (array_key_exists($name, $this->_parameters)) {
  489. unset($this->_parameters[$name]);
  490. }
  491. return $this;
  492. }
  493. /**
  494. * Return an array of optional parameters for (un)subscribe requests
  495. *
  496. * @return array
  497. */
  498. public function getParameters()
  499. {
  500. return $this->_parameters;
  501. }
  502. /**
  503. * Sets an instance of Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface used to background
  504. * save any verification tokens associated with a subscription or other.
  505. *
  506. * @param Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage
  507. * @return Zend_Feed_Pubsubhubbub_Subscriber
  508. */
  509. public function setStorage(Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage)
  510. {
  511. $this->_storage = $storage;
  512. return $this;
  513. }
  514. /**
  515. * Gets an instance of Zend_Feed_Pubsubhubbub_Storage_StorageInterface used
  516. * to background save any verification tokens associated with a subscription
  517. * or other.
  518. *
  519. * @throws Zend_Feed_Pubsubhubbub_Exception
  520. * @return Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface
  521. */
  522. public function getStorage()
  523. {
  524. if ($this->_storage === null) {
  525. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  526. throw new Zend_Feed_Pubsubhubbub_Exception('No storage vehicle '
  527. . 'has been set.');
  528. }
  529. return $this->_storage;
  530. }
  531. /**
  532. * Subscribe to one or more Hub Servers using the stored Hub URLs
  533. * for the given Topic URL (RSS or Atom feed)
  534. */
  535. public function subscribeAll()
  536. {
  537. return $this->_doRequest('subscribe');
  538. }
  539. /**
  540. * Unsubscribe from one or more Hub Servers using the stored Hub URLs
  541. * for the given Topic URL (RSS or Atom feed)
  542. */
  543. public function unsubscribeAll()
  544. {
  545. return $this->_doRequest('unsubscribe');
  546. }
  547. /**
  548. * Returns a boolean indicator of whether the notifications to Hub
  549. * Servers were ALL successful. If even one failed, FALSE is returned.
  550. *
  551. * @return bool
  552. */
  553. public function isSuccess()
  554. {
  555. if (count($this->_errors) > 0) {
  556. return false;
  557. }
  558. return true;
  559. }
  560. /**
  561. * Return an array of errors met from any failures, including keys:
  562. * 'response' => the Zend_Http_Response object from the failure
  563. * 'hubUrl' => the URL of the Hub Server whose notification failed
  564. *
  565. * @return array
  566. */
  567. public function getErrors()
  568. {
  569. return $this->_errors;
  570. }
  571. /**
  572. * Return an array of Hub Server URLs who returned a response indicating
  573. * operation in Asynchronous Verification Mode, i.e. they will not confirm
  574. * any (un)subscription immediately but at a later time (Hubs may be
  575. * doing this as a batch process when load balancing)
  576. *
  577. * @return array
  578. */
  579. public function getAsyncHubs()
  580. {
  581. return $this->_asyncHubs;
  582. }
  583. /**
  584. * Executes an (un)subscribe request
  585. *
  586. * @param string $mode
  587. * @throws Zend_Feed_Pubsubhubbub_Exception
  588. * @throws Zend_Http_Client_Exception
  589. */
  590. protected function _doRequest($mode)
  591. {
  592. $client = $this->_getHttpClient();
  593. $hubs = $this->getHubUrls();
  594. if (empty($hubs)) {
  595. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  596. throw new Zend_Feed_Pubsubhubbub_Exception('No Hub Server URLs'
  597. . ' have been set so no subscriptions can be attempted');
  598. }
  599. $this->_errors = array();
  600. $this->_asyncHubs = array();
  601. foreach ($hubs as $url) {
  602. if (array_key_exists($url, $this->_authentications)) {
  603. $auth = $this->_authentications[$url];
  604. $client->setAuth($auth[0], $auth[1]);
  605. }
  606. $client->setUri($url);
  607. $client->setRawData(
  608. $this->_getRequestParameters($url, $mode),
  609. 'application/x-www-form-urlencoded'
  610. );
  611. $response = $client->request();
  612. if ($response->getStatus() !== 204
  613. && $response->getStatus() !== 202
  614. ) {
  615. $this->_errors[] = array(
  616. 'response' => $response,
  617. 'hubUrl' => $url,
  618. );
  619. /**
  620. * At first I thought it was needed, but the backend storage will
  621. * allow tracking async without any user interference. It's left
  622. * here in case the user is interested in knowing what Hubs
  623. * are using async verification modes so they may update Models and
  624. * move these to asynchronous processes.
  625. */
  626. } elseif ($response->getStatus() == 202) {
  627. $this->_asyncHubs[] = array(
  628. 'response' => $response,
  629. 'hubUrl' => $url,
  630. );
  631. }
  632. }
  633. }
  634. /**
  635. * Get a basic prepared HTTP client for use
  636. *
  637. * @return Zend_Http_Client
  638. */
  639. protected function _getHttpClient()
  640. {
  641. $client = Zend_Feed_Pubsubhubbub::getHttpClient();
  642. $client->setMethod(Zend_Http_Client::POST);
  643. $client->setConfig(array('useragent' => 'Zend_Feed_Pubsubhubbub_Subscriber/'
  644. . Zend_Version::VERSION));
  645. return $client;
  646. }
  647. /**
  648. * Return a list of standard protocol/optional parameters for addition to
  649. * client's POST body that are specific to the current Hub Server URL
  650. *
  651. * @param string $hubUrl
  652. * @param string $mode
  653. * @throws Zend_Feed_Pubsubhubbub_Exception
  654. * @return string
  655. */
  656. protected function _getRequestParameters($hubUrl, $mode)
  657. {
  658. if (!in_array($mode, array('subscribe', 'unsubscribe'))) {
  659. require_once 'Zend/Feed/Pubsubhubbub/Exception.php';
  660. throw new Zend_Feed_Pubsubhubbub_Exception('Invalid mode specified: "'
  661. . $mode . '" which should have been "subscribe" or "unsubscribe"');
  662. }
  663. $params = array(
  664. 'hub.mode' => $mode,
  665. 'hub.topic' => $this->getTopicUrl(),
  666. );
  667. if ($this->getPreferredVerificationMode()
  668. == Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC
  669. ) {
  670. $vmodes = array(
  671. Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC,
  672. Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC,
  673. );
  674. } else {
  675. $vmodes = array(
  676. Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC,
  677. Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC,
  678. );
  679. }
  680. $params['hub.verify'] = array();
  681. foreach($vmodes as $vmode) {
  682. $params['hub.verify'][] = $vmode;
  683. }
  684. /**
  685. * Establish a persistent verify_token and attach key to callback
  686. * URL's path/querystring
  687. */
  688. $key = $this->_generateSubscriptionKey($params, $hubUrl);
  689. $token = $this->_generateVerifyToken();
  690. $params['hub.verify_token'] = $token;
  691. // Note: query string only usable with PuSH 0.2 Hubs
  692. if (!$this->_usePathParameter) {
  693. $params['hub.callback'] = $this->getCallbackUrl()
  694. . '?xhub.subscription=' . Zend_Feed_Pubsubhubbub::urlencode($key);
  695. } else {
  696. $params['hub.callback'] = rtrim($this->getCallbackUrl(), '/')
  697. . '/' . Zend_Feed_Pubsubhubbub::urlencode($key);
  698. }
  699. if ($mode == 'subscribe' && $this->getLeaseSeconds() !== null) {
  700. $params['hub.lease_seconds'] = $this->getLeaseSeconds();
  701. }
  702. // hub.secret not currently supported
  703. $optParams = $this->getParameters();
  704. foreach ($optParams as $name => $value) {
  705. $params[$name] = $value;
  706. }
  707. // store subscription to storage
  708. $now = new Zend_Date;
  709. $expires = null;
  710. if (isset($params['hub.lease_seconds'])) {
  711. $expires = $now->add($params['hub.lease_seconds'], Zend_Date::SECOND)
  712. ->get('yyyy-MM-dd HH:mm:ss');
  713. }
  714. $data = array(
  715. 'id' => $key,
  716. 'topic_url' => $params['hub.topic'],
  717. 'hub_url' => $hubUrl,
  718. 'created_time' => $now->get('yyyy-MM-dd HH:mm:ss'),
  719. 'lease_seconds' => $expires,
  720. 'verify_token' => hash('sha256', $params['hub.verify_token']),
  721. 'secret' => null,
  722. 'expiration_time' => $expires,
  723. 'subscription_state' => Zend_Feed_Pubsubhubbub::SUBSCRIPTION_NOTVERIFIED,
  724. );
  725. $this->getStorage()->setSubscription($data);
  726. return $this->_toByteValueOrderedString(
  727. $this->_urlEncode($params)
  728. );
  729. }
  730. /**
  731. * Simple helper to generate a verification token used in (un)subscribe
  732. * requests to a Hub Server. Follows no particular method, which means
  733. * it might be improved/changed in future.
  734. *
  735. * @return string
  736. */
  737. protected function _generateVerifyToken()
  738. {
  739. if (!empty($this->_testStaticToken)) {
  740. return $this->_testStaticToken;
  741. }
  742. return uniqid(rand(), true) . time();
  743. }
  744. /**
  745. * Simple helper to generate a verification token used in (un)subscribe
  746. * requests to a Hub Server.
  747. *
  748. * @param array $params
  749. * @param string $hubUrl The Hub Server URL for which this token will apply
  750. * @return string
  751. */
  752. protected function _generateSubscriptionKey(array $params, $hubUrl)
  753. {
  754. $keyBase = $params['hub.topic'] . $hubUrl;
  755. $key = md5($keyBase);
  756. return $key;
  757. }
  758. /**
  759. * URL Encode an array of parameters
  760. *
  761. * @param array $params
  762. * @return array
  763. */
  764. protected function _urlEncode(array $params)
  765. {
  766. $encoded = array();
  767. foreach ($params as $key => $value) {
  768. if (is_array($value)) {
  769. $ekey = Zend_Feed_Pubsubhubbub::urlencode($key);
  770. $encoded[$ekey] = array();
  771. foreach ($value as $duplicateKey) {
  772. $encoded[$ekey][]
  773. = Zend_Feed_Pubsubhubbub::urlencode($duplicateKey);
  774. }
  775. } else {
  776. $encoded[Zend_Feed_Pubsubhubbub::urlencode($key)]
  777. = Zend_Feed_Pubsubhubbub::urlencode($value);
  778. }
  779. }
  780. return $encoded;
  781. }
  782. /**
  783. * Order outgoing parameters
  784. *
  785. * @param array $params
  786. * @return array
  787. */
  788. protected function _toByteValueOrderedString(array $params)
  789. {
  790. $return = array();
  791. uksort($params, 'strnatcmp');
  792. foreach ($params as $key => $value) {
  793. if (is_array($value)) {
  794. foreach ($value as $keyduplicate) {
  795. $return[] = $key . '=' . $keyduplicate;
  796. }
  797. } else {
  798. $return[] = $key . '=' . $value;
  799. }
  800. }
  801. return implode('&', $return);
  802. }
  803. /**
  804. * This is STRICTLY for testing purposes only...
  805. */
  806. protected $_testStaticToken = null;
  807. final public function setTestStaticToken($token)
  808. {
  809. $this->_testStaticToken = (string) $token;
  810. }
  811. }