PageRenderTime 259ms CodeModel.GetById 120ms app.highlight 63ms RepoModel.GetById 68ms app.codeStats 0ms

/Feed/Pubsubhubbub/Subscriber.php

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