]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch 'testing' of git@gitorious.org:statusnet/mainline into 0.9.x
authorBrion Vibber <brion@pobox.com>
Tue, 2 Mar 2010 21:38:10 +0000 (13:38 -0800)
committerBrion Vibber <brion@pobox.com>
Tue, 2 Mar 2010 21:38:10 +0000 (13:38 -0800)
50 files changed:
EVENTS.txt
actions/all.php
actions/apidirectmessage.php
actions/apigrouplist.php
actions/apigroupmembership.php
actions/apitimelinefriends.php
actions/apitimelinegroup.php
actions/apitimelinehome.php
actions/apitimelinementions.php
actions/apitimelinepublic.php
actions/apitimelineuser.php
actions/newnotice.php
actions/postnotice.php
actions/updateprofile.php
classes/Fave.php
classes/Inbox.php
classes/Notice.php
classes/Notice_inbox.php
classes/Notice_tag.php
classes/Profile.php
classes/Reply.php
classes/Subscription.php
classes/User.php
classes/User_group.php
lib/action.php
lib/apiaction.php
lib/default.php
lib/oauthstore.php
lib/omb.php
lib/profilelist.php
lib/util.php
plugins/Facebook/FacebookPlugin.php
plugins/Facebook/README
plugins/Facebook/facebookadminpanel.php [new file with mode: 0644]
plugins/MobileProfile/MobileProfilePlugin.php
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/actions/ostatussub.php
plugins/OStatus/actions/pushhub.php
plugins/OStatus/classes/HubSub.php
plugins/OStatus/classes/Magicsig.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/OStatus/lib/discovery.php
plugins/OStatus/lib/xrd.php
plugins/OStatus/scripts/updateostatus.php [new file with mode: 0644]
plugins/OStatus/theme/base/css/ostatus.css
plugins/SphinxSearch/sphinxsearch.php
plugins/TwitterBridge/README
plugins/TwitterBridge/TwitterBridgePlugin.php
plugins/TwitterBridge/twitteradminpanel.php [new file with mode: 0644]
plugins/TwitterBridge/twitterauthorization.php

index a2b405acc8e07f01a829e93ed69478088929cfb9..bb4936b354b6f58c927b41993d0802656533fcbb 100644 (file)
@@ -770,6 +770,12 @@ StartShowSubscriptionsContent: before showing the subscriptions content
 EndShowSubscriptionsContent: after showing the subscriptions content
 - $action: the current action
 
+StartShowAllContent: before showing the all (you and friends) content
+- $action: the current action
+
+EndShowAllContent: after showing the all (you and friends) content
+- $action: the current action
+
 StartDeleteUserForm: starting the data in the form for deleting a user
 - $action: action being shown
 - $user: user being deleted
index 488d00e2ba85eda3719a421ef4a209556d1d624f..8c22e6f5f076337b5829a791129369beaef2b9db 100644 (file)
@@ -152,18 +152,22 @@ class AllAction extends ProfileAction
 
     function showContent()
     {
-        $nl = new NoticeList($this->notice, $this);
+        if (Event::handle('StartShowAllContent', array($this))) {
+            $nl = new NoticeList($this->notice, $this);
 
-        $cnt = $nl->show();
+            $cnt = $nl->show();
 
-        if (0 == $cnt) {
-            $this->showEmptyListMessage();
-        }
+            if (0 == $cnt) {
+                $this->showEmptyListMessage();
+            }
 
-        $this->pagination(
-            $this->page > 1, $cnt > NOTICES_PER_PAGE,
-            $this->page, 'all', array('nickname' => $this->user->nickname)
-        );
+            $this->pagination(
+                $this->page > 1, $cnt > NOTICES_PER_PAGE,
+                $this->page, 'all', array('nickname' => $this->user->nickname)
+            );
+
+            Event::handle('EndShowAllContent', array($this));
+        }
     }
 
     function showPageTitle()
index 5355acf8253805796cfa5411a857b8a5947d2310..53da9e0c68a297195c74c6b33eee1c8a5648539a 100644 (file)
@@ -182,11 +182,6 @@ class ApiDirectMessageAction extends ApiAuthAction
             $message->whereAdd('id > ' . $this->since_id);
         }
 
-        if (!empty($since)) {
-            $d = date('Y-m-d H:i:s', $this->since);
-            $message->whereAdd("created > '$d'");
-        }
-
         $message->orderBy('created DESC, id DESC');
         $message->limit((($this->page - 1) * $this->count), $this->count);
         $message->find();
index 605b3823261338bcf750b5c1a74ad9c5fae28983..98fdb0497a3c3bb5e59194d254d25d04abc0e901 100644 (file)
@@ -152,8 +152,7 @@ class ApiGroupListAction extends ApiBareAuthAction
             ($this->page - 1) * $this->count,
             $this->count,
             $this->since_id,
-            $this->max_id,
-            $this->since
+            $this->max_id
         );
 
         while ($group->fetch()) {
index 3c7c8e88350de063c5adc051947997ed554c34cc..9f72b527cfd7a43601fcf84de764cf330bc82e63 100644 (file)
@@ -125,8 +125,7 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
             ($this->page - 1) * $this->count,
             $this->count,
             $this->since_id,
-            $this->max_id,
-            $this->since
+            $this->max_id
         );
 
         while ($profile->fetch()) {
index 2db76857e3c7615d43647f24ad27f7867a33fcd8..9ef3ace607eb5b9ef22ad96bd495ca887c413b1c 100644 (file)
@@ -202,11 +202,11 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
         if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
             $notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count,
                                                       $this->count, $this->since_id,
-                                                      $this->max_id, $this->since);
+                                                      $this->max_id);
         } else {
             $notice = $this->user->friendsTimeline(($this->page-1) * $this->count,
                                                    $this->count, $this->since_id,
-                                                   $this->max_id, $this->since);
+                                                   $this->max_id);
         }
 
         while ($notice->fetch()) {
index 04456ffea4826456862de6de03b165f51f461919..d0af49844c64957dadd3cc0902d192051b9e8088 100644 (file)
@@ -204,8 +204,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
             ($this->page-1) * $this->count,
             $this->count,
             $this->since_id,
-            $this->max_id,
-            $this->since
+            $this->max_id
         );
 
         while ($notice->fetch()) {
index 0c72f4020c47d77229bb798752bb482dbaac25c3..abd3877860371cd152b7cb7e34266948e5776a1e 100644 (file)
@@ -200,13 +200,13 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
             $notice = $this->user->noticeInbox(
                 ($this->page-1) * $this->count,
                 $this->count, $this->since_id,
-                $this->max_id, $this->since
+                $this->max_id
             );
         } else {
             $notice = $this->user->noticesWithFriends(
                 ($this->page-1) * $this->count,
                 $this->count, $this->since_id,
-                $this->max_id, $this->since
+                $this->max_id
             );
         }
 
index a39c63346abfa2dc990c8a2c806fed835c1924b7..31627ab7bf36424ea6fa09539388265bb61f96b1 100644 (file)
@@ -189,7 +189,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
 
         $notice = $this->user->getReplies(
             ($this->page - 1) * $this->count, $this->count,
-            $this->since_id, $this->max_id, $this->since
+            $this->since_id, $this->max_id
         );
 
         while ($notice->fetch()) {
index 1ff0fd26172892defc5d48c047fe9da50ffb4423..3e4dad690e83ee80684ef39544ca49b3f5b01e66 100644 (file)
@@ -75,10 +75,6 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
 
         $this->notices = $this->getNotices();
 
-        if ($this->since) {
-            throw new ServerException("since parameter is disabled for performance; use since_id", 403);
-        }
-
         return true;
     }
 
index b3ded97c0fc09152b985e62fa93c3fb3929e2627..94491946c21b5943f61a7c40bfafef2818c70cd8 100644 (file)
@@ -211,7 +211,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
 
         $notice = $this->user->getNotices(
             ($this->page-1) * $this->count, $this->count,
-            $this->since_id, $this->max_id, $this->since
+            $this->since_id, $this->max_id
         );
 
         while ($notice->fetch()) {
index 78480ababb069849508550141ee4c5df9862b78c..ed0fa1b2b5768026e486196d0112a2c46af3f54e 100644 (file)
@@ -294,6 +294,9 @@ class NewnoticeAction extends Action
             if ($profile) {
                 $content = '@' . $profile->nickname . ' ';
             }
+        } else {
+            // @fixme most of these bits above aren't being passed on above
+            $inreplyto = null;
         }
 
         $notice_form = new NoticeForm($this, '', $content, null, $inreplyto);
