annotate vendor/zendframework/zend-feed/src/PubSubHubbub/Subscriber.php @ 19:fa3358dc1485 tip

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