]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - plugins/OStatus/classes/FeedSub.php
Merge remote-tracking branch 'upstream/master' into social-master
[quix0rs-gnu-social.git] / plugins / OStatus / classes / FeedSub.php
index 2e62c512d13996d32ed5915d3f433138c3349b86..b0875c298865b86f70fa1d8c57c272368d73d25a 100644 (file)
@@ -62,12 +62,12 @@ class FeedSub extends Managed_DataObject
     public $__table = 'feedsub';
 
     public $id;
-    public $uri;
+    public $uri;    // varchar(191)   not 255 because utf8mb4 takes more space
 
     // PuSH subscription data
     public $huburi;
     public $secret;
-    public $sub_state; // subscribe, active, unsubscribe, inactive
+    public $sub_state; // subscribe, active, unsubscribe, inactive, nohub
     public $sub_start;
     public $sub_end;
     public $last_update;
@@ -80,13 +80,13 @@ class FeedSub extends Managed_DataObject
         return array(
             'fields' => array(
                 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'FeedSub local unique id'),
-                'uri' => array('type' => 'varchar', 'not null' => true, 'length' => 255, 'description' => 'FeedSub uri'),
+                'uri' => array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'FeedSub uri'),
                 'huburi' => array('type' => 'text', 'description' => 'FeedSub hub-uri'),
                 'secret' => array('type' => 'text', 'description' => 'FeedSub stored secret'),
-                'sub_state' => array('type' => 'enum("subscribe","active","unsubscribe","inactive")', 'not null' => true, 'description' => 'subscription state'),
+                'sub_state' => array('type' => 'enum("subscribe","active","unsubscribe","inactive","nohub")', 'not null' => true, 'description' => 'subscription state'),
                 'sub_start' => array('type' => 'datetime', 'description' => 'subscription start'),
                 'sub_end' => array('type' => 'datetime', 'description' => 'subscription end'),
-                'last_update' => array('type' => 'datetime', 'not null' => true, 'description' => 'when this record was last updated'),
+                'last_update' => array('type' => 'datetime', 'description' => 'when this record was last updated'),
                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
             ),
@@ -97,6 +97,37 @@ class FeedSub extends Managed_DataObject
         );
     }
 