index fb0670376651ed145b0ea8ea7673eb3f7e07d30a..b2f6f1bb95debe9a57e441bbdc07a96c8a37447e 100644 (file)
@@ -54,7 +54,10 @@ class PostnoticeAction extends Action
      */
     function prepare($argarray)
     {
+        StatusNet::setApi(true); // Send smaller error pages
+
         parent::prepare($argarray);
+
         try {
             $this->checkNotice();
         } catch (Exception $e) {
@@ -71,6 +74,14 @@ class PostnoticeAction extends Action
             $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
                                             omb_oauth_server());
             $srv->handlePostNotice();
+        } catch (OMB_RemoteServiceException $rse) {
+            $msg = $rse->getMessage();
+            if (preg_match('/Revoked accesstoken/', $msg) ||
+                preg_match('/No subscriber/', $msg)) {
+                $this->clientError($msg, 403);
+            } else {
+                $this->clientError($msg);
+            }
         } catch (Exception $e) {
             $this->serverError($e->getMessage());
             return;
index e416a6fa93dbd881ab152249d1abed50dfd2ff52..bae6108cced4e0647982e4b579705ff48ba938c1 100644 (file)
@@ -55,6 +55,8 @@ class UpdateprofileAction extends Action
      */
     function prepare($argarray)
     {
+        StatusNet::setApi(true); // Send smaller error pages
+
         parent::prepare($argarray);
         $license      = $_POST['omb_listenee_license'];
         $site_license = common_config('license', 'url');
@@ -75,6 +77,14 @@ class UpdateprofileAction extends Action
             $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
                                             omb_oauth_server());
             $srv->handleUpdateProfile();
+        } catch (OMB_RemoteServiceException $rse) {
+            $msg = $rse->getMessage();
+            if (preg_match('/Revoked accesstoken/', $msg) ||
+                preg_match('/No subscriber/', $msg)) {
+                $this->clientError($msg, 403);
+            } else {
+                $this->clientError($msg);
+            }
         } catch (Exception $e) {
             $this->serverError($e->getMessage());
             return;
index 0b6eec2bc40af2e2742332fcc1dae6883521e218..a04f15e9c4e63cb1ddc0a0dc606eaa00dc355ccc 100644 (file)
@@ -77,7 +77,7 @@ class Fave extends Memcached_DataObject
         return $ids;
     }
 
-    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id)
     {
         $fav = new Fave();
         $qry = null;
@@ -100,10 +100,6 @@ class Fave extends Memcached_DataObject
             $qry .= 'AND notice_id <= ' . $max_id . ' ';
         }
 
-        if (!is_null($since)) {
-            $qry .= 'AND modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
-        }
-
         // NOTE: we sort by fave time, not by notice time!
 
         $qry .= 'ORDER BY modified DESC ';
index be62611a16504416675d474e6d1920d6d6080273..014ba3d82923efb70d07fe0b70137fba726ee99c 100644 (file)
@@ -137,7 +137,7 @@ class Inbox extends Memcached_DataObject
         }
     }
 
-    function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
+    function stream($user_id, $offset, $limit, $since_id, $max_id, $own=false)
     {
         $inbox = Inbox::staticGet('user_id', $user_id);
 
@@ -195,15 +195,15 @@ class Inbox extends Memcached_DataObject
      * @param int $limit
      * @param mixed $since_id return only notices after but not including this id
      * @param mixed $max_id return only notices up to and including this id
-     * @param mixed $since obsolete/ignored
      * @param mixed $own ignored?
      * @return array of Notice objects
      *
      * @todo consider repacking the inbox when this happens?
+     * @fixme reimplement $own if we need it?
      */
-    function streamNotices($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
+    function streamNotices($user_id, $offset, $limit, $since_id, $max_id, $own=false)
     {
-        $ids = self::stream($user_id, $offset, self::MAX_NOTICES, $since_id, $max_id, $since, $own);
+        $ids = self::stream($user_id, $offset, self::MAX_NOTICES, $since_id, $max_id, $own);
 
         // Do a bulk lookup for the first $limit items
         // Fast path when nothing's deleted.
index 3702dbcfa8e59010b1ee07aba9983eb8b1f1e3a8..63dc9689727cdc41920c3f45fae26434d90884e0 100644 (file)
@@ -559,17 +559,17 @@ class Notice extends Memcached_DataObject
         }
     }
 
-    function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0)
     {
         $ids = Notice::stream(array('Notice', '_publicStreamDirect'),
                               array(),
                               'public',
-                              $offset, $limit, $since_id, $max_id, $since);
+                              $offset, $limit, $since_id, $max_id);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0)
     {
         $notice = new Notice();
 
@@ -598,10 +598,6 @@ class Notice extends Memcached_DataObject
             $notice->whereAdd('id <= ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $ids = array();
 
         if ($notice->find()) {
@@ -616,17 +612,17 @@ class Notice extends Memcached_DataObject
         return $ids;
     }
 
-    function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0)
     {
         $ids = Notice::stream(array('Notice', '_conversationStreamDirect'),
                               array($id),
                               'notice:conversation_ids:'.$id,
-                              $offset, $limit, $since_id, $max_id, $since);
+                              $offset, $limit, $since_id, $max_id);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0)
     {
         $notice = new Notice();
 
@@ -649,10 +645,6 @@ class Notice extends Memcached_DataObject
             $notice->whereAdd('id <= ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $ids = array();
 
         if ($notice->find()) {
@@ -1134,7 +1126,6 @@ class Notice extends Memcached_DataObject
         }
 
         $xs->element('title', null, $this->content);
-        $xs->element('summary', null, $this->content);
 
         $xs->raw($profile->asAtomAuthor());
         $xs->raw($profile->asActivityActor());
@@ -1270,16 +1261,16 @@ class Notice extends Memcached_DataObject
         }
     }
 
-    function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0)
     {
         $cache = common_memcache();
 
         if (empty($cache) ||
-            $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
+            $since_id != 0 || $max_id != 0 ||
             is_null($limit) ||
             ($offset + $limit) > NOTICE_CACHE_WINDOW) {
             return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
-                                                                      $max_id, $since)));
+                                                                      $max_id)));
         }
 
         $idkey = common_cache_key($cachekey);
index c27dcdfd616079359df0f4db998f7c09ca285537..47ed6b22db628a8baaed7bd797c027ff0d5531fa 100644 (file)
@@ -49,12 +49,12 @@ class Notice_inbox extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
+    function stream($user_id, $offset, $limit, $since_id, $max_id, $own=false)
     {
         throw new Exception('Notice_inbox no longer used; use Inbox');
     }
 
-    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id)
     {
         throw new Exception('Notice_inbox no longer used; use Inbox');
     }
index 4fd76e8ea85e8c04c9119b370bf3c89a462d7dec..a5d0716a7136d6b00c073b0679f7afe273340eb0 100644 (file)
@@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($tag, $offset, $limit, $since_id, $max_id)
     {
         $nt = new Notice_tag();
 
@@ -63,10 +63,6 @@ class Notice_tag extends Memcached_DataObject
             $nt->whereAdd('notice_id < ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $nt->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $nt->orderBy('notice_id DESC');
 
         if (!is_null($offset)) {
index 78223b34a1956d0e16901e949810cbc3c367f6ba..470ef33207da5e7e9a49cb9b1f3768512c0d86d2 100644 (file)
@@ -163,27 +163,27 @@ class Profile extends Memcached_DataObject
         return null;
     }
 
-    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
+    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
     {
         $ids = Notice::stream(array($this, '_streamTaggedDirect'),
                               array($tag),
                               'profile:notice_ids_tagged:' . $this->id . ':' . $tag,
-                              $offset, $limit, $since_id, $max_id, $since);
+                              $offset, $limit, $since_id, $max_id);
         return Notice::getStreamByIds($ids);
     }
 
-    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
+    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
     {
         // XXX: I'm not sure this is going to be any faster. It probably isn't.
         $ids = Notice::stream(array($this, '_streamDirect'),
                               array(),
                               'profile:notice_ids:' . $this->id,
-                              $offset, $limit, $since_id, $max_id, $since);
+                              $offset, $limit, $since_id, $max_id);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id, $since)
+    function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id)
     {
         // XXX It would be nice to do this without a join
 
@@ -202,10 +202,6 @@ class Profile extends Memcached_DataObject
             $query .= " and id < $max_id";
         }
 
-        if (!is_null($since)) {
-            $query .= " and created > '" . date('Y-m-d H:i:s', $since) . "'";
-        }
-
         $query .= ' order by id DESC';
 
         if (!is_null($offset)) {
@@ -223,7 +219,7 @@ class Profile extends Memcached_DataObject
         return $ids;
     }
 
