]> git.mxchange.org Git - quix0rs-gnu-social.git/blobdiff - plugins/OStatus/classes/FeedSub.php
Code cleanup and enabling User object's etc. getUri()
[quix0rs-gnu-social.git] / plugins / OStatus / classes / FeedSub.php
index 7756f6a234325a5a50e751ce65c4c168bc7cdd7f..0a6abfa1bef239346fe4c30ae6d8da4943eb643b 100644 (file)
@@ -30,8 +30,6 @@ if (!defined('STATUSNET')) {
 PuSH subscription flow:
 
     $profile->subscribe()
-        generate random verification token
-            save to verify_token
         sends a sub request to the hub...
 
     main/push/callback
@@ -59,7 +57,7 @@ class FeedDBException extends FeedSubException
  * Higher-level behavior building OStatus stuff on top is handled
  * under Ostatus_profile.
  */
-class FeedSub extends Memcached_DataObject
+class FeedSub extends Managed_DataObject
 {
     public $__table = 'feedsub';
 
@@ -69,8 +67,7 @@ class FeedSub extends Memcached_DataObject
     // PuSH subscription data
     public $huburi;
     public $secret;
-    public $verify_token;
-    public $sub_state; // subscribe, active, unsubscribe, inactive
+    public $sub_state; // subscribe, active, unsubscribe, inactive, nohub
     public $sub_start;
     public $sub_end;
     public $last_update;
@@ -78,94 +75,57 @@ class FeedSub extends Memcached_DataObject
     public $created;
     public $modified;
 
-    public /*static*/ function staticGet($k, $v=null)
+    public static function schemaDef()
     {
-        return parent::staticGet(__CLASS__, $k, $v);
+        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'),
+                '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","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'),
+                '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'),
+            ),
+            'primary key' => array('id'),
+            'unique keys' => array(
+                'feedsub_uri_key' => array('uri'),
+            ),
+        );
     }
 
     /**
-     * return table definition for DB_DataObject
-     *
-     * DB_DataObject needs to know something about the table to manipulate
-     * instances. This method provides all the DB_DataObject needs to know.
-     *
-     * @return array array of column definitions
-     */
-    function table()
-    {
-        return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
-                     'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'huburi' =>  DB_DATAOBJECT_STR,
-                     'secret' => DB_DATAOBJECT_STR,
-                     'verify_token' => DB_DATAOBJECT_STR,
-                     'sub_state' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'last_update' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
-                     'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
-    }
-
-    static function schemaDef()
-    {
-        return array(new ColumnDef('id', 'integer',
-                                   /*size*/ null,
-                                   /*nullable*/ false,
-                                   /*key*/ 'PRI',
-                                   /*default*/ null,
-                                   /*extra*/ null,
-                                   /*auto_increment*/ true),
-                     new ColumnDef('uri', 'varchar',
-                                   255, false, 'UNI'),
-                     new ColumnDef('huburi', 'text',
-                                   null, true),
-                     new ColumnDef('verify_token', 'text',
-                                   null, true),
-                     new ColumnDef('secret', 'text',
-                                   null, true),
-                     new ColumnDef('sub_state', "enum('subscribe','active','unsubscribe','inactive')",
-                                   null, false),
-                     new ColumnDef('sub_start', 'datetime',
-                                   null, true),
-                     new ColumnDef('sub_end', 'datetime',
-                                   null, true),
-                     new ColumnDef('last_update', 'datetime',
-                                   null, false),
-                     new ColumnDef('created', 'datetime',
-                                   null, false),
-                     new ColumnDef('modified', 'datetime',
-                                   null, false));
-    }
-
-    /**
-     * return key definitions for DB_DataObject
-     *
-     * DB_DataObject needs to know about keys that the table has; this function
-     * defines them.
-     *
-     * @return array key definitions
+     * Get the feed uri (http/https)
      */
-    function keys()
+    public function getUri()
     {
-        return array_keys($this->keyTypes());
+        if (empty($this->uri)) {
+            throw new ServerException('No URI for FeedSub entry');
+        }
+        return $this->uri;
     }
 
     /**
-     * return key definitions for Memcached_DataObject
-     *
-     * Our caching system uses the same key definitions, but uses a different
-     * method to get them.
+     * Do we have a hub? Then we are a PuSH feed.
+     * https://en.wikipedia.org/wiki/PubSubHubbub
      *
-     * @return array key definitions
+     * 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.
      */
