]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
OStatus: renamed feedinfo table to ostatus_profile -- will cover remote ostatus peopl...
authorBrion Vibber <brion@pobox.com>
Fri, 12 Feb 2010 00:22:16 +0000 (00:22 +0000)
committerBrion Vibber <brion@pobox.com>
Fri, 12 Feb 2010 00:22:16 +0000 (00:22 +0000)
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/actions/feedsubsettings.php
plugins/OStatus/actions/ostatussub.php
plugins/OStatus/actions/pushcallback.php
plugins/OStatus/classes/Feedinfo.php [deleted file]
plugins/OStatus/classes/Ostatus_profile.php [new file with mode: 0644]
plugins/OStatus/lib/feedmunger.php

index c0f9dadc4a171a8c0031409960c05341500ed99f..8444c3d73d2f2a35aaab5e4cdb5325fd2c9c24b2 100644 (file)
@@ -251,14 +251,14 @@ class OStatusPlugin extends Plugin
      */
     function onEndUnsubscribe($user, $other)
     {
-        $feed = Feedinfo::staticGet('profile_id', $other->id);
+        $profile = Ostatus_profile::staticGet('profile_id', $other->id);
         if ($feed) {
             $sub = new Subscription();
             $sub->subscribed = $other->id;
             $sub->limit(1);
             if (!$sub->find(true)) {
                 common_log(LOG_INFO, "Unsubscribing from now-unused feed $feed->feeduri on hub $feed->huburi");
-                $feed->unsubscribe();
+                $profile->unsubscribe();
             }
         }
         return true;
@@ -269,7 +269,7 @@ class OStatusPlugin extends Plugin
      */
     function onCheckSchema() {
         $schema = Schema::get();
-        $schema->ensureTable('feedinfo', Feedinfo::schemaDef());
+        $schema->ensureTable('ostatus_profile', Ostatus_profile::schemaDef());
         $schema->ensureTable('hubsub', HubSub::schemaDef());
         return true;
     }
index 6f592bf5b0890a8f15e2279cc3d8c0dd5cf86b28..af8bf4d25eadf2c00571a7d600a5262ddc8e358b 100644 (file)
@@ -182,9 +182,9 @@ class FeedSubSettingsAction extends ConnectSettingsAction
         }
         
         $this->munger = $discover->feedMunger();
-        $this->feedinfo = $this->munger->feedInfo();
+        $this->profile = $this->munger->ostatusProfile();
 