-    function _streamDirect($offset, $limit, $since_id, $max_id, $since = null)
+    function _streamDirect($offset, $limit, $since_id, $max_id)
     {
         $notice = new Notice();
 
@@ -240,10 +236,6 @@ class Profile extends Memcached_DataObject
             $notice->whereAdd('id <= ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $notice->orderBy('id DESC');
 
         if (!is_null($offset)) {
index 49b1e05e517e1bd34bb7645cead3148990f27c0b..659e04c9253934ad82dad90b0cd2ce4d198b32f5 100644 (file)
@@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
+    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
     {
         $ids = Notice::stream(array('Reply', '_streamDirect'),
                               array($user_id),
                               'reply:stream:' . $user_id,
-                              $offset, $limit, $since_id, $max_id, $since);
+                              $offset, $limit, $since_id, $max_id);
         return $ids;
     }
 
-    function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
+    function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
     {
         $reply = new Reply();
         $reply->profile_id = $user_id;
@@ -44,10 +44,6 @@ class Reply extends Memcached_DataObject
             $reply->whereAdd('notice_id < ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $reply->whereAdd('modified > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $reply->orderBy('notice_id DESC');
 
         if (!is_null($offset)) {
index d6fb3fcbdda82f17d70dcce6692868dfed6f1840..9cef2df1ad7c222d407fc1c3177fd4fdc1f1fa44 100644 (file)
@@ -172,6 +172,28 @@ class Subscription extends Memcached_DataObject
 
             assert(!empty($sub));
 
+            // @todo: move this block to EndSubscribe handler for
+            // OMB plugin when it exists.
+
+            if (!empty($sub->token)) {
+
+                $token = new Token();
+
+                $token->tok    = $sub->token;
+
+                if ($token->find(true)) {
+
+                    $result = $token->delete();
+
+                    if (!$result) {
+                        common_log_db_error($token, 'DELETE', __FILE__);
+                        throw new Exception(_('Couldn\'t delete subscription OMB token.'));
+                    }
+                } else {
+                    common_log(LOG_ERR, "Couldn't find credentials with token {$token->tok}");
+                }
+            }
+
             $result = $sub->delete();
 
             if (!$result) {
index e0f0d87d1a68faa12368c7ff72b691ddc062eff0..357c1e501343ed324d3de3eb5aa1780dd394be00 100644 (file)
@@ -457,28 +457,28 @@ class User extends Memcached_DataObject
         return $user;
     }
 
-    function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
-        $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
+        $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id);
         return Notice::getStreamByIds($ids);
     }
 
-    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) {
+    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
         $profile = $this->getProfile();
         if (!$profile) {
             return null;
         } else {
-            return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id, $since);
+            return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id);
         }
     }
 
-    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
         $profile = $this->getProfile();
         if (!$profile) {
             return null;
         } else {
-            return $profile->getNotices($offset, $limit, $since_id, $before_id, $since);
+            return $profile->getNotices($offset, $limit, $since_id, $before_id);
         }
     }
 
@@ -488,24 +488,24 @@ class User extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
-        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, false);
+        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false);
     }
 
-    function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
-        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, true);
+        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true);
     }
 
-    function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
-        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, false);
+        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, false);
     }
 
-    function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
     {
-        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, $since, true);
+        return Inbox::streamNotices($this->id, $offset, $limit, $since_id, $before_id, true);
     }
 
     function blowFavesCache()
@@ -790,7 +790,7 @@ class User extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since)
+    function _repeatedByMeDirect($offset, $limit, $since_id, $max_id)
     {
         $notice = new Notice();
 
@@ -814,10 +814,6 @@ class User extends Memcached_DataObject
             $notice->whereAdd('id <= ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $ids = array();
 
         if ($notice->find()) {
@@ -837,12 +833,12 @@ class User extends Memcached_DataObject
         $ids = Notice::stream(array($this, '_repeatsOfMeDirect'),
                               array(),
                               'user:repeats_of_me:'.$this->id,
-                              $offset, $limit, $since_id, $max_id, null);
+                              $offset, $limit, $since_id, $max_id);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since)
+    function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id)
     {
         $qry =
           'SELECT DISTINCT original.id AS id ' .
@@ -857,10 +853,6 @@ class User extends Memcached_DataObject
             $qry .= 'AND original.id <= ' . $max_id . ' ';
         }
 
-        if (!is_null($since)) {
-            $qry .= 'AND original.modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
-        }
-
         // NOTE: we sort by fave time, not by notice time!
 
         $qry .= 'ORDER BY original.id DESC ';
index 6a06583e0b51291e3b8934ac19b51d5be2b40cbd..e9288747467472491459be7b6c72868557040107 100644 (file)
@@ -91,7 +91,7 @@ class User_group extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamDirect($offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($offset, $limit, $since_id, $max_id)
     {
         $inbox = new Group_inbox();
 
@@ -108,10 +108,6 @@ class User_group extends Memcached_DataObject
             $inbox->whereAdd('notice_id <= ' . $max_id);
         }
 
-        if (!is_null($since)) {
-            $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
-        }
-
         $inbox->orderBy('notice_id DESC');
 
         if (!is_null($offset)) {
index b26ec8f01f92d03b43b1f953488541e48363287d..a804b8e82536ac0d3cbdddeb3d6bce0880919e73 100644 (file)
@@ -425,8 +425,6 @@ class Action extends HTMLOutputter // lawsuit
             $connect = 'imsettings';
         } else if (common_config('sms', 'enabled')) {
             $connect = 'smssettings';
-        } else if (common_config('twitter', 'enabled')) {
-            $connect = 'twittersettings';
         }
 
         $this->elementStart('dl', array('id' => 'site_nav_global_primary'));
index 9bfca3b66e49b643c403714c02744dd4ca0363fd..eef0ba637d20a52ef38c5bfcb2c3b2d520c05cbd 100644 (file)
@@ -63,7 +63,6 @@ class ApiAction extends Action
     var $count     = null;
     var $max_id    = null;
     var $since_id  = null;
-    var $since     = null;
 
     var $access    = self::READ_ONLY;  // read (default) or read-write
 
@@ -85,7 +84,10 @@ class ApiAction extends Action
         $this->count    = (int)$this->arg('count', 20);
         $this->max_id   = (int)$this->arg('max_id', 0);
         $this->since_id = (int)$this->arg('since_id', 0);
-        $this->since    = $this->arg('since');
+
+        if ($this->arg('since')) {
+            $this->clientError(_("since parameter is disabled for performance; use since_id"), 403);
+        }
 
         return true;
     }
@@ -1326,8 +1328,6 @@ class ApiAction extends Action
             case 'max_id':
                 $max_id = (int)$this->args['max_id'];
                 return ($max_id < 1) ? 0 : $max_id;
-            case 'since':
-                return strtotime($this->args['since']);
             default:
                 return parent::arg($key, $def);
             }
index b06b9615762072721ed31d8b30f08248a1b6d1af..7b97c7dda874d5470a7a35039d34bec70c036295 100644 (file)
@@ -177,8 +177,8 @@ $default =
         array('source' => 'StatusNet', # source attribute for Twitter
               'taguri' => null), # base for tag URIs
         'twitter' =>
-        array('enabled'       => true,
-              'consumer_key'    => null,
+        array('signin' => true,
+              'consumer_key' => null,
               'consumer_secret' => null),
         'cache' =>
         array('base' => null),
index eabe37f9fa4a944d76f99c91c9a57de69c6c0f23..a6a6de7505c946bbdd0a24b7760dea3ff8abb940 100644 (file)
@@ -390,7 +390,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore
         $sub->subscribed = $user->id;
 
         if (!$sub->find(true)) {
-            return 0;
+            return array();
         }
 
         /* Since we do not use OMB_Service_Provider’s action methods, there
index 17132a594f6ec20ea2e079270d9a791895297f2a..db60fa0ef2d0a122a65805cffe1154713df02658 100644 (file)
@@ -77,7 +77,7 @@ function omb_broadcast_notice($notice)
     /* Get remote users subscribed to this profile. */
     $rp = new Remote_profile();
 
-    $rp->query('SELECT postnoticeurl, token, secret ' .
+    $rp->query('SELECT remote_profile.*, secret, token ' .
                'FROM subscription JOIN remote_profile ' .
                'ON subscription.subscriber = remote_profile.id ' .
                'WHERE subscription.subscribed = ' . $notice->profile_id . ' ');
@@ -93,7 +93,8 @@ function omb_broadcast_notice($notice)
 
         /* Post notice. */
         $service = new StatusNet_OMB_Service_Consumer(
-                     array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl));
+                     array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl),
+                                                      $rp->uri);
         try {
             $service->setToken($rp->token, $rp->secret);
             $service->postNotice($omb_notice);
@@ -125,7 +126,7 @@ function omb_broadcast_profile($profile)
     /* Get remote users subscribed to this profile. */
     $rp = new Remote_profile();
 
-    $rp->query('SELECT updateprofileurl, token, secret ' .
+    $rp->query('SELECT remote_profile.*, secret, token ' .
                'FROM subscription JOIN remote_profile ' .
                'ON subscription.subscriber = remote_profile.id ' .
                'WHERE subscription.subscribed = ' . $profile->id . ' ');
@@ -141,7 +142,8 @@ function omb_broadcast_profile($profile)
 
         /* Update profile. */
         $service = new StatusNet_OMB_Service_Consumer(
-                     array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl));
+                     array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl),
+                                                      $rp->uri);
         try {
             $service->setToken($rp->token, $rp->secret);
             $service->updateProfile($omb_profile);
@@ -159,13 +161,14 @@ function omb_broadcast_profile($profile)
 }
 
 class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer {
-    public function __construct($urls)
+    public function __construct($urls, $listener_uri=null)
     {
         $this->services       = $urls;
         $this->datastore      = omb_oauth_datastore();
         $this->oauth_consumer = omb_oauth_consumer();
         $this->fetcher        = Auth_Yadis_Yadis::getHTTPFetcher();
         $this->fetcher->timeout = intval(common_config('omb', 'timeout'));
+        $this->listener_uri   = $listener_uri;
     }
 
 }
