Merge branch 'testing' of git@gitorious.org:statusnet/mainline into 0.9.x

This commit is contained in:
Brion Vibber 2010-03-02 13:38:10 -08:00
commit ddf3614c84
50 changed files with 1172 additions and 270 deletions

View File

@ -770,6 +770,12 @@ StartShowSubscriptionsContent: before showing the subscriptions content
EndShowSubscriptionsContent: after showing the subscriptions content EndShowSubscriptionsContent: after showing the subscriptions content
- $action: the current action - $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 StartDeleteUserForm: starting the data in the form for deleting a user
- $action: action being shown - $action: action being shown
- $user: user being deleted - $user: user being deleted

View File

@ -152,18 +152,22 @@ class AllAction extends ProfileAction
function showContent() 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) { if (0 == $cnt) {
$this->showEmptyListMessage(); $this->showEmptyListMessage();
}
$this->pagination(
$this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'all', array('nickname' => $this->user->nickname)
);
Event::handle('EndShowAllContent', array($this));
} }
$this->pagination(
$this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'all', array('nickname' => $this->user->nickname)
);
} }
function showPageTitle() function showPageTitle()

View File

@ -182,11 +182,6 @@ class ApiDirectMessageAction extends ApiAuthAction
$message->whereAdd('id > ' . $this->since_id); $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->orderBy('created DESC, id DESC');
$message->limit((($this->page - 1) * $this->count), $this->count); $message->limit((($this->page - 1) * $this->count), $this->count);
$message->find(); $message->find();

View File

@ -152,8 +152,7 @@ class ApiGroupListAction extends ApiBareAuthAction
($this->page - 1) * $this->count, ($this->page - 1) * $this->count,
$this->count, $this->count,
$this->since_id, $this->since_id,
$this->max_id, $this->max_id
$this->since
); );
while ($group->fetch()) { while ($group->fetch()) {

View File

@ -125,8 +125,7 @@ class ApiGroupMembershipAction extends ApiPrivateAuthAction
($this->page - 1) * $this->count, ($this->page - 1) * $this->count,
$this->count, $this->count,
$this->since_id, $this->since_id,
$this->max_id, $this->max_id
$this->since
); );
while ($profile->fetch()) { while ($profile->fetch()) {

View File

@ -202,11 +202,11 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) {
$notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count, $notice = $this->user->ownFriendsTimeline(($this->page-1) * $this->count,
$this->count, $this->since_id, $this->count, $this->since_id,
$this->max_id, $this->since); $this->max_id);
} else { } else {
$notice = $this->user->friendsTimeline(($this->page-1) * $this->count, $notice = $this->user->friendsTimeline(($this->page-1) * $this->count,
$this->count, $this->since_id, $this->count, $this->since_id,
$this->max_id, $this->since); $this->max_id);
} }
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -204,8 +204,7 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction
($this->page-1) * $this->count, ($this->page-1) * $this->count,
$this->count, $this->count,
$this->since_id, $this->since_id,
$this->max_id, $this->max_id
$this->since
); );
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -200,13 +200,13 @@ class ApiTimelineHomeAction extends ApiBareAuthAction
$notice = $this->user->noticeInbox( $notice = $this->user->noticeInbox(
($this->page-1) * $this->count, ($this->page-1) * $this->count,
$this->count, $this->since_id, $this->count, $this->since_id,
$this->max_id, $this->since $this->max_id
); );
} else { } else {
$notice = $this->user->noticesWithFriends( $notice = $this->user->noticesWithFriends(
($this->page-1) * $this->count, ($this->page-1) * $this->count,
$this->count, $this->since_id, $this->count, $this->since_id,
$this->max_id, $this->since $this->max_id
); );
} }

View File

@ -189,7 +189,7 @@ class ApiTimelineMentionsAction extends ApiBareAuthAction
$notice = $this->user->getReplies( $notice = $this->user->getReplies(
($this->page - 1) * $this->count, $this->count, ($this->page - 1) * $this->count, $this->count,
$this->since_id, $this->max_id, $this->since $this->since_id, $this->max_id
); );
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -75,10 +75,6 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction
$this->notices = $this->getNotices(); $this->notices = $this->getNotices();
if ($this->since) {
throw new ServerException("since parameter is disabled for performance; use since_id", 403);
}
return true; return true;
} }

View File