+    /**
+     * Get the feed uri (http/https)
+     */
+    public function getUri()
+    {
+        if (empty($this->uri)) {
+            throw new NoUriException($this);
+        }
+        return $this->uri;
+    }
+
+    /**
+     * Do we have a hub? Then we are a PuSH feed.
+     * https://en.wikipedia.org/wiki/PubSubHubbub
+     *
+     * If huburi is empty, then doublecheck that we are not using
+     * a fallback hub. If there is a fallback hub, it is only if the
+     * sub_state is "nohub" that we assume it's not a PuSH feed.
+     */
+    public function isPuSH()
+    {
+        if (empty($this->huburi)
+                && (!common_config('feedsub', 'fallback_hub')
+                    || $this->sub_state === 'nohub')) {
+                // Here we have no huburi set. Also, either there is no 
+                // fallback hub configured or sub_state is "nohub".
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Fetch the StatusNet-side profile for this feed
      * @return Profile
@@ -137,7 +168,7 @@ class FeedSub extends Managed_DataObject
         $discover->discoverFromFeedURL($feeduri);
 
         $huburi = $discover->getHubLink();
-        if (!$huburi && !common_config('feedsub', 'fallback_hub')) {
+        if (!$huburi && !common_config('feedsub', 'fallback_hub') && !common_config('feedsub', 'nohub')) {
             throw new FeedSubNoHubException();
         }
 
@@ -161,29 +192,35 @@ class FeedSub extends Managed_DataObject
      * Send a subscription request to the hub for this feed.
      * The hub will later send us a confirmation POST to /main/push/callback.
      *
-     * @return bool true on success, false on failure
+     * @return void
      * @throws ServerException if feed state is not valid
      */
     public function subscribe()
     {
         if ($this->sub_state && $this->sub_state != 'inactive') {
-            common_log(LOG_WARNING, "Attempting to (re)start PuSH subscription to {$this->uri} in unexpected state {$this->sub_state}");
+            common_log(LOG_WARNING, sprintf('Attempting to (re)start PuSH subscription to %s in unexpected state %s', $this->getUri(), $this->sub_state));
+        }
+
+        if (!Event::handle('FeedSubscribe', array($this))) {
+            // A plugin handled it
+            return;
         }
+
         if (empty($this->huburi)) {
             if (common_config('feedsub', 'fallback_hub')) {
                 // No native hub on this feed?
                 // Use our fallback hub, which handles polling on our behalf.
             } else if (common_config('feedsub', 'nohub')) {
-                // Fake it! We're just testing remote feeds w/o hubs.
-                // We'll never actually get updates in this mode.
-                return true;
+                // For this to actually work, we'll need some polling mechanism.
+                // The FeedPoller plugin should take care of it.
+                return;
             } else {
                 // TRANS: Server exception.
                 throw new ServerException(_m('Attempting to start PuSH subscription for feed with no hub.'));
             }
         }
 
-        return $this->doSubscribe('subscribe');
+        $this->doSubscribe('subscribe');
     }
 
     /**
@@ -193,28 +230,33 @@ class FeedSub extends Managed_DataObject
      * the system is using it. Most callers will want garbageCollect() instead,
      * which confirms there's no uses left.
      *
-     * @return bool true on success, false on failure
      * @throws ServerException if feed state is not valid
      */
     public function unsubscribe() {
         if ($this->sub_state != 'active') {
-            common_log(LOG_WARNING, "Attempting to (re)end PuSH subscription to {$this->uri} in unexpected state {$this->sub_state}");
+            common_log(LOG_WARNING, sprintf('Attempting to (re)end PuSH subscription to %s in unexpected state %s', $this->getUri(), $this->sub_state));
+        }
+
+        if (!Event::handle('FeedUnsubscribe', array($this))) {
+            // A plugin handled it
+            return;
         }
+
         if (empty($this->huburi)) {
             if (common_config('feedsub', 'fallback_hub')) {
                 // No native hub on this feed?
                 // Use our fallback hub, which handles polling on our behalf.
             } else if (common_config('feedsub', 'nohub')) {
-                // Fake it! We're just testing remote feeds w/o hubs.
-                // We'll never actually get updates in this mode.
-                return true;
+                // We need a feedpolling plugin (like FeedPoller) active so it will
+                // set the 'nohub' state to 'inactive' for us.
+                return;
             } else {
                 // TRANS: Server exception.
                 throw new ServerException(_m('Attempting to end PuSH subscription for feed with no hub.'));
             }
         }
 
-        return $this->doSubscribe('unsubscribe');
+        $this->doSubscribe('unsubscribe');
     }
 
     /**
@@ -222,33 +264,38 @@ class FeedSub extends Managed_DataObject
      * make sure it's inactive, unsubscribing if necessary.
      *
      * @return boolean true if the subscription is now inactive, false if still active.
+     * @throws NoProfileException in FeedSubSubscriberCount for missing Profile entries
+     * @throws Exception if something goes wrong in unsubscribe() method
      */
     public function garbageCollect()
     {
         if ($this->sub_state == '' || $this->sub_state == 'inactive') {
             // No active PuSH subscription, we can just leave it be.
             return true;
-        } else {
-            // PuSH subscription is either active or in an indeterminate state.
-            // Check if we're out of subscribers, and if so send an unsubscribe.
-            $count = 0;
-            Event::handle('FeedSubSubscriberCount', array($this, &$count));
-
-            if ($count) {
-                common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->uri);
-                return false;
-            } else {
-                common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->uri);
-                return $this->unsubscribe();
-            }
         }
+
+        // PuSH subscription is either active or in an indeterminate state.
+        // Check if we're out of subscribers, and if so send an unsubscribe.
+        $count = 0;
+        Event::handle('FeedSubSubscriberCount', array($this, &$count));
+
+        if ($count > 0) {
+            common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->getUri());
+            return false;
+        }
+
+        common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->getUri());
+        // Unsubscribe throws various Exceptions on failure
+        $this->unsubscribe();
+
+        return true;
     }
 
     static public function renewalCheck()
     {
         $fs = new FeedSub();
         // the "" empty string check is because we historically haven't saved unsubscribed feeds as NULL
-        $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() - INTERVAL 1 day');
+        $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() + INTERVAL 1 day');
         if (!$fs->find()) { // find can be both false and 0, depending on why nothing was found
             throw new NoResultException($fs);
         }
@@ -261,11 +308,17 @@ class FeedSub extends Managed_DataObject
     }
 
     /**
-     * @return boolean  true on successful sub/unsub, false on failure
+     * Setting to subscribe means it is _waiting_ to become active. This
+     * cannot be done in a transaction because there is a chance that the
+     * remote script we're calling (as in the case of PuSHpress) performs
+     * the lookup _while_ we're POSTing data, which means the transaction
+     * never completes (PushcallbackAction gets an 'inactive' state).
+     *
+     * @return boolean true when everything is ok (throws Exception on fail)
+     * @throws Exception on failure, can be HTTPClient's or our own.
      */
     protected function doSubscribe($mode)
     {
-        $this->query('BEGIN');
         $orig = clone($this);
         if ($mode == 'subscribe') {
             $this->secret = common_random_hexstr(32);
@@ -283,7 +336,7 @@ class FeedSub extends Managed_DataObject
                           'hub.verify_token' => 'Deprecated-since-PuSH-0.4', // TODO: rm!
 
                           'hub.secret' => $this->secret,
-                          'hub.topic' => $this->uri);
+                          'hub.topic' => $this->getUri());
             $client = new HTTPClient();
             if ($this->huburi) {
                 $hub = $this->huburi;
@@ -296,32 +349,32 @@ class FeedSub extends Managed_DataObject
                         $client->setAuth($u, $p);
                     }
                 } else {
-                    throw new FeedSubException('WTF?');
+                    throw new FeedSubException('Server could not find a usable PuSH hub.');
                 }
             }
             $response = $client->post($hub, $headers, $post);
             $status = $response->getStatus();