index 693cd64492b26fde8c0a6e0a639ea7db43137a50..4f1e84a6af11e0af9097eebad916bea83da7e30a 100644 (file)
@@ -273,13 +273,18 @@ class ProfileListItem extends Widget
                 $usf = new UnsubscribeForm($this->out, $this->profile);
                 $usf->show();
             } else {
-                // Is it a local user? can't remote sub from a list
-                // XXX: make that possible!
                 $other = User::staticGet('id', $this->profile->id);
                 if (!empty($other)) {
                     $sf = new SubscribeForm($this->out, $this->profile);
                     $sf->show();
                 }
+                else {
+                    $url = common_local_url('remotesubscribe',
+                                            array('nickname' => $this->profile->nickname));
+                    $this->out->element('a', array('href' => $url,
+                                              'class' => 'entity_remote_subscribe'),
+                                   _('Subscribe'));
+                }
             }
             $this->out->elementEnd('li');
         }
index c1fc64a5a7c6fbb02e46cac0c4509c4c1aec2861..add1b0ae67e4ec5512713244627e2d5a4deac2d2 100644 (file)
@@ -550,7 +550,7 @@ function common_find_mentions($text, $notice)
             } else if (!empty($originalMentions) &&
                        array_key_exists($nickname, $originalMentions)) {
 
-                $mention = $originalMentions[$nickname];
+                $mentioned = $originalMentions[$nickname];
             } else {
                 $mentioned = common_relative_profile($sender, $nickname);
             }
index 78c9054e14818c1444e4d39e0173b2424b03a5e3..443cb396fcc5107f8e2a10f52a559758c934f97b 100644 (file)
@@ -22,7 +22,7 @@
  * @category  Plugin
  * @package   StatusNet
  * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009-2010 StatusNet, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://status.net/
  */
@@ -32,12 +32,12 @@ if (!defined('STATUSNET')) {
 }
 
 define("FACEBOOK_CONNECT_SERVICE", 3);
-define('FACEBOOKPLUGIN_VERSION', '0.9');
 
 require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
 
 /**
- * Facebook plugin to add a StatusNet Facebook application
+ * Facebook plugin to add a StatusNet Facebook canvas application
+ * and allow registration and authentication via Facebook Connect
  *
  * @category Plugin
  * @package  StatusNet
@@ -49,6 +49,36 @@ require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
 class FacebookPlugin extends Plugin
 {
 
+    const VERSION = STATUSNET_VERSION;
+
+    /**
+     * Initializer for the plugin.
+     */
+
+    function initialize()
+    {
+        // Allow the key and secret to be passed in
+        // Control panel will override
+
+        if (isset($this->apikey)) {
+            $key = common_config('facebook', 'apikey');
+            if (empty($key)) {
+                Config::save('facebook', 'apikey', $this->apikey);
+            }
+        }
+
+        if (isset($this->secret)) {
+            $secret = common_config('facebook', 'secret');
+            if (empty($secret)) {
+                Config::save(
+                    'facebook',
+                    'secret',
+                    $this->secret
+                );
+            }
+        }
+    }
+
     /**
      * Add Facebook app actions to the router table
      *
@@ -70,6 +100,7 @@ class FacebookPlugin extends Plugin
                     array('action' => 'facebooksettings'));
         $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite'));
         $m->connect('facebook/app/remove', array('action' => 'facebookremove'));
+        $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
 
         // Facebook Connect stuff
 
@@ -98,6 +129,7 @@ class FacebookPlugin extends Plugin
         case 'FacebookinviteAction':
         case 'FacebookremoveAction':
         case 'FacebooksettingsAction':
+        case 'FacebookadminpanelAction':
             include_once INSTALLDIR . '/plugins/Facebook/' .
               strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
@@ -122,6 +154,32 @@ class FacebookPlugin extends Plugin
         }
     }
 
+    /**
+     * Add a Facebook tab to the admin panels
+     *
+     * @param Widget $nav Admin panel nav
+     *
+     * @return boolean hook value
+     */
+
+    function onEndAdminPanelNav($nav)
+    {
+        if (AdminPanelAction::canAdmin('facebook')) {
+
+            $action_name = $nav->action->trimmed('action');
+
+            $nav->out->menuItem(
+                common_local_url('facebookadminpanel'),
+                _m('Facebook'),
+                _m('Facebook integration configuration'),
+                $action_name == 'facebookadminpanel',
+                'nav_facebook_admin_panel'
+            );
+        }
+
+        return true;
+    }
+
     /**
      * Override normal HTML output to force the content type to
      * text/html and add in xmlns:fb
@@ -359,8 +417,6 @@ class FacebookPlugin extends Plugin
             $connect = 'imsettings';
         } else if (common_config('sms', 'enabled')) {
             $connect = 'smssettings';
-        } else if (common_config('twitter', 'enabled')) {
-            $connect = 'twittersettings';
         }
 
         if (!empty($user)) {
@@ -525,15 +581,18 @@ class FacebookPlugin extends Plugin
 
     function onPluginVersion(&$versions)
     {
-        $versions[] = array('name' => 'Facebook',
-                            'version' => FACEBOOKPLUGIN_VERSION,
-                            'author' => 'Zach Copley',
-                            'homepage' => 'http://status.net/wiki/Plugin:Facebook',
-                            'rawdescription' =>
-                            _m('The Facebook plugin allows you to integrate ' .
-                               'your StatusNet instance with ' .
-                               '<a href="http://facebook.com/">Facebook</a> ' .
-                               'and Facebook Connect.'));
+        $versions[] = array(
+            'name' => 'Facebook',
+            'version' => self::VERSION,
+            'author' => 'Zach Copley',
+            'homepage' => 'http://status.net/wiki/Plugin:Facebook',
+            'rawdescription' => _m(
+                'The Facebook plugin allows you to integrate ' .
+                'your StatusNet instance with ' .
+                '<a href="http://facebook.com/">Facebook</a> ' .
+                'and Facebook Connect.'
+            )
+        );
         return true;
     }
 
index bf2f4a1800925ab05555125f1b4044c7f8b9a063..14c1d324197b988577caa12cb5c4469f64185d30 100644 (file)
@@ -1,6 +1,9 @@
-This plugin allows you to use Facebook Connect with StatusNet, provides a
-Facebook application for your users, and allows them to update their
-Facebook statuses from StatusNet.
+Facebook Plugin
+===============
+
+This plugin allows you to use Facebook Connect with StatusNet, provides
+a Facebook canvas application for your users, and allows them to update
+their Facebook statuses from StatusNet.
 
 Facebook Connect
 ----------------
@@ -15,12 +18,12 @@ Facebook credentials. With Facebook Connect, your users can:
 Built-in Facebook Application
 -----------------------------
 
-The plugin also installs a StatusNet Facebook application that allows your
-users to automatically update their Facebook statuses with their latest
-notices, invite their friends to use the app (and thus your site), view
-their notice timelines, and post notices -- all from within Facebook. The
-application is built into the StatusNet Facebook plugin and runs on your
-host.
+The plugin also installs a StatusNet Facebook canvas application that
+allows your users to automatically update their Facebook status with
+their latest notices, invite their friends to use the app (and thus your
+site), view their notice timelines and post notices -- all from within
+Facebook.  The application is built into the StatusNet Facebook plugin
+and runs on your host.
 
 Quick setup instructions*
 -------------------------
@@ -29,13 +32,9 @@ Install the Facebook Developer application on Facebook:
 
     http://www.facebook.com/developers/
 
-Use it to create a new application and generate an API key and secret. Add a
-Facebook app section of your config.php and copy in the key and secret,
-e.g.:
-
-    // Config section for the built-in Facebook application
-    $config['facebook']['apikey'] = 'APIKEY';
-    $config['facebook']['secret'] = 'SECRET';
+Use it to create a new application and generate an API key and secret.
+You will need the key and secret so cut-n-paste them into your text
+editor or write them down.
 
 In Facebook's application editor, specify the following URLs for your app:
 
@@ -67,11 +66,36 @@ can be left with default values.
     http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site
     http://wiki.developers.facebook.com/index.php/Creating_your_first_application
 
-Finally you must activate the plugin by adding the following line to your
-config.php:
+Finally you must activate the plugin by adding it in your config.php
+(this is where you'll need the API key and secret generated earlier):
+
+    addPlugin(
+        'Facebook',
+        array(
+            'apikey' => 'YOUR_APIKEY',
+            'secret' => 'YOUR_SECRET'
+        )
+    );
+
+Administration Panel
+--------------------
+
+As of StatusNet 0.9.0 you can alternatively specify the key and secret
+via a Facebook administration panel from within StatusNet, in which case
+you can just add:
 
     addPlugin('Facebook');
 
+to activate the plugin.
+
+NOTE: To enable the administration panel you'll need to add it to the
+list of active administration panels, e.g.:
+
+    $config['admin']['panels'][] = 'facebook';
+
+and of course you'll need a user with the administrative role to access
+it and input the API key and secret (see: scripts/userrole.php).
+
 Testing It Out
 --------------
 
@@ -81,11 +105,11 @@ disconnect* to their Facebook accounts from it.
 
 To try out the plugin, fire up your browser and connect to:
 
-    http://SITE/PATH_TO_STATUSNET/main/facebooklogin
+    http://example.net/mublog/main/facebooklogin
 
 or, if you do not have fancy URLs turned on:
 
-    http://SITE/PATH_TO_STATUSNET/index.php/main/facebooklogin
+    http://example.net/mublog/index.php/main/facebooklogin
 
 You should see a page with a blue button that says: "Connect with Facebook"
 and you should be able to login or register.
@@ -101,7 +125,7 @@ the app, you are given the option to update their Facebook status via
 StatusNet.
 
 * Note: Before a user can disconnect from Facebook, she must set a normal
-  StatusNet password. Otherwise, she might not be able to login in to her
+  StatusNet password.  Otherwise, she might not be able to login in to her
   account in the future.  This is usually only required for users who have
   used Facebook Connect to register their StatusNet account, and therefore
   haven't already set a local password.
@@ -109,16 +133,20 @@ StatusNet.
 Offline Queue Handling
 ----------------------
 
-For larger sites needing better performance it's possible to enable queuing
-and have users' notices posted to Facebook via a separate "offline"
-FacebookQueueHandler (facebookqueuhandler.php in the Facebook plugin
-directory), which will be started by the plugin along with their other
-daemons when you run scripts/startdaemons.sh. See the StatusNet README for
-more about queuing and daemons.
+For larger sites needing better performance it's possible to enable
+queuing and have users' notices posted to Facebook via a separate
+"offline" process -- FacebookQueueHandler (facebookqueuhandler.php in
+the Facebook plugin directory).  It will run automatically if you have
+enabled StatusNet's offline queueing subsystem.  See the "Queues and
+daemons" section in the StatusNet README for more about queuing.
+
 
 TODO
 ----
 
+- Make Facebook Connect work for authentication for multi-site setups
+  (e.g.: *.status.net)
+- Posting to Facebook user streams using only Facebook Connect
 - Invite Facebook friends to use your StatusNet installation via Facebook
   Connect
 - Auto-subscribe Facebook friends already using StatusNet
@@ -126,4 +154,4 @@ TODO
 - Allow users to update their Facebook statuses once they have authenticated
   with Facebook Connect (no need for them to use the Facebook app if they
   don't want to).
-- Re-design the whole thing to support multiple instances of StatusNet
+- Import a user's Facebook updates into StatusNet
diff --git a/plugins/Facebook/facebookadminpanel.php b/plugins/Facebook/facebookadminpanel.php
new file mode 100644 (file)
index 0000000..ae1c730
--- /dev/null
@@ -0,0 +1,223 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Facebook integration administration panel
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  Settings
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Administer global Facebook integration settings
+ *
+ * @category Admin
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class FacebookadminpanelAction extends AdminPanelAction
+{
+    /**
+     * Returns the page title
+     *
+     * @return string page title
+     */
+
+    function title()
+    {
+        return _m('Facebook');
+    }
+
+    /**
+     * Instructions for using this form.
+     *
+     * @return string instructions
+     */
+
+    function getInstructions()
+    {
+        return _m('Facebook integration settings');
+    }
+
+    /**
+     * Show the Facebook admin panel form
+     *
+     * @return void
+     */
+
+    function showForm()
+    {
+        $form = new FacebookAdminPanelForm($this);
+        $form->show();
+        return;
+    }
+
+    /**
+     * Save settings from the form
+     *
+     * @return void
+     */
+
+    function saveSettings()
+    {
+        static $settings = array(
+            'facebook'     => array('apikey', 'secret'),
+        );
+
+        $values = array();
+
+        foreach ($settings as $section => $parts) {
+            foreach ($parts as $setting) {
+                $values[$section][$setting]
+                    = $this->trimmed($setting);
+            }
+        }
+
+        // This throws an exception on validation errors
+
+        $this->validate($values);
+
+        // assert(all values are valid);
+
+        $config = new Config();
+
+        $config->query('BEGIN');
+
+        foreach ($settings as $section => $parts) {
+            foreach ($parts as $setting) {
+                Config::save($section, $setting, $values[$section][$setting]);
+            }
+        }
+
+        $config->query('COMMIT');
+
+        return;
+    }
+
+    function validate(&$values)
+    {
+        // Validate consumer key and secret (can't be too long)
+
+        if (mb_strlen($values['facebook']['apikey']) > 255) {
+            $this->clientError(
+                _m("Invalid Facebook API key. Max length is 255 characters.")
+            );
+        }
+
+        if (mb_strlen($values['facebook']['secret']) > 255) {
+            $this->clientError(
+                _m("Invalid Facebook API secret. Max length is 255 characters.")
+            );
+        }
+    }
+}
+
+class FacebookAdminPanelForm extends AdminForm
+{
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        return 'facebookadminpanel';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('facebookadminpanel');
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->out->elementStart(
+            'fieldset',
+            array('id' => 'settings_facebook-application')
+        );
+        $this->out->element('legend', null, _m('Facebook application settings'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->input(
+            'apikey',
+            _m('API key'),
+            _m('API key provided by Facebook'),
+            'facebook'
+        );
+        $this->unli();
+
+        $this->li();
+        $this->input(
+            'secret',
+             _m('Secret'),
+            _m('API secret provided by Facebook'),
+            'facebook'
+        );
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Save'), 'submit', null, _('Save Facebook settings'));
+    }
+}
index cd2531fa727a8f3b7d4d2fd41ed645a8f3d4dc7d..f788639aed7383d7b7fe1ca2ec9042c2cc257f07 100644 (file)
@@ -312,8 +312,6 @@ class MobileProfilePlugin extends WAP20Plugin
             $connect = 'imsettings';
         } else if (common_config('sms', 'enabled')) {
             $connect = 'smssettings';
-        } else if (common_config('twitter', 'enabled')) {
-            $connect = 'twittersettings';
         }
 
         $action->elementStart('ul', array('id' => 'site_nav_global_primary'));
