web/Zend/Feed/Pubsubhubbub/Subscriber.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     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-2010 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 23170 2010-10-19 18:29:24Z mabe $
       
    20  */
       
    21 
       
    22 /**
       
    23  * @see Zend_Feed_Pubsubhubbub
       
    24  */
       
    25 require_once 'Zend/Feed/Pubsubhubbub.php';
       
    26 
       
    27 /**
       
    28  * @see Zend_Date
       
    29  */
       
    30 require_once 'Zend/Date.php';
       
    31 
       
    32 /**
       
    33  * @category   Zend
       
    34  * @package    Zend_Feed_Pubsubhubbub
       
    35  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    36  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    37  */
       
    38 class 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 }