@ -211,7 +211,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$notice = $this->user->getNotices( $notice = $this->user->getNotices(
($this->page-1) * $this->count, $this->count, ($this->page-1) * $this->count, $this->count,
$this->since_id, $this->max_id, $this->since $this->since_id, $this->max_id
); );
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -294,6 +294,9 @@ class NewnoticeAction extends Action
if ($profile) { if ($profile) {
$content = '@' . $profile->nickname . ' '; $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); $notice_form = new NoticeForm($this, '', $content, null, $inreplyto);

View File

@ -54,7 +54,10 @@ class PostnoticeAction extends Action
*/ */
function prepare($argarray) function prepare($argarray)
{ {
StatusNet::setApi(true); // Send smaller error pages
parent::prepare($argarray); parent::prepare($argarray);
try { try {
$this->checkNotice(); $this->checkNotice();
} catch (Exception $e) { } catch (Exception $e) {
@ -71,6 +74,14 @@ class PostnoticeAction extends Action
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(), $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server()); omb_oauth_server());
$srv->handlePostNotice(); $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) { } catch (Exception $e) {
$this->serverError($e->getMessage()); $this->serverError($e->getMessage());
return; return;

View File

@ -55,6 +55,8 @@ class UpdateprofileAction extends Action
*/ */
function prepare($argarray) function prepare($argarray)
{ {
StatusNet::setApi(true); // Send smaller error pages
parent::prepare($argarray); parent::prepare($argarray);
$license = $_POST['omb_listenee_license']; $license = $_POST['omb_listenee_license'];
$site_license = common_config('license', 'url'); $site_license = common_config('license', 'url');
@ -75,6 +77,14 @@ class UpdateprofileAction extends Action
$srv = new OMB_Service_Provider(null, omb_oauth_datastore(), $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
omb_oauth_server()); omb_oauth_server());
$srv->handleUpdateProfile(); $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) { } catch (Exception $e) {
$this->serverError($e->getMessage()); $this->serverError($e->getMessage());
return; return;

View File

@ -77,7 +77,7 @@ class Fave extends Memcached_DataObject
return $ids; 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(); $fav = new Fave();
$qry = null; $qry = null;
@ -100,10 +100,6 @@ class Fave extends Memcached_DataObject
$qry .= 'AND notice_id <= ' . $max_id . ' '; $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! // NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY modified DESC '; $qry .= 'ORDER BY modified DESC ';

View 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); $inbox = Inbox::staticGet('user_id', $user_id);
@ -195,15 +195,15 @@ class Inbox extends Memcached_DataObject
* @param int $limit * @param int $limit
* @param mixed $since_id return only notices after but not including this id * @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 $max_id return only notices up to and including this id
* @param mixed $since obsolete/ignored
* @param mixed $own ignored? * @param mixed $own ignored?
* @return array of Notice objects * @return array of Notice objects
* *
* @todo consider repacking the inbox when this happens? * @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 // Do a bulk lookup for the first $limit items
// Fast path when nothing's deleted. // Fast path when nothing's deleted.

View 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'), $ids = Notice::stream(array('Notice', '_publicStreamDirect'),
array(), array(),
'public', 'public',
$offset, $limit, $since_id, $max_id, $since); $offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); 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(); $notice = new Notice();
@ -598,10 +598,6 @@ class Notice extends Memcached_DataObject
$notice->whereAdd('id <= ' . $max_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) {
$notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$ids = array(); $ids = array();
if ($notice->find()) { if ($notice->find()) {
@ -616,17 +612,17 @@ class Notice extends Memcached_DataObject
return $ids; 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'), $ids = Notice::stream(array('Notice', '_conversationStreamDirect'),
array($id), array($id),
'notice:conversation_ids:'.$id, 'notice:conversation_ids:'.$id,
$offset, $limit, $since_id, $max_id, $since); $offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); 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(); $notice = new Notice();
@ -649,10 +645,6 @@ class Notice extends Memcached_DataObject
$notice->whereAdd('id <= ' . $max_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) {
$notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$ids = array(); $ids = array();
if ($notice->find()) { if ($notice->find()) {
@ -1134,7 +1126,6 @@ class Notice extends Memcached_DataObject
} }
$xs->element('title', null, $this->content); $xs->element('title', null, $this->content);
$xs->element('summary', null, $this->content);
$xs->raw($profile->asAtomAuthor()); $xs->raw($profile->asAtomAuthor());
$xs->raw($profile->asActivityActor()); $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(); $cache = common_memcache();
if (empty($cache) || if (empty($cache) ||
$since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) || $since_id != 0 || $max_id != 0 ||
is_null($limit) || is_null($limit) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) { ($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id, return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
$max_id, $since))); $max_id)));
} }
$idkey = common_cache_key($cachekey); $idkey = common_cache_key($cachekey);

View File

@ -49,12 +49,12 @@ class Notice_inbox extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###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'); 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'); throw new Exception('Notice_inbox no longer used; use Inbox');
} }

View File