index 02282f2f67e195a40221e03eecfa78b6fba1d02e..b69430ea78a74d890c90bc19ebfa38ea3fd8fa6e 100644 (file)
@@ -222,31 +222,62 @@ class OStatusPlugin extends Plugin
     }
 
     /**
-     *
+     * Find any explicit remote mentions. Accepted forms:
+     *   Webfinger: @user@example.com
+     *   Profile link: @example.com/mublog/user
+     * @param Profile $sender (os user?)
+     * @param string $text input markup text
+     * @param array &$mention in/out param: set of found mentions
+     * @return boolean hook return value
      */
 
     function onEndFindMentions($sender, $text, &$mentions)
     {
-        preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/',
+        preg_match_all('!(?:^|\s+)
+                        @(                                # Webfinger:
+                          (?:\w+\.)*\w+                   #   user
+                          @                               #   @
+                          (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ #   domain
+                         |                                # Profile:
+                          (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ #   domain
+                          (?:/\w+)+                       #   /path1(/path2...)
+                         )!x',
                        $text,
                        $wmatches,
                        PREG_OFFSET_CAPTURE);
 
         foreach ($wmatches[1] as $wmatch) {
-
-            $webfinger = $wmatch[0];
-
-            $this->log(LOG_INFO, "Checking Webfinger for address '$webfinger'");
-
-            $oprofile = Ostatus_profile::ensureWebfinger($webfinger);
+            $target = $wmatch[0];
+            $oprofile = null;
+
+            if (strpos($target, '/') === false) {
+                $this->log(LOG_INFO, "Checking Webfinger for address '$target'");
+                try {
+                    $oprofile = Ostatus_profile::ensureWebfinger($target);
+                } catch (Exception $e) {
+                    $this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage());
+                }
+            } else {
+                $schemes = array('https', 'http');
+                foreach ($schemes as $scheme) {
+                    $url = "$scheme://$target";
+                    $this->log(LOG_INFO, "Checking profile address '$url'");
+                    try {
+                        $oprofile = Ostatus_profile::ensureProfile($url);
+                        if ($oprofile) {
+                            continue;
+                        }
+                    } catch (Exception $e) {
+                        $this->log(LOG_ERR, "Profile check failed: " . $e->getMessage());
+                    }
+                }
+            }
 
             if (empty($oprofile)) {
-
-                $this->log(LOG_INFO, "No Ostatus_profile found for address '$webfinger'");
-
+                $this->log(LOG_INFO, "No Ostatus_profile found for address '$target'");
             } else {
 
-                $this->log(LOG_INFO, "Ostatus_profile found for address '$webfinger'");
+                $this->log(LOG_INFO, "Ostatus_profile found for address '$target'");
 
                 if ($oprofile->isGroup()) {
                     continue;
@@ -261,7 +292,7 @@ class OStatusPlugin extends Plugin
                     }
                 }
                 $mentions[] = array('mentioned' => array($profile),
-                                    'text' => $wmatch[0],
+                                    'text' => $target,
                                     'position' => $pos,
                                     'url' => $profile->profileurl);
             }
@@ -674,6 +705,20 @@ class OStatusPlugin extends Plugin
     }
 
     function onStartShowSubscriptionsContent($action)
+    {
+        $this->showEntityRemoteSubscribe($action);
+
+        return true;
+    }
+
+    function onStartShowAllContent($action)
+    {
+        $this->showEntityRemoteSubscribe($action);
+
+        return true;
+    }
+
+    function showEntityRemoteSubscribe($action)
     {
         $user = common_current_user();
         if ($user && ($user->id == $action->profile->id)) {
@@ -686,8 +731,6 @@ class OStatusPlugin extends Plugin
             $action->elementEnd('p');
             $action->elementEnd('div');
         }
-
-        return true;
     }
 
     /**
index f45e6a8d1abae4bc3633a5e86fdb55ddb2d3429b..e318701a24f7e84b71cdde5bae2ce3b626a046f4 100644 (file)
@@ -112,7 +112,7 @@ class OStatusSubAction extends Action
             $this->submit('submit', _m('Join'), 'submit', null,
                          _m('Join this group'));
         } else {
-            $this->submit('submit', _m('Subscribe'), 'submit', null,
+            $this->submit('submit', _m('Confirm'), 'submit', null,
                          _m('Subscribe to this user'));
         }
         $this->elementEnd('fieldset');
index f33690bc4999acc89c1974913ea95a59d9d130ef..842d65e7d29d9eac29518249560aa98c3e37a7e7 100644 (file)
@@ -104,7 +104,7 @@ class PushHubAction extends Action
             throw new ClientException("Invalid hub.secret $secret; must be under 200 bytes.");
         }
 
-        $sub = HubSub::staticGet($sub->topic, $sub->callback);
+        $sub = HubSub::staticGet($topic, $callback);
         if (!$sub) {
             // Creating a new one!
             $sub = new HubSub();
index e599d83a967bf3a8e69551f37dad8fde5c0a455f..3120a70f9fb39dd978c14482c59b79ed90abbbc4 100644 (file)
@@ -260,9 +260,15 @@ class HubSub extends Memcached_DataObject
             $retries = intval(common_config('ostatus', 'hub_retries'));
         }
 
-        $data = array('sub' => clone($this),
+        // We dare not clone() as when the clone is discarded it'll
+        // destroy the result data for the parent query.
+        // @fixme use clone() again when it's safe to copy an
+        // individual item from a multi-item query again.
+        $sub = HubSub::staticGet($this->topic, $this->callback);
+        $data = array('sub' => $sub,
                       'atom' => $atom,
                       'retries' => $retries);
+        common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback");
         $qm = QueueManager::get();
         $qm->enqueue($data, 'hubout');
     }
index 96900d876144c80c20e37100bf8f1498390ee876..5a46aeeb6e5f79b8b6ce59fab283685b51521d42 100644 (file)
@@ -146,8 +146,10 @@ class Magicsig extends Memcached_DataObject
         
         $mod = base64_url_decode($matches[1]);
         $exp = base64_url_decode($matches[2]);
-        if ($matches[4]) {
+        if (!empty($matches[4])) {
             $private_exp = base64_url_decode($matches[4]);
+        } else {
+            $private_exp = false;
         }
 
         $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');
index 7b1aec76baa7eac9b73879b108b7e10a8eee85ff..a33e95d932c02f7faea0c4f7ee3f56eb9ac788d6 100644 (file)
@@ -698,7 +698,7 @@ class Ostatus_profile extends Memcached_DataObject
     {
         // Get the canonical feed URI and check it
         $discover = new FeedDiscovery();
-        if ($hints['feedurl']) {
+        if (isset($hints['feedurl'])) {
             $feeduri = $hints['feedurl'];
             $feeduri = $discover->discoverFromFeedURL($feeduri);
         } else {
@@ -1145,7 +1145,7 @@ class Ostatus_profile extends Memcached_DataObject
 
         if (!empty($poco)) {
             $url = $poco->getPrimaryURL();
-            if ($url->type == 'homepage') {
+            if ($url && $url->type == 'homepage') {
                 $homepage = $url->value;
             }
         }
index 388df0a28feed184d48df8a1887fb7bc0c5a3044..f8449b309ec11bda481710af91b803118921f89c 100644 (file)
@@ -94,7 +94,7 @@ class Discovery
             $links = call_user_func(array($class, 'discover'), $uri);
             if ($link = Discovery::getService($links, Discovery::LRDD_REL)) {
                 // Load the LRDD XRD
-                if ($link['template']) {
+                if (!empty($link['template'])) {
                     $xrd_uri = Discovery::applyTemplate($link['template'], $uri);
                 } else {
                     $xrd_uri = $link['href'];
index 16d27f8eb7f66072d24f1f5b98ba8606d85c9563..85df26c54cb752605c9babb4bccd4ef65f690085 100644 (file)
@@ -53,17 +53,22 @@ class XRD
         $xrd = new XRD();
 
         $dom = new DOMDocument();
-        $dom->loadXML($xml);
+        if (!$dom->loadXML($xml)) {
+            throw new Exception("Invalid XML");
+        }
         $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
 
         // Check for host-meta host
-        $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue;
+        $host = $xrd_element->getElementsByTagName('Host')->item(0);
         if ($host) {
-            $xrd->host = $host;
+            $xrd->host = $host->nodeValue;
         }
 
         // Loop through other elements
         foreach ($xrd_element->childNodes as $node) {
+            if (!($node instanceof DOMElement)) {
+                continue;
+            }
             switch ($node->tagName) {
             case 'Expires':
                 $xrd->expires = $node->nodeValue;
@@ -156,20 +161,20 @@ class XRD
     function saveLink($doc, $link)
     {
         $link_element = $doc->createElement('Link');
-        if ($link['rel']) {
+        if (!empty($link['rel'])) {
             $link_element->setAttribute('rel', $link['rel']);
         }
-        if ($link['type']) {
+        if (!empty($link['type'])) {
             $link_element->setAttribute('type', $link['type']);
         }
-        if ($link['href']) {
+        if (!empty($link['href'])) {
             $link_element->setAttribute('href', $link['href']);
         }
-        if ($link['template']) {
+        if (!empty($link['template'])) {
             $link_element->setAttribute('template', $link['template']);
         }
 
-        if (is_array($link['title'])) {
+        if (!empty($link['title']) && is_array($link['title'])) {
             foreach($link['title'] as $title) {
                 $title = $doc->createElement('Title', $title);
                 $link_element->appendChild($title);
diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php
new file mode 100644 (file)
index 0000000..d553a7d
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, 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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'i:n:a';
+$longoptions = array('id=', 'nickname=', 'all');
+
+$helptext = <<<END_OF_UPDATEOSTATUS_HELP
+updateostatus.php [options]
+update the OMB subscriptions of a user to use OStatus if possible
+
+  -i --id       ID of user to update
+  -n --nickname nickname of the user to update
+  -a --all      update all
+
+END_OF_UPDATEOSTATUS_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+try {
+    $user = null;
+
+    if (have_option('i', 'id')) {
+        $id = get_option_value('i', 'id');
+        $user = User::staticGet('id', $id);
+        if (empty($user)) {
+            throw new Exception("Can't find user with id '$id'.");
+        }
+        updateProfileURL($user);
+    } else if (have_option('n', 'nickname')) {
+        $nickname = get_option_value('n', 'nickname');
+        $user = User::staticGet('nickname', $nickname);
+        if (empty($user)) {
+            throw new Exception("Can't find user with nickname '$nickname'");
+        }
+        updateProfileURL($user);
+    } else if (have_option('a', 'all')) {
+        $user = new User();
+        if ($user->find()) {
+            while ($user->fetch()) {
+                updateOStatus($user);
+            }
+        }
+    } else {
+        show_help();
+        exit(1);
+    }
+} catch (Exception $e) {
+    print $e->getMessage()."\n";
+    exit(1);
+}
+
+function updateOStatus($user)
+{
+    if (!have_option('q', 'quiet')) {
+        echo "{$user->nickname}...";
+    }
+
+    $up = $user->getProfile();
+
+    $sp = $user->getSubscriptions();
+
+    $rps = array();
+
+    while ($sp->fetch()) {
+        $remote = Remote_profile::staticGet('id', $sp->id);
+
+        if (!empty($remote)) {
+            $rps[] = clone($sp);
+        }
+    }
+
+    if (!have_option('q', 'quiet')) {
+        echo count($rps) . "\n";
+    }
+
+    foreach ($rps as $rp) {
+        try {
+            if (!have_option('q', 'quiet')) {
+                echo "Checking {$rp->nickname}...";
+            }
+
+            $op = Ostatus_profile::ensureProfile($rp->profileurl);
+
+            if (empty($op)) {
+                echo "can't convert.\n";
+                continue;
+            } else {
+                if (!have_option('q', 'quiet')) {
+                    echo "Converting...";
+                }
+                Subscription::cancel($up, $rp);
+                Subscription::start($up, $op->localProfile());
+                if (!have_option('q', 'quiet')) {
+                    echo "done.\n";
+                }
+            }
+
+        } catch (Exception $e) {
+            if (!have_option('q', 'quiet')) {
+                echo "fail.\n";
+            }
+            continue;
+            common_log(LOG_WARNING, "Couldn't convert OMB subscription (" . $up->nickname . ", " . $rp->nickname .
+                       ") to OStatus: " . $e->getMessage());
+            continue;
+        }
+    }
+}
index feeeb47d38209c7fa76acbf6ec25c1ec97ae1433..13e30ef5df01df55a4d96c44a10ab1069ca9da96 100644 (file)
@@ -38,11 +38,11 @@ display:none;
 min-width:96px;
 }
 
-#subscriptions #entity_remote_subscribe {
+#entity_remote_subscribe {
 padding:0;
 float:right;
 }
 
-#subscriptions .entity_remote_subscribe {
-float:right;
+#all #entity_remote_subscribe {
+margin-top:-52px;
 }
index 71f3308281163b9338efc660c3f156e986e61bd1..654b9c9d5beada8e83664c7a3a3138310e50466f 100644 (file)
@@ -75,7 +75,7 @@ class SphinxSearch extends SearchEngine
     {
         if ('chron' === $mode) {
             $this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts');
-            return $this->target->orderBy('created desc');
+            return $this->target->orderBy('id desc');
         }
     }
 
index d3bcda5984d7c432c93dbb80ca2d9ee4eb49ed5a..72278b32e6eac525ec38fd19422a66a0872c4975 100644 (file)
@@ -1,47 +1,89 @@
+Twitter Bridge Plugin
+=====================
+
 This Twitter "bridge" plugin allows you to integrate your StatusNet
 instance with Twitter.  Installing it will allow your users to:
 
-    - automatically post notices to thier Twitter accounts
+    - automatically post notices to their Twitter accounts
     - automatically subscribe to other Twitter users who are also using
       your StatusNet install, if possible (requires running a daemon)
     - import their Twitter friends' tweets (requires running a daemon)
+    - allow users to authenticate using Twitter ('Sign in with Twitter')
 
 Installation
 ------------
 
-To enable the plugin, add the following to your config.php:
-
-    addPlugin("TwitterBridge");
-
-OAuth is used to to access protected resources on Twitter (as opposed to
-HTTP Basic Auth)*.  To use Twitter bridging you will need to register
-your instance of StatusNet as an application on Twitter
-(http://twitter.com/apps), and update the following variables in your
-config.php with the consumer key and secret Twitter generates for you:
-
-    $config['twitter']['consumer_key']    = 'YOURKEY';
-    $config['twitter']['consumer_secret'] = 'YOURSECRET';
+OAuth 1.0a (http://oauth.net) is used to to access protected resources
+on Twitter (as opposed to HTTP Basic Auth)*.  To use Twitter bridging
+you will need to register your instance of StatusNet as an application
+on Twitter (http://twitter.com/apps).  During the application
+registration process your application will be assigned a "consumer" key
+and secret, which the plugin will use to make OAuth requests to Twitter.
+You can either pass the consumer key and secret in when you enable the
+plugin, or set it using the Twitter administration panel.
 
 When registering your application with Twitter set the type to "Browser"
 and your Callback URL to:
 
     http://example.org/mublog/twitter/authorization
 
-The default access type should be, "Read & Write".
+(Change "example.org" to your site domain and "mublog" to your site
+path.)
+
+The default access type should be "Read & Write".
+
+To enable the plugin, add the following to your config.php:
+
+    addPlugin(
+        'TwitterBridge',
+        array(
+            'consumer_key'    => 'YOUR_CONSUMER_KEY',
+            'consumer_secret' => 'YOUR_CONSUMER_SECRET'
+        )
+    );
 
 * Note: The plugin will still push notices to Twitter for users who
-  have previously setup the Twitter bridge using their Twitter name and
-  password under an older versions of StatusNet, but all new Twitter
+  have previously set up the Twitter bridge using their Twitter name and
+  password under an older version of StatusNet, but all new Twitter
   bridge connections will use OAuth.
 
-Deamons
+Administration panel
+--------------------
+
+As of StatusNet 0.9.0 there is a new administration panel that allows
+you to configure Twitter bridge settings within StatusNet itself,
+instead of having to specify them manually in your config.php.  To enable
+the administration panel, you will need to add it to the list of active
+administration panels.  You can do this via your config.php. E.g.:
+
+    $config['admin']['panels'][] = 'twitter';
+
+And to access it, you'll need to use a user with the "administrator"
+role (see: scripts/userrole.php).
+
+Sign in with Twitter
+--------------------
+
+With 0.9.0, StatusNet optionally allows users to register and
+authenticate using their Twitter credentials via the "Sign in with
+Twitter" pattern described here:
+
+    http://apiwiki.twitter.com/Sign-in-with-Twitter
+
+The option is _on_ by default when you install the plugin, but it can
+disabled via the Twitter bridge administration panel, or by adding the
+following line to your config.php:
+
+    $config['twitter']['signin'] = false;
+
+Daemons
 -------
 
-For friend syncing and importing notices running two additional daemon
-scripts is necessary (synctwitterfriends.php and
-twitterstatusfetcher.php).
+For friend syncing and importing Twitter tweets, running two
+additional daemon scripts is necessary: synctwitterfriends.php and
+twitterstatusfetcher.php.
 
-In the daemons subidrectory of the plugin are three scripts:
+In the daemons subdirectory of the plugin are three scripts:
 
 * Twitter Friends Syncing (daemons/synctwitterfriends.php)
 
@@ -51,13 +93,13 @@ subscribe to "friends" (people they "follow") on Twitter who also have
 accounts on your StatusNet system, and who have previously set up a link
 for automatically posting notices to Twitter.
 
-The plugin will try to start this daemon when you run
-scripts/startdaemons.sh.
+The plugin will start this daemon when you run scripts/startdaemons.sh.
 
 * Importing statuses from Twitter (daemons/twitterstatusfetcher.php)
 
-To allow your users to import their friends' Twitter statuses, you will
-need to enable the bidirectional Twitter bridge in your config.php:
+You can allow uses to enable importing of your friends' Twitter
+timelines either in the Twitter bridge administration panel or in your
+config.php using the following configuration line:
 
     $config['twitterimport']['enabled'] = true;
 
@@ -66,8 +108,9 @@ other daemons when you run scripts/startdaemons.sh.
 
 Additionally, you will want to set the integration source variable,
 which will keep notices posted to Twitter via StatusNet from looping
-back.  The integration source should be set to the name of your
-application, exactly as you specified it on the settings page for your
+back.  You can do this in the Twitter bridge administration panel, or
+via config.php. The integration source should be set to the name of your
+application _exactly_ as you specified it on the settings page for your
 StatusNet application on Twitter, e.g.:
 
     $config['integration']['source'] = 'YourApp';
@@ -79,7 +122,9 @@ set up Twitter bridging.
 
 It's not strictly necessary to run this queue handler, and sites that
 haven't enabled queuing are still able to push notices to Twitter, but
-for larger sites and sites that wish to improve performance, this
-script allows notices to be sent "offline" via a separate process.
+for larger sites and sites that wish to improve performance the script
+allows notices to be sent "offline" via a separate process.
 
-The plugin will start this script when you run scripts/startdaemons.sh.
+StatusNet will automatically use the TwitterQueueHandler if you have
+enabled the queuing subsystem.  See the "Queues and daemons" section of
+the main README file for more information about how to do that.
index c7f57ffc7753066f29b86d4020b072fe41ddd928..6ce69d5e2b3f5b2434b465b87afb9852372abb32 100644 (file)
@@ -23,7 +23,7 @@
  * @author    Julien C <chaumond@gmail.com>
  * @copyright 2009-2010 Control Yourself, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://laconi.ca/
+ * @link      http://status.net/
  */
 
 if (!defined('STATUSNET')) {
@@ -32,8 +32,6 @@ if (!defined('STATUSNET')) {
 
 require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
 
-define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
-
 /**
  * Plugin for sending and importing Twitter statuses
  *
@@ -44,19 +42,41 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
  * @author   Zach Copley <zach@status.net>
  * @author   Julien C <chaumond@gmail.com>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
+ * @link     http://status.net/
  * @link     http://twitter.com/
  */
 
 class TwitterBridgePlugin extends Plugin
 {
+
+    const VERSION = STATUSNET_VERSION;
+
     /**
      * Initializer for the plugin.
      */
 
-    function __construct()
+    function initialize()
     {
-        parent::__construct();
+        // Allow the key and secret to be passed in
+        // Control panel will override
+
+        if (isset($this->consumer_key)) {
+            $key = common_config('twitter', 'consumer_key');
+            if (empty($key)) {
+                Config::save('twitter', 'consumer_key', $this->consumer_key);
+            }
+        }
+
+        if (isset($this->consumer_secret)) {
+            $secret = common_config('twitter', 'consumer_secret');
+            if (empty($secret)) {
+                Config::save(
+                    'twitter',
+                    'consumer_secret',
+                    $this->consumer_secret
+                );
+            }
+        }
     }
 
     /**
@@ -71,10 +91,17 @@ class TwitterBridgePlugin extends Plugin
 
     function onRouterInitialized($m)
     {
-        $m->connect('twitter/authorization',
-                    array('action' => 'twitterauthorization'));
+        $m->connect(
+            'twitter/authorization',
+            array('action' => 'twitterauthorization')
+        );
         $m->connect('settings/twitter', array('action' => 'twittersettings'));
-        $m->connect('main/twitterlogin', array('action' => 'twitterlogin'));
+
+        if (common_config('twitter', 'signin')) {
+            $m->connect('main/twitterlogin', array('action' => 'twitterlogin'));
+        }
+
+        $m->connect('admin/twitter', array('action' => 'twitteradminpanel'));
 
         return true;
     }
@@ -88,13 +115,16 @@ class TwitterBridgePlugin extends Plugin
      */
     function onEndLoginGroupNav(&$action)
     {
-
         $action_name = $action->trimmed('action');
 
-        $action->menuItem(common_local_url('twitterlogin'),
-                                           _('Twitter'),
-                                           _('Login or register using Twitter'),
-                                             'twitterlogin' === $action_name);
+        if (common_config('twitter', 'signin')) {
+            $action->menuItem(
+                common_local_url('twitterlogin'),
+                _m('Twitter'),
+                _m('Login or register using Twitter'),
+                'twitterlogin' === $action_name
+            );
+        }
 
         return true;
     }
@@ -110,10 +140,12 @@ class TwitterBridgePlugin extends Plugin
     {
         $action_name = $action->trimmed('action');
 
-        $action->menuItem(common_local_url('twittersettings'),
-                          _m('Twitter'),
-                          _m('Twitter integration options'),
-                          $action_name === 'twittersettings');
+        $action->menuItem(
+            common_local_url('twittersettings'),
+            _m('Twitter'),
+            _m('Twitter integration options'),
+            $action_name === 'twittersettings'
+        );
 
         return true;
     }
@@ -132,6 +164,7 @@ class TwitterBridgePlugin extends Plugin
         case 'TwittersettingsAction':
         case 'TwitterauthorizationAction':
         case 'TwitterloginAction':
+        case 'TwitteradminpanelAction':
             include_once INSTALLDIR . '/plugins/TwitterBridge/' .
               strtolower(mb_substr($cls, 0, -6)) . '.php';
             return false;
@@ -173,12 +206,18 @@ class TwitterBridgePlugin extends Plugin
      */
     function onGetValidDaemons($daemons)
     {
-        array_push($daemons, INSTALLDIR .
-                   '/plugins/TwitterBridge/daemons/synctwitterfriends.php');
+        array_push(
+            $daemons,
+            INSTALLDIR
+            . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'
+        );
 
         if (common_config('twitterimport', 'enabled')) {
-            array_push($daemons, INSTALLDIR
-                . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php');
+            array_push(
+                $daemons,
+                INSTALLDIR
+                . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'
+            );
         }
 
         return true;
@@ -197,17 +236,55 @@ class TwitterBridgePlugin extends Plugin
         return true;
     }
 
+    /**
+     * Add a Twitter tab to the admin panel
+     *
+     * @param Widget $nav Admin panel nav
+     *
+     * @return boolean hook value
+     */
+
+    function onEndAdminPanelNav($nav)
+    {
+        if (AdminPanelAction::canAdmin('twitter')) {
+
+            $action_name = $nav->action->trimmed('action');
+
+            $nav->out->menuItem(
+                common_local_url('twitteradminpanel'),
+                _m('Twitter'),
+                _m('Twitter bridge configuration'),
+                $action_name == 'twitteradminpanel',
+                'nav_twitter_admin_panel'
+            );
+        }
+
+        return true;
+    }
+
+    /**
+     * Plugin version data
+     *
+     * @param array &$versions array of version blocks
+     *
+     * @return boolean hook value
+     */
+
     function onPluginVersion(&$versions)
     {
-        $versions[] = array('name' => 'TwitterBridge',
-                            'version' => TWITTERBRIDGEPLUGIN_VERSION,
-                            'author' => 'Zach Copley',
-                            'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
-                            'rawdescription' =>
-                            _m('The Twitter "bridge" plugin allows you to integrate ' .
-                               'your StatusNet instance with ' .
-                               '<a href="http://twitter.com/">Twitter</a>.'));
+        $versions[] = array(
+            'name' => 'TwitterBridge',
+            'version' => self::VERSION,
+            'author' => 'Zach Copley, Julien C',
+            'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
+            'rawdescription' => _m(
+                'The Twitter "bridge" plugin allows you to integrate ' .
+                'your StatusNet instance with ' .
+                '<a href="http://twitter.com/">Twitter</a>.'
+            )
+        );
         return true;
     }
 
 }
+
diff --git a/plugins/TwitterBridge/twitteradminpanel.php b/plugins/TwitterBridge/twitteradminpanel.php
new file mode 100644 (file)
index 0000000..b22e6d9
--- /dev/null
@@ -0,0 +1,280 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Twitter bridge administration panel
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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/>.
+ *
+ * @category  Settings
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Administer global Twitter bridge settings
+ *
+ * @category Admin
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class TwitteradminpanelAction extends AdminPanelAction
+{
+    /**
+     * Returns the page title
+     *
+     * @return string page title
+     */
+
+    function title()
+    {
+        return _m('Twitter');
+    }
+
+    /**
+     * Instructions for using this form.
+     *
+     * @return string instructions
+     */
+
+    function getInstructions()
+    {
+        return _m('Twitter bridge settings');
+    }
+
+    /**
+     * Show the Twitter admin panel form
+     *
+     * @return void
+     */
+
+    function showForm()
+    {
+        $form = new TwitterAdminPanelForm($this);
+        $form->show();
+        return;
+    }
+
+    /**
+     * Save settings from the form
+     *
+     * @return void
+     */
+
+    function saveSettings()
+    {
+        static $settings = array(
+            'twitter'     => array('consumer_key', 'consumer_secret'),
+            'integration' => array('source')
+        );
+
+        static $booleans = array(
+            'twitter'       => array('signin'),
+            'twitterimport' => array('enabled')
+        );
+
+        $values = array();
+
+        foreach ($settings as $section => $parts) {
+            foreach ($parts as $setting) {
+                $values[$section][$setting]
+                    = $this->trimmed($setting);
+            }
+        }
+
+        foreach ($booleans as $section => $parts) {
+            foreach ($parts as $setting) {
+                $values[$section][$setting]
+                    = ($this->boolean($setting)) ? 1 : 0;
+            }
+        }
+
+        // This throws an exception on validation errors
+
+        $this->validate($values);
+
+        // assert(all values are valid);
+
+        $config = new Config();
+
+        $config->query('BEGIN');
+
+        foreach ($settings as $section => $parts) {
+            foreach ($parts as $setting) {
+                Config::save($section, $setting, $values[$section][$setting]);
+            }
+        }
+
+        foreach ($booleans as $section => $parts) {
+            foreach ($parts as $setting) {
+                Config::save($section, $setting, $values[$section][$setting]);
+            }
+        }
+
+        $config->query('COMMIT');
+
+        return;
+    }
+
+    function validate(&$values)
+    {
+        // Validate consumer key and secret (can't be too long)
+
+        if (mb_strlen($values['twitter']['consumer_key']) > 255) {
+            $this->clientError(
+                _m("Invalid consumer key. Max length is 255 characters.")
+            );
+        }
+
+        if (mb_strlen($values['twitter']['consumer_secret']) > 255) {
+            $this->clientError(
+                _m("Invalid consumer secret. Max length is 255 characters.")
+            );
+        }
+    }
+}
+
+class TwitterAdminPanelForm extends AdminForm
+{
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        return 'twitteradminpanel';
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_settings';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('twitteradminpanel');
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->out->elementStart(
+            'fieldset',
+            array('id' => 'settings_twitter-application')
+        );
+        $this->out->element('legend', null, _m('Twitter application settings'));
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+        $this->input(
+            'consumer_key',
+            _m('Consumer key'),
+            _m('Consumer key assigned by Twitter'),
+            'twitter'
+        );
+        $this->unli();
+
+        $this->li();
+        $this->input(
+            'consumer_secret',
+             _m('Consumer secret'),
+            _m('Consumer secret assigned by Twitter'),
+            'twitter'
+        );
+        $this->unli();
+
+        $this->li();
+        $this->input(
+            'source',
+             _m('Integration source'),
+            _m('Name of your Twitter application'),
+            'integration'
+        );
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+        $this->out->elementEnd('fieldset');
+
+        $this->out->elementStart(
+            'fieldset',
+            array('id' => 'settings_twitter-options')
+        );
+        $this->out->element('legend', null, _m('Options'));
+
+        $this->out->elementStart('ul', 'form_data');
+
+        $this->li();
+
+        $this->out->checkbox(
+            'signin', _m('Enable "Sign-in with Twitter"'),
+            (bool) $this->value('signin', 'twitter'),
+            _m('Allow users to login with their Twitter credentials')
+        );
+        $this->unli();
+
+        $this->li();
+        $this->out->checkbox(
+            'enabled', _m('Enable Twitter import'),
+            (bool) $this->value('enabled', 'twitterimport'),
+            _m('Allow users to import their Twitter friends\' timelines')
+        );
+        $this->unli();
+
+        $this->out->elementEnd('ul');
+
+        $this->out->elementEnd('fieldset');
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Save'), 'submit', null, _('Save Twitter settings'));
+    }
+}
index cabf69d7a8b47a0e03dc0fad1cf2eb3c877a5ca9..c93f6666bcc3f718ae42e3ede404abd849005783 100644 (file)
@@ -47,7 +47,7 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
  * @author   Zach Copley <zach@status.net>
  * @author   Julien C <chaumond@gmail.com>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
+ * @link     http://status.net/
  *
  */
 class TwitterauthorizationAction extends Action