-    function keyTypes()
-    {
-        return array('id' => 'K', 'uri' => 'U');
-    }
-
-    function sequenceKey()
+    public function isPuSH()
     {
-        return array('id', true, false);
+        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;
     }
 
     /**
@@ -175,7 +135,7 @@ class FeedSub extends Memcached_DataObject
     public function localProfile()
     {
         if ($this->profile_id) {
-            return Profile::staticGet('id', $this->profile_id);
+            return Profile::getKV('id', $this->profile_id);
         }
         return null;
     }
@@ -187,7 +147,7 @@ class FeedSub extends Memcached_DataObject
     public function localGroup()
     {
         if ($this->group_id) {
-            return User_group::staticGet('id', $this->group_id);
+            return User_group::getKV('id', $this->group_id);
         }
         return null;
     }
@@ -199,8 +159,8 @@ class FeedSub extends Memcached_DataObject
      */
     public static function ensureFeed($feeduri)
     {
-        $current = self::staticGet('uri', $feeduri);
-        if ($current) {
+        $current = self::getKV('uri', $feeduri);
+        if ($current instanceof FeedSub) {
             return $current;
         }
 
@@ -221,7 +181,7 @@ class FeedSub extends Memcached_DataObject
         $feedsub->modified = common_sql_now();
 
         $result = $feedsub->insert();
-        if (empty($result)) {
+        if ($result === false) {
             throw new FeedDBException($feedsub);
         }
 
@@ -235,11 +195,17 @@ class FeedSub extends Memcached_DataObject
      * @return bool true on success, false on failure
      * @throws ServerException if feed state is not valid
      */
-    public function subscribe($mode='subscribe')
+    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 true;
         }
+
         if (empty($this->huburi)) {
             if (common_config('feedsub', 'fallback_hub')) {
                 // No native hub on this feed?
@@ -249,6 +215,7 @@ class FeedSub extends Memcached_DataObject
                 // We'll never actually get updates in this mode.
                 return true;
             } else {
+                // TRANS: Server exception.
                 throw new ServerException(_m('Attempting to start PuSH subscription for feed with no hub.'));
             }
         }
@@ -268,8 +235,14 @@ class FeedSub extends Memcached_DataObject
      */
     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 true;
         }
+
         if (empty($this->huburi)) {
             if (common_config('feedsub', 'fallback_hub')) {
                 // No native hub on this feed?
@@ -279,6 +252,7 @@ class FeedSub extends Memcached_DataObject
                 // We'll never actually get updates in this mode.
                 return true;
             } else {
+                // TRANS: Server exception.
                 throw new ServerException(_m('Attempting to end PuSH subscription for feed with no hub.'));
             }
         }
@@ -304,21 +278,45 @@ class FeedSub extends Memcached_DataObject
             Event::handle('FeedSubSubscriberCount', array($this, &$count));
 
             if ($count) {
-                common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->uri);
+                common_log(LOG_INFO, __METHOD__ . ': ok, ' . $count . ' user(s) left for ' . $this->getUri());
                 return false;
             } else {
-                common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->uri);
+                common_log(LOG_INFO, __METHOD__ . ': unsubscribing, no users left for ' . $this->getUri());
                 return $this->unsubscribe();
             }
         }
     }
 
+    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');
+        if (!$fs->find()) { // find can be both false and 0, depending on why nothing was found
+            throw new NoResultException($fs);
+        }
+        return $fs;
+    }
+
+    public function renew()
+    {
+        $this->subscribe();
+    }
+
+    /**
+     * 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 on successful sub/unsub, false on failure
+     */
     protected function doSubscribe($mode)
     {
         $orig = clone($this);
-        $this->verify_token = common_good_rand(16);
         if ($mode == 'subscribe') {
-            $this->secret = common_good_rand(32);
+            $this->secret = common_random_hexstr(32);
         }
         $this->sub_state = $mode;
         $this->update($orig);
@@ -329,10 +327,11 @@ class FeedSub extends Memcached_DataObject
             $headers = array('Content-Type: application/x-www-form-urlencoded');
             $post = array('hub.mode' => $mode,
                           'hub.callback' => $callback,
-                          'hub.verify' => 'sync',
-                          'hub.verify_token' => $this->verify_token,
+                          'hub.verify' => 'async',  // TODO: deprecated, remove when noone uses PuSH <0.4 (only 'async' method used there)
+                          '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;
@@ -353,28 +352,21 @@ class FeedSub extends Memcached_DataObject
             if ($status == 202) {
                 common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
                 return true;
-            } else if ($status == 204) {
-                common_log(LOG_INFO, __METHOD__ . ': sub req ok and verified');
-                return true;
             } else if ($status >= 200 && $status < 300) {
                 common_log(LOG_ERR, __METHOD__ . ": sub req returned unexpected HTTP $status: " . $response->getBody());
-                return false;
             } else {
                 common_log(LOG_ERR, __METHOD__ . ": sub req failed with HTTP $status: " . $response->getBody());
-                return false;
             }
         } catch (Exception $e) {
             // 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());
 
             $orig = clone($this);
-            $this->verify_token = '';
             $this->sub_state = 'inactive';
             $this->update($orig);
             unset($orig);
-
-            return false;
         }
+        return false;
     }
 
     /**
@@ -383,7 +375,7 @@ class FeedSub extends Memcached_DataObject
      *
      * @param int $lease_seconds provided hub.lease_seconds parameter, if given
      */
-    public function confirmSubscribe($lease_seconds=0)
+    public function confirmSubscribe($lease_seconds)
     {
         $original = clone($this);
 
@@ -392,7 +384,7 @@ class FeedSub extends Memcached_DataObject
         if ($lease_seconds > 0) {
             $this->sub_end = common_sql_date(time() + $lease_seconds);
         } else {
-            $this->sub_end = null;
+            $this->sub_end = null;  // Backwards compatibility to StatusNet (PuSH <0.4 supported permanent subs)
         }
         $this->modified = common_sql_now();
 
@@ -408,7 +400,6 @@ class FeedSub extends Memcached_DataObject
         $original = clone($this);
 
         // @fixme these should all be null, but DB_DataObject doesn't save null values...?????
-        $this->verify_token = '';
         $this->secret = '';
         $this->sub_state = '';
         $this->sub_start = '';
@@ -434,10 +425,10 @@ class FeedSub extends Memcached_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')");
+            common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed " . $this->getUri() . " (in state '$this->sub_state')");
             return;
         }
 
@@ -492,9 +483,9 @@ class FeedSub extends Memcached_DataObject
                     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'");