-        if ($this->feedinfo->huburi == '' && !common_config('feedsub', 'nohub')) {
+        if ($this->profile->huburi == '' && !common_config('feedsub', 'nohub')) {
             $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.'));
             return false;
         }
@@ -196,13 +196,13 @@ class FeedSubSettingsAction extends ConnectSettingsAction
     {
         if ($this->validateFeed()) {
             $this->preview = true;
-            $this->feedinfo = Feedinfo::ensureProfile($this->munger);
+            $this->profile = Ostatus_profile::ensureProfile($this->munger);
 
             // If not already in use, subscribe to updates via the hub
-            if ($this->feedinfo->sub_start) {
-                common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}");
+            if ($this->profile->sub_start) {
+                common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->profile->feeduri} last subbed {$this->profile->sub_start}");
             } else {
-                $ok = $this->feedinfo->subscribe();
+                $ok = $this->profile->subscribe();
                 common_log(LOG_INFO, __METHOD__ . ": sub was $ok");
                 if (!$ok) {
                     $this->showForm(_m('Feed subscription failed! Bad response from hub.'));
@@ -212,15 +212,15 @@ class FeedSubSettingsAction extends ConnectSettingsAction
 
             // And subscribe the current user to the local profile
             $user = common_current_user();
-            $profile = $this->feedinfo->getProfile();
+            $profile = $this->profile->getLocalProfile();
             if (!$profile) {
                 throw new ServerException("Feed profile was not saved properly.");
             }
 
-            if ($this->feedinfo->isGroup()) {
+            if ($this->profile->isGroup()) {
                 if ($user->isMember($profile)) {
                     $this->showForm(_m('Already a member!'));
-                } elseif (Group_member::join($this->feedinfo->group_id, $user->id)) {
+                } elseif (Group_member::join($this->profile->group_id, $user->id)) {
                     $this->showForm(_m('Joined remote group!'));
                 } else {
                     $this->showForm(_m('Remote group join failed!'));
@@ -247,7 +247,7 @@ class FeedSubSettingsAction extends ConnectSettingsAction
 
     function previewFeed()
     {
-        $feedinfo = $this->munger->feedinfo();
+        $profile = $this->munger->ostatusProfile();
         $notice = $this->munger->notice(0, true); // preview
 
         if ($notice) {
index ffc4ae8dfe24f46c66044d7b8296622ff71a8b4d..9774286fddaf3c6c538538fa734cf20ef96e4b9a 100644 (file)
@@ -164,9 +164,9 @@ class OStatusSubAction extends Action
         }
         
         $this->munger = $discover->feedMunger();
-        $this->feedinfo = $this->munger->feedInfo();
+        $this->profile = $this->munger->ostatusProfile();
 
-        if ($this->feedinfo->huburi == '') {
+        if ($this->profile->huburi == '') {
             $this->showForm(_m('Feed is not PuSH-enabled; cannot subscribe.'));
             return false;
         }
@@ -178,13 +178,13 @@ class OStatusSubAction extends Action
     {
         if ($this->validateFeed()) {
             $this->preview = true;
-            $this->feedinfo = Feedinfo::ensureProfile($this->munger);
+            $this->profile = Ostatus_profile::ensureProfile($this->munger);
 
             // If not already in use, subscribe to updates via the hub
-            if ($this->feedinfo->sub_start) {
-                common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->feedinfo->feeduri} last subbed {$this->feedinfo->sub_start}");
+            if ($this->profile->sub_start) {
+                common_log(LOG_INFO, __METHOD__ . ": double the fun! new sub for {$this->profile->feeduri} last subbed {$this->profile->sub_start}");
             } else {
-                $ok = $this->feedinfo->subscribe();
+                $ok = $this->profile->subscribe();
                 common_log(LOG_INFO, __METHOD__ . ": sub was $ok");
                 if (!$ok) {
                     $this->showForm(_m('Feed subscription failed! Bad response from hub.'));
@@ -194,7 +194,7 @@ class OStatusSubAction extends Action
             
             // And subscribe the current user to the local profile
             $user = common_current_user();
-            $profile = $this->feedinfo->getProfile();
+            $profile = $this->profile->getProfile();
             
             if ($user->isSubscribed($profile)) {
                 $this->showForm(_m('Already subscribed!'));
@@ -209,7 +209,7 @@ class OStatusSubAction extends Action
     
     function previewFeed()
     {
-        $feedinfo = $this->munger->feedinfo();
+        $profile = $this->munger->ostatusProfile();
         $notice = $this->munger->notice(0, true); // preview
 
         if ($notice) {
index 471d079ab91e33ac510193760943765861670e04..a446593ff998cb0d435aeff885d434dd13593767 100644 (file)
@@ -48,9 +48,9 @@ class PushCallbackAction extends Action
             throw new ServerException('Empty or invalid feed id', 400);
         }
 
-        $feedinfo = Feedinfo::staticGet('id', $feedid);
-        if (!$feedinfo) {
-            throw new ServerException('Unknown feed id ' . $feedid, 400);
+        $profile = Ostatus_profile::staticGet('id', $feedid);
+        if (!$profile) {
+            throw new ServerException('Unknown OStatus/PuSH feed id ' . $feedid, 400);
         }
 
         $hmac = '';
@@ -59,7 +59,7 @@ class PushCallbackAction extends Action
         }
 
         $post = file_get_contents('php://input');
-        $feedinfo->postUpdates($post, $hmac);
+        $profile->postUpdates($post, $hmac);
     }
     
     /**
@@ -78,8 +78,8 @@ class PushCallbackAction extends Action
             throw new ServerException("Bogus hub callback: bad mode", 404);
         }
         
-        $feedinfo = Feedinfo::staticGet('feeduri', $topic);
-        if (!$feedinfo) {
+        $profile = Ostatus_profile::staticGet('feeduri', $topic);
+        if (!$profile) {
             common_log(LOG_WARNING, __METHOD__ . ": bogus hub callback for unknown feed $topic");
             throw new ServerException("Bogus hub callback: unknown feed", 404);
         }
@@ -93,16 +93,16 @@ class PushCallbackAction extends Action
         // OK!
         if ($mode == 'subscribe') {
             common_log(LOG_INFO, __METHOD__ . ': sub confirmed');
-            $feedinfo->sub_start = common_sql_date(time());
+            $profile->sub_start = common_sql_date(time());
             if ($lease_seconds > 0) {
-                $feedinfo->sub_end = common_sql_date(time() + $lease_seconds);
+                $profile->sub_end = common_sql_date(time() + $lease_seconds);
             } else {
-                $feedinfo->sub_end = null;
+                $profile->sub_end = null;
             }
-            $feedinfo->update();
+            $profile->update();
         } else {
             common_log(LOG_INFO, __METHOD__ . ": unsub confirmed; deleting sub record for $topic");
-            $feedinfo->delete();
+            $profile->delete();
         }
 
         print $challenge;
diff --git a/plugins/OStatus/classes/Feedinfo.php b/plugins/OStatus/classes/Feedinfo.php
deleted file mode 100644 (file)
index 5b8a903..0000000
+++ /dev/null
@@ -1,408 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009-2010, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * @package FeedSubPlugin
- * @maintainer Brion Vibber <brion@status.net>
- */
-
-/*
-PuSH subscription flow:
-
-    $feedinfo->subscribe()
-        generate random verification token
-            save to verify_token
-        sends a sub request to the hub...
-    
-    feedsub/callback
-        hub sends confirmation back to us via GET
-        We verify the request, then echo back the challenge.
-        On our end, we save the time we subscribed and the lease expiration
-    
-    feedsub/callback
-        hub sends us updates via POST
-    
-*/
-
-class FeedDBException extends FeedSubException
-{
-    public $obj;
-
-    function __construct($obj)
-    {
-        parent::__construct('Database insert failure');
-        $this->obj = $obj;
-    }
-}
-
-class Feedinfo extends Memcached_DataObject
-{
-    public $__table = 'feedinfo';
-
-    public $id;
-    public $profile_id;
-
-    public $feeduri;
-    public $homeuri;
-    public $huburi;
-
-    // PuSH subscription data
-    public $secret;
-    public $verify_token;
-    public $sub_start;
-    public $sub_end;
-
-    public $created;
-    public $lastupdate;
-
-
-    public /*static*/ function staticGet($k, $v=null)
-    {
-        return parent::staticGet(__CLASS__, $k, $v);
-    }
-
-    /**
-     * 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,
-                     'profile_id' => DB_DATAOBJECT_INT,
-                     'group_id' => DB_DATAOBJECT_INT,
-                     'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
-                     'huburi' =>  DB_DATAOBJECT_STR,
-                     'secret' => DB_DATAOBJECT_STR,
-                     'verify_token' => DB_DATAOBJECT_STR,
-                     'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
-                     'salmonuri' =>  DB_DATAOBJECT_STR,
-                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
-                     'lastupdate' => 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*/ '0',
-                                   /*extra*/ null,
-                                   /*auto_increment*/ true),
-                     new ColumnDef('profile_id', 'integer',
-                                   null, true, 'UNI'),
-                     new ColumnDef('group_id', 'integer',
-                                   null, true, 'UNI'),
-                     new ColumnDef('feeduri', 'varchar',
-                                   255, false, 'UNI'),
-                     new ColumnDef('homeuri', 'varchar',
-                                   255, false),
-                     new ColumnDef('huburi', 'text',
-                                   null, true),
-                     new ColumnDef('verify_token', 'varchar',
-                                   32, true),
-                     new ColumnDef('secret', 'varchar',
-                                   64, true),
-                     new ColumnDef('sub_start', 'datetime',
-                                   null, true),
-                     new ColumnDef('sub_end', 'datetime',
-                                   null, true),
-                     new ColumnDef('salmonuri', 'text',
-                                   null, true),
-                     new ColumnDef('created', 'datetime',
-                                   null, false),
-                     new ColumnDef('lastupdate', '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
-     */
-
-    function keys()
-    {
-        return array_keys($this->keyTypes());
-    }
-
-    /**
-     * return key definitions for Memcached_DataObject
-     *
-     * Our caching system uses the same key definitions, but uses a different
-     * method to get them.
-     *
-     * @return array key definitions
-     */
-
-    function keyTypes()
-    {
-        return array('id' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U');
-    }
-
-    function sequenceKey()
-    {
-        return array('id', true, false);
-    }
-
-    /**
-     * Fetch the StatusNet-side profile for this feed
-     * @return Profile
-     */
-    public function getProfile()
-    {
-        return Profile::staticGet('id', $this->profile_id);
-    }
-
-    /**
-     * @param FeedMunger $munger
-     * @param boolean $isGroup is this a group record?
-     * @return Feedinfo
-     */
-    public static function ensureProfile($munger)
-    {
-        $feedinfo = $munger->feedinfo();
-
-        $current = self::staticGet('feeduri', $feedinfo->feeduri);
-        if ($current) {
-            // @fixme we should probably update info as necessary
-            return $current;
-        }
-
-        $feedinfo->query('BEGIN');
-
-        // Awful hack! Awful hack!
-        $feedinfo->verify = common_good_rand(16);
-        $feedinfo->secret = common_good_rand(32);
-
-        try {
-            $profile = $munger->profile();
-            $result = $profile->insert();
-            if (empty($result)) {
-                throw new FeedDBException($profile);
-            }
-
-            $avatar = $munger->getAvatar();
-            if ($avatar) {
-                // @fixme this should be better encapsulated
-                // ripped from oauthstore.php (for old OMB client)
-                $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
-                copy($avatar, $temp_filename);
-                $imagefile = new ImageFile($profile->id, $temp_filename);
-                $filename = Avatar::filename($profile->id,
-                                             image_type_to_extension($imagefile->type),
-                                             null,
-                                             common_timestamp());
-                rename($temp_filename, Avatar::path($filename));
-                $profile->setOriginal($filename);
-            }
-
-            $feedinfo->profile_id = $profile->id;
-            if ($feedinfo->isGroup()) {
-                $group = new User_group();
-                $group->nickname = $profile->nickname . '@remote'; // @fixme
-                $group->fullname = $profile->fullname;
-                $group->homepage = $profile->homepage;
-                $group->location = $profile->location;
-                $group->created = $profile->created;
-                $group->insert();
-
-                if ($avatar) {
-                    $group->setOriginal($filename);
-                }
-
-                $feedinfo->group_id = $group->id;
-            }
-
-            $result = $feedinfo->insert();
-            if (empty($result)) {
-                throw new FeedDBException($feedinfo);
-            }
-
-            $feedinfo->query('COMMIT');
-        } catch (FeedDBException $e) {
-            common_log_db_error($e->obj, 'INSERT', __FILE__);
-            $feedinfo->query('ROLLBACK');
-            return false;
-        }
-        return $feedinfo;
-    }
-
-    /**
-     * Damn dirty hack!
-     */
-    function isGroup()
-    {
-        return (strpos($this->feeduri, '/groups/') !== false);
-    }
-
-    /**
-     * 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
-     */
-    public function subscribe($mode='subscribe')
-    {
-        if (common_config('feedsub', 'nohub')) {
-            // Fake it! We're just testing remote feeds w/o hubs.
-            return true;
-        }
-        // @fixme use the verification token
-        #$token = md5(mt_rand() . ':' . $this->feeduri);
-        #$this->verify_token = $token;
-        #$this->update(); // @fixme
-        try {
-            $callback = common_local_url('pushcallback', array('feed' => $this->id));
-            $headers = array('Content-Type: application/x-www-form-urlencoded');
-            $post = array('hub.mode' => $mode,
-                          'hub.callback' => $callback,
-                          'hub.verify' => 'async',
-                          'hub.verify_token' => $this->verify_token,
-                          'hub.secret' => $this->secret,
-                          //'hub.lease_seconds' => 0,
-                          'hub.topic' => $this->feeduri);
-            $client = new HTTPClient();
-            $response = $client->post($this->huburi, $headers, $post);
-            $status = $response->getStatus();
-            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->feeduri");
-            return false;
-        }
-    }
-
-    /**
-     * Send an unsubscription 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
-     */
-    public function unsubscribe() {
-        return $this->subscribe('unsubscribe');
-    }
-
-    /**
-     * Read and post notices for updates from the feed.
-     * Currently assumes that all items in the feed are new,
-     * coming from a PuSH hub.
-     *
-     * @param string $xml source of Atom or RSS feed
-     * @param string $hmac X-Hub-Signature header, if present
-     */
-    public function postUpdates($xml, $hmac)
-    {
-        common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml");
-
-        if ($this->secret) {
-            if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) {
-                $their_hmac = strtolower($matches[1]);
-                $our_hmac = hash_hmac('sha1', $xml, $this->secret);
-                if ($their_hmac !== $our_hmac) {
-                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac");
-                    return;
-                }
-            } else {
-                common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
-                return;
-            }
-        } else if ($hmac) {
-            common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'");
-            return;
-        }
-
-        require_once "XML/Feed/Parser.php";
-        $feed = new XML_Feed_Parser($xml, false, false, true);
-        $munger = new FeedMunger($feed);
-        
-        $hits = 0;
-        foreach ($feed as $index => $entry) {
-            // @fixme this might sort in wrong order if we get multiple updates
-
-            $notice = $munger->notice($index);
-
-            // Double-check for oldies
-            // @fixme this could explode horribly for multiple feeds on a blog. sigh
-            $dupe = new Notice();
-            $dupe->uri = $notice->uri;
-            if ($dupe->find(true)) {
-                common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}");
-                continue;
-            }
-
-            // @fixme need to ensure that groups get handled correctly
-            $saved = Notice::saveNew($notice->profile_id,
-                                     $notice->content,
-                                     'ostatus',
-                                     array('is_local' => Notice::REMOTE_OMB,
-                                           'uri' => $notice->uri,
-                                           'lat' => $notice->lat,
-                                           'lon' => $notice->lon,
-                                           'location_ns' => $notice->location_ns,
-                                           'location_id' => $notice->location_id));
-
-            /*
-            common_log(LOG_DEBUG, "going to check group delivery...");
-            if ($this->group_id) {
-                $group = User_group::staticGet($this->group_id);
-                if ($group) {
-                    common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname");
-                    $groups = array($group);
-                } else {
-                    common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?");
-                }
-            } else {
-                common_log(LOG_INFO, __METHOD__ . ": no local shadow groups");
-                $groups = array();
-            }
-            common_log(LOG_DEBUG, "going to add to inboxes...");
-            $notice->addToInboxes($groups, array());
-            common_log(LOG_DEBUG, "added to inboxes.");
-            */
-
-            $hits++;
-        }
-        if ($hits == 0) {
-            common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml");
-        }
-    }
-}
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
new file mode 100644 (file)
index 0000000..748ecce
--- /dev/null
@@ -0,0 +1,415 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009-2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @package FeedSubPlugin
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+
+/*
+PuSH subscription flow:
+
+    $profile->subscribe()
+        generate random verification token
+            save to verify_token
+        sends a sub request to the hub...
+    
+    main/push/callback
+        hub sends confirmation back to us via GET
+        We verify the request, then echo back the challenge.
+        On our end, we save the time we subscribed and the lease expiration
+    
+    main/push/callback
+        hub sends us updates via POST
+    
+*/
+
+class FeedDBException extends FeedSubException
+{
+    public $obj;
+
+    function __construct($obj)
+    {
+        parent::__construct('Database insert failure');
+        $this->obj = $obj;
+    }
+}
+
+class Ostatus_profile extends Memcached_DataObject
+{
+    public $__table = 'ostatus_profile';
+
+    public $id;
+    public $profile_id;
+    public $group_id;
+
+    public $feeduri;
+    public $homeuri;
+
+    // PuSH subscription data
+    public $huburi;
+    public $secret;
+    public $verify_token;
+    public $sub_state; // subscribe, active, unsubscribe
+    public $sub_start;
+    public $sub_end;
+
+    public $salmonuri;
+
+    public $created;
+    public $lastupdate;
+
+
+    public /*static*/ function staticGet($k, $v=null)
+    {
+        return parent::staticGet(__CLASS__, $k, $v);
+    }
+
+    /**
+     * 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,
+                     'profile_id' => DB_DATAOBJECT_INT,
+                     'group_id' => DB_DATAOBJECT_INT,
+                     'feeduri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'homeuri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'huburi' =>  DB_DATAOBJECT_STR,
+                     'secret' => DB_DATAOBJECT_STR,
+                     'verify_token' => DB_DATAOBJECT_STR,
+                     'sub_state' => DB_DATAOBJECT_STR,
+                     'sub_start' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+                     'sub_end' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME,
+                     'salmonuri' =>  DB_DATAOBJECT_STR,
+                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+                     'lastupdate' => 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*/ '0',
+                                   /*extra*/ null,
+                                   /*auto_increment*/ true),
+                     new ColumnDef('profile_id', 'integer',
+                                   null, true, 'UNI'),
+                     new ColumnDef('group_id', 'integer',
+                                   null, true, 'UNI'),
+                     new ColumnDef('feeduri', 'varchar',
+                                   255, false, 'UNI'),
+                     new ColumnDef('homeuri', 'varchar',
+                                   255, false),
+                     new ColumnDef('huburi', 'text',
+                                   null, true),
+                     new ColumnDef('verify_token', 'varchar',
+                                   32, true),
+                     new ColumnDef('secret', 'varchar',
+                                   64, true),
+                     new ColumnDef('sub_state', "enum('subscribe','active','unsubscribe')",
+                                   null, true),
+                     new ColumnDef('sub_start', 'datetime',
+                                   null, true),
+                     new ColumnDef('sub_end', 'datetime',
+                                   null, true),
+                     new ColumnDef('salmonuri', 'text',
+                                   null, true),
+                     new ColumnDef('created', 'datetime',
+                                   null, false),
+                     new ColumnDef('lastupdate', '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
+     */
+
+    function keys()
+    {
+        return array_keys($this->keyTypes());
+    }
+
+    /**
+     * return key definitions for Memcached_DataObject
+     *
+     * Our caching system uses the same key definitions, but uses a different
+     * method to get them.
+     *
+     * @return array key definitions
+     */
+
+    function keyTypes()
+    {
+        return array('id' => 'K', 'profile_id' => 'U', 'group_id' => 'U', 'feeduri' => 'U');
+    }
+
+    function sequenceKey()
+    {
+        return array('id', true, false);
+    }
+
+    /**
+     * Fetch the StatusNet-side profile for this feed
+     * @return Profile
+     */
+    public function getLocalProfile()
+    {
+        return Profile::staticGet('id', $this->profile_id);
+    }
+
+    /**
+     * @param FeedMunger $munger
+     * @param boolean $isGroup is this a group record?
+     * @return Ostatus_profile
+     */
+    public static function ensureProfile($munger)
+    {
+        $entity = $munger->ostatusProfile();
+
+        $current = self::staticGet('feeduri', $entity->feeduri);
+        if ($current) {
+            // @fixme we should probably update info as necessary
+            return $current;
+        }
+
+        $entity->query('BEGIN');
+
+        // Awful hack! Awful hack!
+        $entity->verify = common_good_rand(16);
+        $entity->secret = common_good_rand(32);
+
+        try {
+            $profile = $munger->profile();
+            $result = $profile->insert();
+            if (empty($result)) {
+                throw new FeedDBException($profile);
+            }
+
+            $avatar = $munger->getAvatar();
+            if ($avatar) {
+                // @fixme this should be better encapsulated
+                // ripped from oauthstore.php (for old OMB client)
+                $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+                copy($avatar, $temp_filename);
+                $imagefile = new ImageFile($profile->id, $temp_filename);
+                $filename = Avatar::filename($profile->id,
+                                             image_type_to_extension($imagefile->type),
+                                             null,
+                                             common_timestamp());
+                rename($temp_filename, Avatar::path($filename));
+                $profile->setOriginal($filename);
+            }
+
+            $entity->profile_id = $profile->id;
+            if ($entity->isGroup()) {
+                $group = new User_group();
+                $group->nickname = $profile->nickname . '@remote'; // @fixme
+                $group->fullname = $profile->fullname;
+                $group->homepage = $profile->homepage;
+                $group->location = $profile->location;
+                $group->created = $profile->created;
+                $group->insert();
+
+                if ($avatar) {
+                    $group->setOriginal($filename);
+                }
+
+                $entity->group_id = $group->id;
+            }
+
+            $result = $entity->insert();
+            if (empty($result)) {
+                throw new FeedDBException($entity);
+            }
+
+            $entity->query('COMMIT');
+        } catch (FeedDBException $e) {
+            common_log_db_error($e->obj, 'INSERT', __FILE__);
+            $entity->query('ROLLBACK');
+            return false;
+        }
+        return $entity;
+    }
+
+    /**
+     * Damn dirty hack!
+     */
+    function isGroup()
+    {
+        return (strpos($this->feeduri, '/groups/') !== false);
+    }
+
+    /**
+     * 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
+     */
+    public function subscribe($mode='subscribe')
+    {
+        if (common_config('feedsub', 'nohub')) {
+            // Fake it! We're just testing remote feeds w/o hubs.
+            return true;
+        }
+        // @fixme use the verification token
+        #$token = md5(mt_rand() . ':' . $this->feeduri);
+        #$this->verify_token = $token;
+        #$this->update(); // @fixme
+        try {
+            $callback = common_local_url('pushcallback', array('feed' => $this->id));
+            $headers = array('Content-Type: application/x-www-form-urlencoded');
+            $post = array('hub.mode' => $mode,
+                          'hub.callback' => $callback,
+                          'hub.verify' => 'async',
+                          'hub.verify_token' => $this->verify_token,
+                          'hub.secret' => $this->secret,
+                          //'hub.lease_seconds' => 0,
+                          'hub.topic' => $this->feeduri);
+            $client = new HTTPClient();
+            $response = $client->post($this->huburi, $headers, $post);
+            $status = $response->getStatus();
+            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->feeduri");
+            return false;
+        }
+    }
+
+    /**
+     * Send an unsubscription 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
+     */
+    public function unsubscribe() {
+        return $this->subscribe('unsubscribe');
+    }
+
+    /**
+     * Read and post notices for updates from the feed.
+     * Currently assumes that all items in the feed are new,
+     * coming from a PuSH hub.
+     *
+     * @param string $xml source of Atom or RSS feed
+     * @param string $hmac X-Hub-Signature header, if present
+     */
+    public function postUpdates($xml, $hmac)
+    {
+        common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml");
+
+        if ($this->secret) {
+            if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) {
+                $their_hmac = strtolower($matches[1]);
+                $our_hmac = hash_hmac('sha1', $xml, $this->secret);
+                if ($their_hmac !== $our_hmac) {
+                    common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac");
+                    return;
+                }
+            } else {
+                common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'");
+                return;
+            }
+        } else if ($hmac) {
+            common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'");
+            return;
+        }
+
+        require_once "XML/Feed/Parser.php";
+        $feed = new XML_Feed_Parser($xml, false, false, true);
+        $munger = new FeedMunger($feed);
+        
+        $hits = 0;
+        foreach ($feed as $index => $entry) {
+            // @fixme this might sort in wrong order if we get multiple updates
+
+            $notice = $munger->notice($index);
+
+            // Double-check for oldies
+            // @fixme this could explode horribly for multiple feeds on a blog. sigh
+            $dupe = new Notice();
+            $dupe->uri = $notice->uri;
+            if ($dupe->find(true)) {
+                common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}");
+                continue;
+            }
+
+            // @fixme need to ensure that groups get handled correctly
+            $saved = Notice::saveNew($notice->profile_id,
+                                     $notice->content,
+                                     'ostatus',
+                                     array('is_local' => Notice::REMOTE_OMB,
+                                           'uri' => $notice->uri,
+                                           'lat' => $notice->lat,
+                                           'lon' => $notice->lon,
+                                           'location_ns' => $notice->location_ns,
+                                           'location_id' => $notice->location_id));
+
+            /*
+            common_log(LOG_DEBUG, "going to check group delivery...");
+            if ($this->group_id) {
+                $group = User_group::staticGet($this->group_id);
+                if ($group) {
+                    common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname");
+                    $groups = array($group);
+                } else {
+                    common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?");
+                }
+            } else {
+                common_log(LOG_INFO, __METHOD__ . ": no local shadow groups");
+                $groups = array();
+            }
+            common_log(LOG_DEBUG, "going to add to inboxes...");
+            $notice->addToInboxes($groups, array());
+            common_log(LOG_DEBUG, "added to inboxes.");
+            */
+
+            $hits++;
+        }
+        if ($hits == 0) {
+            common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml");
+        }
+    }
+}
index 927a2fe7a70d1d57a1bcf35627cb0d44f4913506..c895b6ce24afde9a48ae991530415cc457437045 100644 (file)
@@ -83,17 +83,17 @@ class FeedMunger
         $this->url = $url;
     }
     
-    function feedinfo()
+    function ostatusProfile()
     {
-        $feedinfo = new Feedinfo();
-        $feedinfo->feeduri = $this->url;
-        $feedinfo->homeuri = $this->feed->link;
-        $feedinfo->huburi = $this->getHubLink();
+        $profile = new Ostatus_profile();
+        $profile->feeduri = $this->url;
+        $profile->homeuri = $this->feed->link;
+        $profile->huburi = $this->getHubLink();
         $salmon = $this->getSalmonLink();
         if ($salmon) {
-            $feedinfo->salmonuri = $salmon;
+            $profile->salmonuri = $salmon;
         }
-        return $feedinfo;
+        return $profile;
     }
 
     function getAtomLink($item, $attribs=array())
@@ -258,9 +258,7 @@ class FeedMunger
     {
         // hack hack hack
         // should get profile for this entry's author...
-        $feed = new Feedinfo();
-        $feed->feeduri = $self;
-        $feed = Feedinfo::staticGet('feeduri', $this->getSelfLink());
+        $remote = Ostatus_profile::staticGet('feeduri', $this->getSelfLink());
         if ($feed) {
             return $feed->profile_id;
         } else {