@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
return Notice::getStreamByIds($ids); 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(); $nt = new Notice_tag();
@ -63,10 +63,6 @@ class Notice_tag extends Memcached_DataObject
$nt->whereAdd('notice_id < ' . $max_id); $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'); $nt->orderBy('notice_id DESC');
if (!is_null($offset)) { if (!is_null($offset)) {

View File

@ -163,27 +163,27 @@ class Profile extends Memcached_DataObject
return null; 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'), $ids = Notice::stream(array($this, '_streamTaggedDirect'),
array($tag), array($tag),
'profile:notice_ids_tagged:' . $this->id . ':' . $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); 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. // XXX: I'm not sure this is going to be any faster. It probably isn't.
$ids = Notice::stream(array($this, '_streamDirect'), $ids = Notice::stream(array($this, '_streamDirect'),
array(), array(),
'profile:notice_ids:' . $this->id, 'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $max_id, $since); $offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); 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 // 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"; $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'; $query .= ' order by id DESC';
if (!is_null($offset)) { if (!is_null($offset)) {
@ -223,7 +219,7 @@ class Profile extends Memcached_DataObject
return $ids; return $ids;
} }
function _streamDirect($offset, $limit, $since_id, $max_id, $since = null) function _streamDirect($offset, $limit, $since_id, $max_id)
{ {
$notice = new Notice(); $notice = new Notice();
@ -240,10 +236,6 @@ class Profile extends Memcached_DataObject
$notice->whereAdd('id <= ' . $max_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) {
$notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$notice->orderBy('id DESC'); $notice->orderBy('id DESC');
if (!is_null($offset)) { if (!is_null($offset)) {

View File

@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###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'), $ids = Notice::stream(array('Reply', '_streamDirect'),
array($user_id), array($user_id),
'reply:stream:' . $user_id, 'reply:stream:' . $user_id,
$offset, $limit, $since_id, $max_id, $since); $offset, $limit, $since_id, $max_id);
return $ids; 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 = new Reply();
$reply->profile_id = $user_id; $reply->profile_id = $user_id;
@ -44,10 +44,6 @@ class Reply extends Memcached_DataObject
$reply->whereAdd('notice_id < ' . $max_id); $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'); $reply->orderBy('notice_id DESC');
if (!is_null($offset)) { if (!is_null($offset)) {

View File

@ -172,6 +172,28 @@ class Subscription extends Memcached_DataObject
assert(!empty($sub)); 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(); $result = $sub->delete();
if (!$result) { if (!$result) {

View File

@ -457,28 +457,28 @@ class User extends Memcached_DataObject
return $user; 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); 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(); $profile = $this->getProfile();
if (!$profile) { if (!$profile) {
return null; return null;
} else { } 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(); $profile = $this->getProfile();
if (!$profile) { if (!$profile) {
return null; return null;
} else { } 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); 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() function blowFavesCache()
@ -790,7 +790,7 @@ class User extends Memcached_DataObject
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _repeatedByMeDirect($offset, $limit, $since_id, $max_id, $since) function _repeatedByMeDirect($offset, $limit, $since_id, $max_id)
{ {
$notice = new Notice(); $notice = new Notice();
@ -814,10 +814,6 @@ class User extends Memcached_DataObject
$notice->whereAdd('id <= ' . $max_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) {
$notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
}
$ids = array(); $ids = array();
if ($notice->find()) { if ($notice->find()) {
@ -837,12 +833,12 @@ class User extends Memcached_DataObject
$ids = Notice::stream(array($this, '_repeatsOfMeDirect'), $ids = Notice::stream(array($this, '_repeatsOfMeDirect'),
array(), array(),
'user:repeats_of_me:'.$this->id, 'user:repeats_of_me:'.$this->id,
$offset, $limit, $since_id, $max_id, null); $offset, $limit, $since_id, $max_id);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id, $since) function _repeatsOfMeDirect($offset, $limit, $since_id, $max_id)
{ {
$qry = $qry =
'SELECT DISTINCT original.id AS id ' . 'SELECT DISTINCT original.id AS id ' .
@ -857,10 +853,6 @@ class User extends Memcached_DataObject
$qry .= 'AND original.id <= ' . $max_id . ' '; $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! // NOTE: we sort by fave time, not by notice time!
$qry .= 'ORDER BY original.id DESC '; $qry .= 'ORDER BY original.id DESC ';

View File

@ -91,7 +91,7 @@ class User_group extends Memcached_DataObject
return Notice::getStreamByIds($ids); 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(); $inbox = new Group_inbox();
@ -108,10 +108,6 @@ class User_group extends Memcached_DataObject
$inbox->whereAdd('notice_id <= ' . $max_id); $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'); $inbox->orderBy('notice_id DESC');
if (!is_null($offset)) { if (!is_null($offset)) {

View File

@ -425,8 +425,6 @@ class Action extends HTMLOutputter // lawsuit
$connect = 'imsettings'; $connect = 'imsettings';
} else if (common_config('sms', 'enabled')) { } else if (common_config('sms', 'enabled')) {
$connect = 'smssettings'; $connect = 'smssettings';
} else if (common_config('twitter', 'enabled')) {
$connect = 'twittersettings';
} }
$this->elementStart('dl', array('id' => 'site_nav_global_primary')); $this->elementStart('dl', array('id' => 'site_nav_global_primary'));

View File

@ -63,7 +63,6 @@ class ApiAction extends Action
var $count = null; var $count = null;
var $max_id = null; var $max_id = null;
var $since_id = null; var $since_id = null;
var $since = null;
var $access = self::READ_ONLY; // read (default) or read-write 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->count = (int)$this->arg('count', 20);
$this->max_id = (int)$this->arg('max_id', 0); $this->max_id = (int)$this->arg('max_id', 0);
$this->since_id = (int)$this->arg('since_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; return true;
} }
@ -1326,8 +1328,6 @@ class ApiAction extends Action
case 'max_id': case 'max_id':
$max_id = (int)$this->args['max_id']; $max_id = (int)$this->args['max_id'];
return ($max_id < 1) ? 0 : $max_id; return ($max_id < 1) ? 0 : $max_id;
case 'since':
return strtotime($this->args['since']);
default: default:
return parent::arg($key, $def); return parent::arg($key, $def);
} }

View File

@ -177,8 +177,8 @@ $default =
array('source' => 'StatusNet', # source attribute for Twitter array('source' => 'StatusNet', # source attribute for Twitter
'taguri' => null), # base for tag URIs 'taguri' => null), # base for tag URIs
'twitter' => 'twitter' =>
array('enabled' => true, array('signin' => true,
'consumer_key' => null, 'consumer_key' => null,
'consumer_secret' => null), 'consumer_secret' => null),
'cache' => 'cache' =>
array('base' => null), array('base' => null),

View File

@ -390,7 +390,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore
$sub->subscribed = $user->id; $sub->subscribed = $user->id;
if (!$sub->find(true)) { if (!$sub->find(true)) {
return 0; return array();
} }
/* Since we do not use OMB_Service_Providers action methods, there /* Since we do not use OMB_Service_Providers action methods, there

View File

@ -77,7 +77,7 @@ function omb_broadcast_notice($notice)
/* Get remote users subscribed to this profile. */ /* Get remote users subscribed to this profile. */
$rp = new Remote_profile(); $rp = new Remote_profile();
$rp->query('SELECT postnoticeurl, token, secret ' . $rp->query('SELECT remote_profile.*, secret, token ' .
'FROM subscription JOIN remote_profile ' . 'FROM subscription JOIN remote_profile ' .
'ON subscription.subscriber = remote_profile.id ' . 'ON subscription.subscriber = remote_profile.id ' .
'WHERE subscription.subscribed = ' . $notice->profile_id . ' '); 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ');
@ -93,7 +93,8 @@ function omb_broadcast_notice($notice)
/* Post notice. */ /* Post notice. */
$service = new StatusNet_OMB_Service_Consumer( $service = new StatusNet_OMB_Service_Consumer(
array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl)); array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl),
$rp->uri);
try { try {
$service->setToken($rp->token, $rp->secret); $service->setToken($rp->token, $rp->secret);
$service->postNotice($omb_notice); $service->postNotice($omb_notice);
@ -125,7 +126,7 @@ function omb_broadcast_profile($profile)
/* Get remote users subscribed to this profile. */ /* Get remote users subscribed to this profile. */
$rp = new Remote_profile(); $rp = new Remote_profile();
$rp->query('SELECT updateprofileurl, token, secret ' . $rp->query('SELECT remote_profile.*, secret, token ' .
'FROM subscription JOIN remote_profile ' . 'FROM subscription JOIN remote_profile ' .
'ON subscription.subscriber = remote_profile.id ' . 'ON subscription.subscriber = remote_profile.id ' .
'WHERE subscription.subscribed = ' . $profile->id . ' '); 'WHERE subscription.subscribed = ' . $profile->id . ' ');
@ -141,7 +142,8 @@ function omb_broadcast_profile($profile)
/* Update profile. */ /* Update profile. */
$service = new StatusNet_OMB_Service_Consumer( $service = new StatusNet_OMB_Service_Consumer(
array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl)); array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl),
$rp->uri);
try { try {
$service->setToken($rp->token, $rp->secret); $service->setToken($rp->token, $rp->secret);
$service->updateProfile($omb_profile); $service->updateProfile($omb_profile);
@ -159,13 +161,14 @@ function omb_broadcast_profile($profile)
} }
class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer { class StatusNet_OMB_Service_Consumer extends OMB_Service_Consumer {
public function __construct($urls) public function __construct($urls, $listener_uri=null)
{ {
$this->services = $urls; $this->services = $urls;
$this->datastore = omb_oauth_datastore(); $this->datastore = omb_oauth_datastore();
$this->oauth_consumer = omb_oauth_consumer(); $this->oauth_consumer = omb_oauth_consumer();
$this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
$this->fetcher->timeout = intval(common_config('omb', 'timeout')); $this->fetcher->timeout = intval(common_config('omb', 'timeout'));
$this->listener_uri = $listener_uri;
} }
} }

View File

@ -273,13 +273,18 @@ class ProfileListItem extends Widget
$usf = new UnsubscribeForm($this->out, $this->profile); $usf = new UnsubscribeForm($this->out, $this->profile);
$usf->show(); $usf->show();
} else { } else {
// Is it a local user? can't remote sub from a list
// XXX: make that possible!
$other = User::staticGet('id', $this->profile->id); $other = User::staticGet('id', $this->profile->id);
if (!empty($other)) { if (!empty($other)) {
$sf = new SubscribeForm($this->out, $this->profile); $sf = new SubscribeForm($this->out, $this->profile);
$sf->show(); $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'); $this->out->elementEnd('li');
} }

View File

@ -550,7 +550,7 @@ function common_find_mentions($text, $notice)
} else if (!empty($originalMentions) && } else if (!empty($originalMentions) &&
array_key_exists($nickname, $originalMentions)) { array_key_exists($nickname, $originalMentions)) {
$mention = $originalMentions[$nickname]; $mentioned = $originalMentions[$nickname];
} else { } else {
$mentioned = common_relative_profile($sender, $nickname); $mentioned = common_relative_profile($sender, $nickname);
} }

View File

@ -22,7 +22,7 @@
* @category Plugin * @category Plugin
* @package StatusNet * @package StatusNet
* @author Zach Copley <zach@status.net> * @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 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
@ -32,12 +32,12 @@ if (!defined('STATUSNET')) {
} }
define("FACEBOOK_CONNECT_SERVICE", 3); define("FACEBOOK_CONNECT_SERVICE", 3);
define('FACEBOOKPLUGIN_VERSION', '0.9');
require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php'; 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 * @category Plugin
* @package StatusNet * @package StatusNet
@ -49,6 +49,36 @@ require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
class FacebookPlugin extends Plugin 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 * Add Facebook app actions to the router table
* *
@ -70,6 +100,7 @@ class FacebookPlugin extends Plugin
array('action' => 'facebooksettings')); array('action' => 'facebooksettings'));
$m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite'));
$m->connect('facebook/app/remove', array('action' => 'facebookremove')); $m->connect('facebook/app/remove', array('action' => 'facebookremove'));
$m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
// Facebook Connect stuff // Facebook Connect stuff
@ -98,6 +129,7 @@ class FacebookPlugin extends Plugin
case 'FacebookinviteAction': case 'FacebookinviteAction':
case 'FacebookremoveAction': case 'FacebookremoveAction':
case 'FacebooksettingsAction': case 'FacebooksettingsAction':
case 'FacebookadminpanelAction':
include_once INSTALLDIR . '/plugins/Facebook/' . include_once INSTALLDIR . '/plugins/Facebook/' .
strtolower(mb_substr($cls, 0, -6)) . '.php'; strtolower(mb_substr($cls, 0, -6)) . '.php';
return false; 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 * Override normal HTML output to force the content type to
* text/html and add in xmlns:fb * text/html and add in xmlns:fb
@ -359,8 +417,6 @@ class FacebookPlugin extends Plugin
$connect = 'imsettings'; $connect = 'imsettings';
} else if (common_config('sms', 'enabled')) { } else if (common_config('sms', 'enabled')) {
$connect = 'smssettings'; $connect = 'smssettings';
} else if (common_config('twitter', 'enabled')) {
$connect = 'twittersettings';
} }
if (!empty($user)) { if (!empty($user)) {
@ -525,15 +581,18 @@ class FacebookPlugin extends Plugin
function onPluginVersion(&$versions) function onPluginVersion(&$versions)
{ {
$versions[] = array('name' => 'Facebook', $versions[] = array(
'version' => FACEBOOKPLUGIN_VERSION, 'name' => 'Facebook',
'author' => 'Zach Copley', 'version' => self::VERSION,
'homepage' => 'http://status.net/wiki/Plugin:Facebook', 'author' => 'Zach Copley',
'rawdescription' => 'homepage' => 'http://status.net/wiki/Plugin:Facebook',
_m('The Facebook plugin allows you to integrate ' . 'rawdescription' => _m(
'your StatusNet instance with ' . 'The Facebook plugin allows you to integrate ' .
'<a href="http://facebook.com/">Facebook</a> ' . 'your StatusNet instance with ' .
'and Facebook Connect.')); '<a href="http://facebook.com/">Facebook</a> ' .
'and Facebook Connect.'
)
);
return true; return true;
} }

View File

@ -1,6 +1,9 @@
This plugin allows you to use Facebook Connect with StatusNet, provides a Facebook Plugin
Facebook application for your users, and allows them to update their ===============
Facebook statuses from StatusNet.
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 Facebook Connect
---------------- ----------------
@ -15,12 +18,12 @@ Facebook credentials. With Facebook Connect, your users can:
Built-in Facebook Application Built-in Facebook Application
----------------------------- -----------------------------
The plugin also installs a StatusNet Facebook application that allows your The plugin also installs a StatusNet Facebook canvas application that
users to automatically update their Facebook statuses with their latest allows your users to automatically update their Facebook status with
notices, invite their friends to use the app (and thus your site), view their latest notices, invite their friends to use the app (and thus your
their notice timelines, and post notices -- all from within Facebook. The site), view their notice timelines and post notices -- all from within
application is built into the StatusNet Facebook plugin and runs on your Facebook. The application is built into the StatusNet Facebook plugin
host. and runs on your host.
Quick setup instructions* Quick setup instructions*
------------------------- -------------------------
@ -29,13 +32,9 @@ Install the Facebook Developer application on Facebook:
http://www.facebook.com/developers/ http://www.facebook.com/developers/
Use it to create a new application and generate an API key and secret. Add a Use it to create a new application and generate an API key and secret.
Facebook app section of your config.php and copy in the key and secret, You will need the key and secret so cut-n-paste them into your text
e.g.: editor or write them down.
// Config section for the built-in Facebook application
$config['facebook']['apikey'] = 'APIKEY';
$config['facebook']['secret'] = 'SECRET';
In Facebook's application editor, specify the following URLs for your app: 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/Connect/Setting_Up_Your_Site
http://wiki.developers.facebook.com/index.php/Creating_your_first_application http://wiki.developers.facebook.com/index.php/Creating_your_first_application
Finally you must activate the plugin by adding the following line to your Finally you must activate the plugin by adding it in your config.php
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'); 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 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: 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: 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" You should see a page with a blue button that says: "Connect with Facebook"
and you should be able to login or register. 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. StatusNet.
* Note: Before a user can disconnect from Facebook, she must set a normal * 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 account in the future. This is usually only required for users who have
used Facebook Connect to register their StatusNet account, and therefore used Facebook Connect to register their StatusNet account, and therefore
haven't already set a local password. haven't already set a local password.
@ -109,16 +133,20 @@ StatusNet.
Offline Queue Handling Offline Queue Handling
---------------------- ----------------------
For larger sites needing better performance it's possible to enable queuing For larger sites needing better performance it's possible to enable
and have users' notices posted to Facebook via a separate "offline" queuing and have users' notices posted to Facebook via a separate
FacebookQueueHandler (facebookqueuhandler.php in the Facebook plugin "offline" process -- FacebookQueueHandler (facebookqueuhandler.php in
directory), which will be started by the plugin along with their other the Facebook plugin directory). It will run automatically if you have
daemons when you run scripts/startdaemons.sh. See the StatusNet README for enabled StatusNet's offline queueing subsystem. See the "Queues and
more about queuing and daemons. daemons" section in the StatusNet README for more about queuing.
TODO 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 - Invite Facebook friends to use your StatusNet installation via Facebook
Connect Connect
- Auto-subscribe Facebook friends already using StatusNet - Auto-subscribe Facebook friends already using StatusNet
@ -126,4 +154,4 @@ TODO
- Allow users to update their Facebook statuses once they have authenticated - 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 with Facebook Connect (no need for them to use the Facebook app if they
don't want to). don't want to).
- Re-design the whole thing to support multiple instances of StatusNet - Import a user's Facebook updates into StatusNet

View File

@ -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'));
}
}

View File

@ -312,8 +312,6 @@ class MobileProfilePlugin extends WAP20Plugin
$connect = 'imsettings'; $connect = 'imsettings';
} else if (common_config('sms', 'enabled')) { } else if (common_config('sms', 'enabled')) {
$connect = 'smssettings'; $connect = 'smssettings';
} else if (common_config('twitter', 'enabled')) {
$connect = 'twittersettings';
} }
$action->elementStart('ul', array('id' => 'site_nav_global_primary')); $action->elementStart('ul', array('id' => 'site_nav_global_primary'));

View 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) 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, $text,
$wmatches, $wmatches,
PREG_OFFSET_CAPTURE); PREG_OFFSET_CAPTURE);
foreach ($wmatches[1] as $wmatch) { foreach ($wmatches[1] as $wmatch) {
$target = $wmatch[0];
$oprofile = null;
$webfinger = $wmatch[0]; if (strpos($target, '/') === false) {
$this->log(LOG_INFO, "Checking Webfinger for address '$target'");
$this->log(LOG_INFO, "Checking Webfinger for address '$webfinger'"); try {
$oprofile = Ostatus_profile::ensureWebfinger($target);
$oprofile = Ostatus_profile::ensureWebfinger($webfinger); } 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)) { if (empty($oprofile)) {
$this->log(LOG_INFO, "No Ostatus_profile found for address '$target'");
$this->log(LOG_INFO, "No Ostatus_profile found for address '$webfinger'");
} else { } else {
$this->log(LOG_INFO, "Ostatus_profile found for address '$webfinger'"); $this->log(LOG_INFO, "Ostatus_profile found for address '$target'");
if ($oprofile->isGroup()) { if ($oprofile->isGroup()) {
continue; continue;
@ -261,7 +292,7 @@ class OStatusPlugin extends Plugin
} }
} }
$mentions[] = array('mentioned' => array($profile), $mentions[] = array('mentioned' => array($profile),
'text' => $wmatch[0], 'text' => $target,
'position' => $pos, 'position' => $pos,
'url' => $profile->profileurl); 'url' => $profile->profileurl);
} }
@ -674,6 +705,20 @@ class OStatusPlugin extends Plugin
} }
function onStartShowSubscriptionsContent($action) function onStartShowSubscriptionsContent($action)
{
$this->showEntityRemoteSubscribe($action);
return true;
}
function onStartShowAllContent($action)
{
$this->showEntityRemoteSubscribe($action);
return true;
}
function showEntityRemoteSubscribe($action)
{ {
$user = common_current_user(); $user = common_current_user();
if ($user && ($user->id == $action->profile->id)) { if ($user && ($user->id == $action->profile->id)) {
@ -686,8 +731,6 @@ class OStatusPlugin extends Plugin
$action->elementEnd('p'); $action->elementEnd('p');
$action->elementEnd('div'); $action->elementEnd('div');
} }
return true;
} }
/** /**

View File

@ -112,7 +112,7 @@ class OStatusSubAction extends Action
$this->submit('submit', _m('Join'), 'submit', null, $this->submit('submit', _m('Join'), 'submit', null,
_m('Join this group')); _m('Join this group'));
} else { } else {
$this->submit('submit', _m('Subscribe'), 'submit', null, $this->submit('submit', _m('Confirm'), 'submit', null,
_m('Subscribe to this user')); _m('Subscribe to this user'));
} }
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');

View File

@ -104,7 +104,7 @@ class PushHubAction extends Action
throw new ClientException("Invalid hub.secret $secret; must be under 200 bytes."); 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) { if (!$sub) {
// Creating a new one! // Creating a new one!
$sub = new HubSub(); $sub = new HubSub();

View File

@ -260,9 +260,15 @@ class HubSub extends Memcached_DataObject
$retries = intval(common_config('ostatus', 'hub_retries')); $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, 'atom' => $atom,
'retries' => $retries); 'retries' => $retries);
common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback");
$qm = QueueManager::get(); $qm = QueueManager::get();
$qm->enqueue($data, 'hubout'); $qm->enqueue($data, 'hubout');
} }

View File

@ -146,8 +146,10 @@ class Magicsig extends Memcached_DataObject
$mod = base64_url_decode($matches[1]); $mod = base64_url_decode($matches[1]);
$exp = base64_url_decode($matches[2]); $exp = base64_url_decode($matches[2]);
if ($matches[4]) { if (!empty($matches[4])) {
$private_exp = base64_url_decode($matches[4]); $private_exp = base64_url_decode($matches[4]);
} else {
$private_exp = false;
} }
$params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public'); $params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');

View File

@ -698,7 +698,7 @@ class Ostatus_profile extends Memcached_DataObject
{ {
// Get the canonical feed URI and check it // Get the canonical feed URI and check it
$discover = new FeedDiscovery(); $discover = new FeedDiscovery();
if ($hints['feedurl']) { if (isset($hints['feedurl'])) {
$feeduri = $hints['feedurl']; $feeduri = $hints['feedurl'];
$feeduri = $discover->discoverFromFeedURL($feeduri); $feeduri = $discover->discoverFromFeedURL($feeduri);
} else { } else {
@ -1145,7 +1145,7 @@ class Ostatus_profile extends Memcached_DataObject
if (!empty($poco)) { if (!empty($poco)) {
$url = $poco->getPrimaryURL(); $url = $poco->getPrimaryURL();
if ($url->type == 'homepage') { if ($url && $url->type == 'homepage') {
$homepage = $url->value; $homepage = $url->value;
} }
} }

View File

@ -94,7 +94,7 @@ class Discovery
$links = call_user_func(array($class, 'discover'), $uri); $links = call_user_func(array($class, 'discover'), $uri);
if ($link = Discovery::getService($links, Discovery::LRDD_REL)) { if ($link = Discovery::getService($links, Discovery::LRDD_REL)) {
// Load the LRDD XRD // Load the LRDD XRD
if ($link['template']) { if (!empty($link['template'])) {
$xrd_uri = Discovery::applyTemplate($link['template'], $uri); $xrd_uri = Discovery::applyTemplate($link['template'], $uri);
} else { } else {
$xrd_uri = $link['href']; $xrd_uri = $link['href'];

View File

@ -53,17 +53,22 @@ class XRD
$xrd = new XRD(); $xrd = new XRD();
$dom = new DOMDocument(); $dom = new DOMDocument();
$dom->loadXML($xml); if (!$dom->loadXML($xml)) {
throw new Exception("Invalid XML");
}
$xrd_element = $dom->getElementsByTagName('XRD')->item(0); $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
// Check for host-meta host // Check for host-meta host
$host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; $host = $xrd_element->getElementsByTagName('Host')->item(0);
if ($host) { if ($host) {
$xrd->host = $host; $xrd->host = $host->nodeValue;
} }
// Loop through other elements // Loop through other elements
foreach ($xrd_element->childNodes as $node) { foreach ($xrd_element->childNodes as $node) {
if (!($node instanceof DOMElement)) {
continue;
}
switch ($node->tagName) { switch ($node->tagName) {
case 'Expires': case 'Expires':
$xrd->expires = $node->nodeValue; $xrd->expires = $node->nodeValue;
@ -156,20 +161,20 @@ class XRD
function saveLink($doc, $link) function saveLink($doc, $link)
{ {
$link_element = $doc->createElement('Link'); $link_element = $doc->createElement('Link');
if ($link['rel']) { if (!empty($link['rel'])) {
$link_element->setAttribute('rel', $link['rel']); $link_element->setAttribute('rel', $link['rel']);
} }
if ($link['type']) { if (!empty($link['type'])) {
$link_element->setAttribute('type', $link['type']); $link_element->setAttribute('type', $link['type']);
} }
if ($link['href']) { if (!empty($link['href'])) {
$link_element->setAttribute('href', $link['href']); $link_element->setAttribute('href', $link['href']);
} }
if ($link['template']) { if (!empty($link['template'])) {
$link_element->setAttribute('template', $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) { foreach($link['title'] as $title) {
$title = $doc->createElement('Title', $title); $title = $doc->createElement('Title', $title);
$link_element->appendChild($title); $link_element->appendChild($title);

View File

@ -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;
}
}
}

View File

@ -38,11 +38,11 @@ display:none;
min-width:96px; min-width:96px;
} }
#subscriptions #entity_remote_subscribe { #entity_remote_subscribe {
padding:0; padding:0;
float:right; float:right;
} }
#subscriptions .entity_remote_subscribe { #all #entity_remote_subscribe {
float:right; margin-top:-52px;
} }

View File

@ -75,7 +75,7 @@ class SphinxSearch extends SearchEngine
{ {
if ('chron' === $mode) { if ('chron' === $mode) {
$this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts'); $this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts');
return $this->target->orderBy('created desc'); return $this->target->orderBy('id desc');
} }
} }

View File

@ -1,47 +1,89 @@
Twitter Bridge Plugin
=====================
This Twitter "bridge" plugin allows you to integrate your StatusNet This Twitter "bridge" plugin allows you to integrate your StatusNet
instance with Twitter. Installing it will allow your users to: 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 - automatically subscribe to other Twitter users who are also using
your StatusNet install, if possible (requires running a daemon) your StatusNet install, if possible (requires running a daemon)
- import their Twitter friends' tweets (requires running a daemon) - import their Twitter friends' tweets (requires running a daemon)
- allow users to authenticate using Twitter ('Sign in with Twitter')
Installation Installation
------------ ------------
To enable the plugin, add the following to your config.php: 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
addPlugin("TwitterBridge"); you will need to register your instance of StatusNet as an application
on Twitter (http://twitter.com/apps). During the application
OAuth is used to to access protected resources on Twitter (as opposed to registration process your application will be assigned a "consumer" key
HTTP Basic Auth)*. To use Twitter bridging you will need to register and secret, which the plugin will use to make OAuth requests to Twitter.
your instance of StatusNet as an application on Twitter You can either pass the consumer key and secret in when you enable the
(http://twitter.com/apps), and update the following variables in your plugin, or set it using the Twitter administration panel.
config.php with the consumer key and secret Twitter generates for you:
$config['twitter']['consumer_key'] = 'YOURKEY';
$config['twitter']['consumer_secret'] = 'YOURSECRET';
When registering your application with Twitter set the type to "Browser" When registering your application with Twitter set the type to "Browser"
and your Callback URL to: and your Callback URL to:
http://example.org/mublog/twitter/authorization 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 * Note: The plugin will still push notices to Twitter for users who
have previously setup the Twitter bridge using their Twitter name and have previously set up the Twitter bridge using their Twitter name and
password under an older versions of StatusNet, but all new Twitter password under an older version of StatusNet, but all new Twitter
bridge connections will use OAuth. 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 For friend syncing and importing Twitter tweets, running two
scripts is necessary (synctwitterfriends.php and additional daemon scripts is necessary: synctwitterfriends.php and
twitterstatusfetcher.php). 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) * 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 accounts on your StatusNet system, and who have previously set up a link
for automatically posting notices to Twitter. for automatically posting notices to Twitter.
The plugin will try to start this daemon when you run The plugin will start this daemon when you run scripts/startdaemons.sh.
scripts/startdaemons.sh.
* Importing statuses from Twitter (daemons/twitterstatusfetcher.php) * Importing statuses from Twitter (daemons/twitterstatusfetcher.php)
To allow your users to import their friends' Twitter statuses, you will You can allow uses to enable importing of your friends' Twitter
need to enable the bidirectional Twitter bridge in your config.php: timelines either in the Twitter bridge administration panel or in your
config.php using the following configuration line:
$config['twitterimport']['enabled'] = true; $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, Additionally, you will want to set the integration source variable,
which will keep notices posted to Twitter via StatusNet from looping which will keep notices posted to Twitter via StatusNet from looping
back. The integration source should be set to the name of your back. You can do this in the Twitter bridge administration panel, or
application, exactly as you specified it on the settings page for your 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.: StatusNet application on Twitter, e.g.:
$config['integration']['source'] = 'YourApp'; $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 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 haven't enabled queuing are still able to push notices to Twitter, but
for larger sites and sites that wish to improve performance, this for larger sites and sites that wish to improve performance the script
script allows notices to be sent "offline" via a separate process. 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.

View File

@ -23,7 +23,7 @@
* @author Julien C <chaumond@gmail.com> * @author Julien C <chaumond@gmail.com>
* @copyright 2009-2010 Control Yourself, Inc. * @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 * @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')) { if (!defined('STATUSNET')) {
@ -32,8 +32,6 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
/** /**
* Plugin for sending and importing Twitter statuses * Plugin for sending and importing Twitter statuses
* *
@ -44,19 +42,41 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @author Julien C <chaumond@gmail.com> * @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 * @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/ * @link http://twitter.com/
*/ */
class TwitterBridgePlugin extends Plugin class TwitterBridgePlugin extends Plugin
{ {
const VERSION = STATUSNET_VERSION;
/** /**
* Initializer for the plugin. * 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) function onRouterInitialized($m)
{ {
$m->connect('twitter/authorization', $m->connect(
array('action' => 'twitterauthorization')); 'twitter/authorization',
array('action' => 'twitterauthorization')
);
$m->connect('settings/twitter', array('action' => 'twittersettings')); $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; return true;
} }
@ -88,13 +115,16 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onEndLoginGroupNav(&$action) function onEndLoginGroupNav(&$action)
{ {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
$action->menuItem(common_local_url('twitterlogin'), if (common_config('twitter', 'signin')) {
_('Twitter'), $action->menuItem(
_('Login or register using Twitter'), common_local_url('twitterlogin'),
'twitterlogin' === $action_name); _m('Twitter'),
_m('Login or register using Twitter'),
'twitterlogin' === $action_name
);
}
return true; return true;
} }
@ -110,10 +140,12 @@ class TwitterBridgePlugin extends Plugin
{ {
$action_name = $action->trimmed('action'); $action_name = $action->trimmed('action');
$action->menuItem(common_local_url('twittersettings'), $action->menuItem(
_m('Twitter'), common_local_url('twittersettings'),
_m('Twitter integration options'), _m('Twitter'),
$action_name === 'twittersettings'); _m('Twitter integration options'),
$action_name === 'twittersettings'
);
return true; return true;
} }
@ -132,6 +164,7 @@ class TwitterBridgePlugin extends Plugin
case 'TwittersettingsAction': case 'TwittersettingsAction':
case 'TwitterauthorizationAction': case 'TwitterauthorizationAction':
case 'TwitterloginAction': case 'TwitterloginAction':
case 'TwitteradminpanelAction':
include_once INSTALLDIR . '/plugins/TwitterBridge/' . include_once INSTALLDIR . '/plugins/TwitterBridge/' .
strtolower(mb_substr($cls, 0, -6)) . '.php'; strtolower(mb_substr($cls, 0, -6)) . '.php';
return false; return false;
@ -173,12 +206,18 @@ class TwitterBridgePlugin extends Plugin
*/ */
function onGetValidDaemons($daemons) function onGetValidDaemons($daemons)
{ {
array_push($daemons, INSTALLDIR . array_push(
'/plugins/TwitterBridge/daemons/synctwitterfriends.php'); $daemons,
INSTALLDIR
. '/plugins/TwitterBridge/daemons/synctwitterfriends.php'
);
if (common_config('twitterimport', 'enabled')) { if (common_config('twitterimport', 'enabled')) {
array_push($daemons, INSTALLDIR array_push(
. '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); $daemons,
INSTALLDIR
. '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'
);
} }
return true; return true;
@ -197,17 +236,55 @@ class TwitterBridgePlugin extends Plugin
return true; 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) function onPluginVersion(&$versions)
{ {
$versions[] = array('name' => 'TwitterBridge', $versions[] = array(
'version' => TWITTERBRIDGEPLUGIN_VERSION, 'name' => 'TwitterBridge',
'author' => 'Zach Copley', 'version' => self::VERSION,
'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge', 'author' => 'Zach Copley, Julien C',
'rawdescription' => 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
_m('The Twitter "bridge" plugin allows you to integrate ' . 'rawdescription' => _m(
'your StatusNet instance with ' . 'The Twitter "bridge" plugin allows you to integrate ' .
'<a href="http://twitter.com/">Twitter</a>.')); 'your StatusNet instance with ' .
'<a href="http://twitter.com/">Twitter</a>.'
)
);
return true; return true;
} }
} }

View File

@ -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'));
}
}

View File

@ -47,7 +47,7 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
* @author Julien C <chaumond@gmail.com> * @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 * @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 class TwitterauthorizationAction extends Action