-            if ($status == 202) {
-                $this->query('COMMIT');
+            // PuSH specificed response status code
+            if ($status == 202  || $status == 204) {
                 common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
-                return true;
+                return;
             } else if ($status >= 200 && $status < 300) {
                 common_log(LOG_ERR, __METHOD__ . ": sub req returned unexpected HTTP $status: " . $response->getBody());
             } else {
                 common_log(LOG_ERR, __METHOD__ . ": sub req failed with HTTP $status: " . $response->getBody());
             }
-            $this->query('ROLLBACK');
         } catch (Exception $e) {
-            $this->query('ROLLBACK');
-            // wtf!
-            common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->uri");
+            common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub {$this->huburi} subscribing to {$this->getUri()}");
 
+            // Reset the subscription state.
             $orig = clone($this);
             $this->sub_state = 'inactive';
             $this->update($orig);
-            unset($orig);
+
+            // Throw the Exception again.
+            throw $e;
         }
-        return false;
+        throw new ServerException("{$mode} request failed.");
     }
 
     /**
@@ -380,10 +433,10 @@ class FeedSub extends Managed_DataObject
      */
     public function receive($post, $hmac)
     {
-        common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->uri\"! $hmac $post");
+        common_log(LOG_INFO, __METHOD__ . ": packet for \"" . $this->getUri() . "\"! $hmac $post");
 
-        if ($this->sub_state != 'active') {
-            common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed $this->uri (in state '$this->sub_state')");
+        if (!in_array($this->sub_state, array('active', 'nohub'))) {
+            common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed " . $this->getUri() . " (in state '$this->sub_state')");
             return;
         }
 
@@ -434,13 +487,13 @@ class FeedSub extends Managed_DataObject
                     return true;
                 }
                 if (common_config('feedsub', 'debug')) {
-                    $tempfile = tempnam(sys_get_temp_dir(), 'feedsub-receive');
+                    $tempfile = tempnam(common_get_temp_dir(), 'feedsub-receive');
                     if ($tempfile) {
                         file_put_contents($tempfile, $post);
                     }
-                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed $this->uri on $this->huburi; saved to $tempfile");
+                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi; saved to $tempfile");
                 } else {
-                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed $this->uri on $this->huburi");
+                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac for feed " . $this->getUri() . " on $this->huburi");
                 }
             } else {
                 common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
@@ -454,4 +507,21 @@ class FeedSub extends Managed_DataObject
         }
         return false;
     }
+
+    public function delete($useWhere=false)
+    {
+        try {
+            $oprofile = Ostatus_profile::getKV('feeduri', $this->getUri());
+            if ($oprofile instanceof Ostatus_profile) {
+                // Check if there's a profile. If not, handle the NoProfileException below
+                $profile = $oprofile->localProfile();
+            }
+        } catch (NoProfileException $e) {
+            // If the Ostatus_profile has no local Profile bound to it, let's clean it out at the same time
+            $oprofile->delete();
+        } catch (NoUriException $e) {
+            // FeedSub->getUri() can throw a NoUriException, let's just go ahead and delete it
+        }
+        return parent::delete($useWhere);
+    }
 }