diff options
author | Craig Andrews <candrews@integralblue.com> | 2010-01-27 13:41:02 -0500 |
---|---|---|
committer | Craig Andrews <candrews@integralblue.com> | 2010-01-27 13:41:02 -0500 |
commit | 5224c7d6c263f078c3628650ff2b4b706862307c (patch) | |
tree | 9ce11b5470dfe1f64a8944dccb120f628a377e09 | |
parent | 767b77d6c2291d6e7f4795ce18c70d5354a07de9 (diff) | |
parent | 656d95418c6d7f8b884c4c8af14ad6952032ace6 (diff) |
Merge branch '0.9.x' into 1.0.x
36 files changed, 989 insertions, 367 deletions
diff --git a/actions/apiaccountupdateprofile.php b/actions/apiaccountupdateprofile.php index fd4384a25..9b371ea95 100644 --- a/actions/apiaccountupdateprofile.php +++ b/actions/apiaccountupdateprofile.php @@ -115,11 +115,11 @@ class ApiAccountUpdateProfileAction extends ApiAuthAction $original = clone($profile); - if (empty($this->name)) { + if (!empty($this->name)) { $profile->fullname = $this->name; } - if (empty($this->url)) { + if (!empty($this->url)) { $profile->homepage = $this->url; } diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php index d9d4820c0..128c881e2 100644 --- a/actions/apistatusesretweet.php +++ b/actions/apistatusesretweet.php @@ -112,7 +112,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $repeat = $this->original->repeat($this->user->id, $this->source); - common_broadcast_notice($repeat); + $this->showNotice($repeat); } diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index f8bf7cf87..31c9b20ce 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -255,7 +255,7 @@ class ApiStatusesUpdateAction extends ApiAuthAction $upload->attachToNotice($this->notice); } - common_broadcast_notice($this->notice); + } $this->showNotice(); diff --git a/actions/newnotice.php b/actions/newnotice.php index a4ed87bb6..78480abab 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -201,7 +201,7 @@ class NewnoticeAction extends Action $upload->attachToNotice($notice); } - common_broadcast_notice($notice); + if ($this->boolean('ajax')) { header('Content-Type: text/xml;charset=utf-8'); diff --git a/actions/repeat.php b/actions/repeat.php index b75523498..e112496bc 100644 --- a/actions/repeat.php +++ b/actions/repeat.php @@ -106,7 +106,7 @@ class RepeatAction extends Action { $repeat = $this->notice->repeat($this->user->id, 'web'); - common_broadcast_notice($repeat); + if ($this->boolean('ajax')) { $this->startHTML('text/xml;charset=utf-8'); diff --git a/classes/Inbox.php b/classes/Inbox.php index 086dba1c9..26b27d2b5 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -120,11 +120,7 @@ class Inbox extends Memcached_DataObject $notice_id, $user_id)); if ($result) { - $c = self::memcache(); - - if (!empty($c)) { - $c->delete(self::cacheKey('inbox', 'user_id', $user_id)); - } + self::blow('inbox:user_id:%d', $user_id); } return $result; diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 33645a3e8..43610dddb 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -458,4 +458,23 @@ class Memcached_DataObject extends DB_DataObject return $dsn; } + + static function blow() + { + $c = self::memcache(); + + if (empty($c)) { + return false; + } + + $args = func_get_args(); + + $format = array_shift($args); + + $keyPart = vsprintf($format, $args); + + $cacheKey = common_cache_key($keyPart); + + return $c->delete($cacheKey); + } } diff --git a/classes/Notice.php b/classes/Notice.php index 38b10db04..0966697e2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -94,10 +94,6 @@ class Notice extends Memcached_DataObject function delete() { - $this->blowCaches(true); - $this->blowFavesCache(true); - $this->blowSubsCache(true); - // For auditing purposes, save a record that the notice // was deleted. @@ -109,31 +105,20 @@ class Notice extends Memcached_DataObject $deleted->created = $this->created; $deleted->deleted = common_sql_now(); - $this->query('BEGIN'); - $deleted->insert(); - //Null any notices that are replies to this notice - $this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id)); - - //Null any notices that are repeats of this notice - //XXX: probably need to uncache these, too + // Clear related records - $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id)); + $this->clearReplies(); + $this->clearRepeats(); + $this->clearFaves(); + $this->clearTags(); + $this->clearGroupInboxes(); - $related = array('Reply', - 'Fave', - 'Notice_tag', - 'Group_inbox', - 'Queue_item'); + // NOTE: we don't clear inboxes + // NOTE: we don't clear queue items - foreach ($related as $cls) { - $inst = new $cls(); - $inst->notice_id = $this->id; - $inst->delete(); - } $result = parent::delete(); - $this->query('COMMIT'); } function saveTags() @@ -155,6 +140,7 @@ class Notice extends Memcached_DataObject foreach(array_unique($hashtags) as $hashtag) { /* elide characters we don't want in the tag */ $this->saveTag($hashtag); + self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $tag->tag); } return true; } @@ -172,6 +158,9 @@ class Notice extends Memcached_DataObject $last_error->message)); return; } + + // if it's saved, blow its cache + $tag->blowCache(false); } /** @@ -331,27 +320,43 @@ class Notice extends Memcached_DataObject } } - // XXX: do we need to change this for remote users? + } + + # Clear the cache for subscribed users, so they'll update at next request + # XXX: someone clever could prepend instead of clearing the cache + $notice->blowOnInsert(); - $notice->saveTags(); + $qm = QueueManager::get(); - $groups = $notice->saveGroups(); + $qm->enqueue($notice, 'distrib'); - $recipients = $notice->saveReplies(); + return $notice; + } - $notice->addToInboxes($groups, $recipients); + function blowOnInsert() + { + self::blow('profile:notice_ids:%d', $this->profile_id); + self::blow('public'); - $notice->saveUrls(); + if ($this->conversation != $this->id) { + self::blow('notice:conversation_ids:%d', $this->conversation); + } - Event::handle('EndNoticeSave', array($notice)); + if (!empty($this->repeat_of)) { + self::blow('notice:repeats:%d', $this->repeat_of); } - # Clear the cache for subscribed users, so they'll update at next request - # XXX: someone clever could prepend instead of clearing the cache + $original = Notice::staticGet('id', $this->repeat_of); - $notice->blowCaches(); + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + self::blow('user:repeats_of_me:%d', $originalUser->id); + } + } - return $notice; + $profile = Profile::staticGet($this->profile_id); + $profile->blowNoticeCount(); } /** save all urls in the notice to the db @@ -456,227 +461,6 @@ class Notice extends Memcached_DataObject return $att; } - function blowCaches($blowLast=false) - { - $this->blowSubsCache($blowLast); - $this->blowNoticeCache($blowLast); - $this->blowRepliesCache($blowLast); - $this->blowPublicCache($blowLast); - $this->blowTagCache($blowLast); - $this->blowGroupCache($blowLast); - $this->blowConversationCache($blowLast); - $this->blowRepeatCache(); - $profile = Profile::staticGet($this->profile_id); - $profile->blowNoticeCount(); - } - - function blowRepeatCache() - { - if (!empty($this->repeat_of)) { - $cache = common_memcache(); - if (!empty($cache)) { - // XXX: only blow if <100 in cache - $ck = common_cache_key('notice:repeats:'.$this->repeat_of); - $result = $cache->delete($ck); - - $user = User::staticGet('id', $this->profile_id); - - if (!empty($user)) { - $uk = common_cache_key('user:repeated_by_me:'.$user->id); - $cache->delete($uk); - $user->free(); - unset($user); - } - - $original = Notice::staticGet('id', $this->repeat_of); - - if (!empty($original)) { - $originalUser = User::staticGet('id', $original->profile_id); - if (!empty($originalUser)) { - $ouk = common_cache_key('user:repeats_of_me:'.$originalUser->id); - $cache->delete($ouk); - $originalUser->free(); - unset($originalUser); - } - $original->free(); - unset($original); - } - } - } - } - - function blowConversationCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $ck = common_cache_key('notice:conversation_ids:'.$this->conversation); - $cache->delete($ck); - if ($blowLast) { - $cache->delete($ck.';last'); - } - } - } - - function blowGroupCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $group_inbox = new Group_inbox(); - $group_inbox->notice_id = $this->id; - if ($group_inbox->find()) { - while ($group_inbox->fetch()) { - $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id)); - if ($blowLast) { - $cache->delete(common_cache_key('user_group:notice_ids:' . $group_inbox->group_id.';last')); - } - $member = new Group_member(); - $member->group_id = $group_inbox->group_id; - if ($member->find()) { - while ($member->fetch()) { - $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id)); - $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id)); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id)); - $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id)); - } - if ($blowLast) { - $cache->delete(common_cache_key('notice_inbox:by_user:' . $member->profile_id . ';last')); - $cache->delete(common_cache_key('notice_inbox:by_user_own:' . $member->profile_id . ';last')); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:' . $member->profile_id . ';last')); - $cache->delete(common_cache_key('user:friends_timeline_own:' . $member->profile_id . ';last')); - } - } - } - } - } - } - $group_inbox->free(); - unset($group_inbox); - } - } - - function blowTagCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - if ($tag->find()) { - while ($tag->fetch()) { - $tag->blowCache($blowLast); - $ck = 'profile:notice_ids_tagged:' . $this->profile_id . ':' . $tag->tag; - - $cache->delete($ck); - if ($blowLast) { - $cache->delete($ck . ';last'); - } - } - } - $tag->free(); - unset($tag); - } - } - - function blowSubsCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $user = new User(); - - $UT = common_config('db','type')=='pgsql'?'"user"':'user'; - $user->query('SELECT id ' . - - "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " . - 'WHERE subscription.subscribed = ' . $this->profile_id); - - while ($user->fetch()) { - $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id)); - $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id)); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:'.$user->id)); - $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id)); - } - if ($blowLast) { - $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last')); - $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last')); - if (empty($this->repeat_of)) { - $cache->delete(common_cache_key('user:friends_timeline:'.$user->id.';last')); - $cache->delete(common_cache_key('user:friends_timeline_own:'.$user->id.';last')); - } - } - } - $user->free(); - unset($user); - } - } - - function blowNoticeCache($blowLast=false) - { - if ($this->is_local) { - $cache = common_memcache(); - if (!empty($cache)) { - $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('profile:notice_ids:'.$this->profile_id.';last')); - } - } - } - } - - function blowRepliesCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $reply = new Reply(); - $reply->notice_id = $this->id; - if ($reply->find()) { - while ($reply->fetch()) { - $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id)); - if ($blowLast) { - $cache->delete(common_cache_key('reply:stream:'.$reply->profile_id.';last')); - } - } - } - $reply->free(); - unset($reply); - } - } - - function blowPublicCache($blowLast=false) - { - if ($this->is_local == Notice::LOCAL_PUBLIC) { - $cache = common_memcache(); - if ($cache) { - $cache->delete(common_cache_key('public')); - if ($blowLast) { - $cache->delete(common_cache_key('public').';last'); - } - } - } - } - - function blowFavesCache($blowLast=false) - { - $cache = common_memcache(); - if ($cache) { - $fave = new Fave(); - $fave->notice_id = $this->id; - if ($fave->find()) { - while ($fave->fetch()) { - $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id)); - $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id)); - if ($blowLast) { - $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id.';last')); - $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id.';last')); - } - } - } - $fave->free(); - unset($fave); - } - } - function getStreamByIds($ids) { $cache = common_memcache(); @@ -999,7 +783,14 @@ class Notice extends Memcached_DataObject $gi->notice_id = $this->id; $gi->created = $this->created; - return $gi->insert(); + $result = $gi->insert(); + + if (!result) { + common_log_db_error($gi, 'INSERT', __FILE__); + throw new ServerException(_('Problem saving group inbox.')); + } + + self::blow('user_group:notice_ids:%d', $gi->group_id); } return true; @@ -1094,7 +885,8 @@ class Notice extends Memcached_DataObject foreach ($recipientIds as $recipientId) { $user = User::staticGet('id', $recipientId); - if ($user) { + if (!empty($user)) { + self::blow('reply:stream:%d', $reply->profile_id); mail_notify_attn($user, $this); } } @@ -1553,4 +1345,104 @@ class Notice extends Memcached_DataObject return $options; } + + function clearReplies() + { + $replyNotice = new Notice(); + $replyNotice->reply_to = $this->id; + + //Null any notices that are replies to this notice + + if ($replyNotice->find()) { + while ($replyNotice->fetch()) { + $orig = clone($replyNotice); + $replyNotice->reply_to = null; + $replyNotice->update($orig); + } + } + + // Reply records + + $reply = new Reply(); + $reply->notice_id = $this->id; + + if ($reply->find()) { + while($reply->fetch()) { + self::blow('reply:stream:%d', $reply->profile_id); + $reply->delete(); + } + } + + $reply->free(); + + return $ids; + } + + function clearRepeats() + { + $repeatNotice = new Notice(); + $repeatNotice->repeat_of = $this->id; + + //Null any notices that are repeats of this notice + + if ($repeatNotice->find()) { + while ($repeatNotice->fetch()) { + $orig = clone($repeatNotice); + $repeatNotice->repeat_of = null; + $repeatNotice->update($orig); + } + } + } + + function clearFaves() + { + $fave = new Fave(); + $fave->notice_id = $this->id; + + if ($fave->find()) { + while ($fave->fetch()) { + self::blow('fave:ids_by_user_own:%d', $fave->user_id); + self::blow('fave:ids_by_user_own:%d;last', $fave->user_id); + self::blow('fave:ids_by_user:%d', $fave->user_id); + self::blow('fave:ids_by_user:%d;last', $fave->user_id); + $fave->delete(); + } + } + + $fave->free(); + } + + function clearTags() + { + $tag = new Notice_tag(); + $tag->notice_id = $this->id; + + if ($tag->find()) { + while ($tag->fetch()) { + self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, common_keyize($tag->tag)); + self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, common_keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s', common_keyize($tag->tag)); + self::blow('notice_tag:notice_ids:%s;last', common_keyize($tag->tag)); + $tag->delete(); + } + } + + $tag->free(); + } + + function clearGroupInboxes() + { + $gi = new Group_inbox(); + + $gi->notice_id = $this->id; + + if ($gi->find()) { + while ($gi->fetch()) { + self::blow('user_group:notice_ids:%d', $gi->group_id); + $gi->delete(); + } + } + + $gi->free(); + } } diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php index 79231f0b0..4fd76e8ea 100644 --- a/classes/Notice_tag.php +++ b/classes/Notice_tag.php @@ -86,13 +86,9 @@ class Notice_tag extends Memcached_DataObject function blowCache($blowLast=false) { - $cache = common_memcache(); - if ($cache) { - $idkey = common_cache_key('notice_tag:notice_ids:' . common_keyize($this->tag)); - $cache->delete($idkey); - if ($blowLast) { - $cache->delete($idkey.';last'); - } + self::blow('notice_tag:notice_ids:%s', common_keyize($this->tag)); + if ($blowLast) { + self::blow('notice_tag:notice_ids:%s;last', common_keyize($this->tag)); } } diff --git a/classes/Status_network.php b/classes/Status_network.php index ef8e1ed43..445f8a5a3 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -48,6 +48,7 @@ class Status_network extends DB_DataObject static $cache = null; static $base = null; + static $wildcard = null; /** * @param string $dbhost @@ -187,7 +188,12 @@ class Status_network extends DB_DataObject $config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname"; - $config['site']['name'] = $sn->sitename; + $config['site']['name'] = $sn->sitename; + $config['site']['nickname'] = $sn->nickname; + + self::$wildcard = $wildcard; + + $config['site']['wildcard'] =& self::$wildcard; if (!empty($sn->hostname)) { $config['site']['server'] = $sn->hostname; @@ -230,4 +236,13 @@ class Status_network extends DB_DataObject exit; } + + function getServerName() + { + if (!empty($this->hostname)) { + return $this->hostname; + } else { + return $this->nickname . '.' . self::$wildcard; + } + } } diff --git a/classes/User.php b/classes/User.php index d6b52be01..6ea975202 100644 --- a/classes/User.php +++ b/classes/User.php @@ -383,7 +383,7 @@ class User extends Memcached_DataObject common_config('site', 'name'), $user->nickname), 'system'); - common_broadcast_notice($notice); + } } diff --git a/js/util.js b/js/util.js index a7339010a..b864867fd 100644 --- a/js/util.js +++ b/js/util.js @@ -495,7 +495,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled'); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -537,7 +537,8 @@ var SN = { // StatusNet NLNU: location.url, NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue)); + + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); }); } @@ -658,6 +659,13 @@ var SN = { // StatusNet } return false; }); + }, + + GetFullYear: function(year, month, day) { + var date = new Date(); + date.setFullYear(year, month, day); + + return date; } }, diff --git a/lib/command.php b/lib/command.php index c0a32e1b1..2a51fd687 100644 --- a/lib/command.php +++ b/lib/command.php @@ -422,7 +422,7 @@ class RepeatCommand extends Command $repeat = $notice->repeat($this->user->id, $channel->source); if ($repeat) { - common_broadcast_notice($repeat); + $channel->output($this->user, sprintf(_('Notice from %s repeated'), $recipient->nickname)); } else { $channel->error($this->user, _('Error repeating notice.')); @@ -492,7 +492,7 @@ class ReplyCommand extends Command } else { $channel->error($this->user, _('Error saving notice.')); } - common_broadcast_notice($notice); + } } diff --git a/lib/default.php b/lib/default.php index d258bbaf4..4f24a6d22 100644 --- a/lib/default.php +++ b/lib/default.php @@ -30,6 +30,8 @@ $default = array('site' => array('name' => 'Just another StatusNet microblog', + 'nickname' => 'statusnet', + 'wildcard' => null, 'server' => $_server, 'theme' => 'default', 'path' => $_path, diff --git a/lib/distribqueuehandler.php b/lib/distribqueuehandler.php new file mode 100644 index 000000000..f458d238d --- /dev/null +++ b/lib/distribqueuehandler.php @@ -0,0 +1,84 @@ +<?php +/* + * StatusNet - the 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/>. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Base class for queue handlers. + * + * As extensions of the Daemon class, each queue handler has the ability + * to launch itself in the background, at which point it'll pass control + * to the configured QueueManager class to poll for updates. + * + * Subclasses must override at least the following methods: + * - transport + * - handle_notice + */ + +class DistribQueueHandler +{ + /** + * Return transport keyword which identifies items this queue handler + * services; must be defined for all subclasses. + * + * Must be 8 characters or less to fit in the queue_item database. + * ex "email", "jabber", "sms", "irc", ... + * + * @return string + */ + + function transport() + { + return 'distrib'; + } + + /** + * Here's the meat of your queue handler -- you're handed a Notice + * object, which you may do as you will with. + * + * If this function indicates failure, a warning will be logged + * and the item is placed back in the queue to be re-run. + * + * @param Notice $notice + * @return boolean true on success, false on failure + */ + function handle($notice) + { + // XXX: do we need to change this for remote users? + + $notice->saveTags(); + + $groups = $notice->saveGroups(); + + $recipients = $notice->saveReplies(); + + $notice->addToInboxes($groups, $recipients); + + $notice->saveUrls(); + + Event::handle('EndNoticeSave', array($notice)); + + // Enqueue for other handlers + + common_enqueue_notice($notice); + + return true; + } +} + diff --git a/lib/iomaster.php b/lib/iomaster.php index 004e92b3e..94abdeefc 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -88,7 +88,7 @@ abstract class IoMaster $sn = new Status_network(); $sn->find(); while ($sn->fetch()) { - $hosts[] = $sn->hostname; + $hosts[] = $sn->getServerName(); } return $hosts; } @@ -102,7 +102,7 @@ abstract class IoMaster */ protected function instantiate($class) { - if (isset($this->singletons[$class])) { + if (is_string($class) && isset($this->singletons[$class])) { // Already instantiated a multi-site-capable handler. // Just let it know it should listen to this site too! $this->singletons[$class]->addSite(common_config('site', 'server')); @@ -129,7 +129,11 @@ abstract class IoMaster protected function getManager($class) { - return call_user_func(array($class, 'get')); + if(is_object($class)){ + return $class; + } else { + return call_user_func(array($class, 'get')); + } } /** @@ -347,7 +351,7 @@ abstract class IoMaster * for per-queue and per-site records. * * @param string $key counter name - * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01' + * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01' */ public function stats($key, $owners=array()) { diff --git a/lib/mailhandler.php b/lib/mailhandler.php index 85be89f18..890f6d5b4 100644 --- a/lib/mailhandler.php +++ b/lib/mailhandler.php @@ -160,7 +160,7 @@ class MailHandler foreach($mediafiles as $mf){ $mf->attachToNotice($notice); } - common_broadcast_notice($notice); + $this->log(LOG_INFO, 'Added notice ' . $notice->id . ' from user ' . $user->nickname); return true; diff --git a/lib/oauthstore.php b/lib/oauthstore.php index df63cc151..b30fb49d5 100644 --- a/lib/oauthstore.php +++ b/lib/oauthstore.php @@ -362,7 +362,7 @@ class StatusNetOAuthDataStore extends OAuthDataStore array('is_local' => Notice::REMOTE_OMB, 'uri' => $omb_notice->getIdentifierURI())); - common_broadcast_notice($notice, true); + } /** diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 8921b02cc..61e28085a 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -170,7 +170,9 @@ abstract class QueueManager extends IoManager { if (isset($this->handlers[$queue])) { $class = $this->handlers[$queue]; - if (class_exists($class)) { + if(is_object($class)) { + return $class; + } else if (class_exists($class)) { return new $class(); } else { common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'"); @@ -206,6 +208,7 @@ abstract class QueueManager extends IoManager $this->connect('plugin', 'PluginQueueHandler'); $this->connect('omb', 'OmbQueueHandler'); $this->connect('ping', 'PingQueueHandler'); + $this->connect('distrib', 'DistribQueueHandler'); if (common_config('sms', 'enabled')) { $this->connect('sms', 'SmsQueueHandler'); } @@ -213,7 +216,7 @@ abstract class QueueManager extends IoManager // XMPP output handlers... $this->connect('jabber', 'JabberQueueHandler'); $this->connect('public', 'PublicQueueHandler'); - + // @fixme this should get an actual queue //$this->connect('confirm', 'XmppConfirmHandler'); @@ -231,7 +234,7 @@ abstract class QueueManager extends IoManager * Only registered transports will be reliably picked up! * * @param string $transport - * @param string $class + * @param string $class class name or object instance * @param string $group */ public function connect($transport, $class, $group='queuedaemon') diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 4bbdeedc2..8f0091a13 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -294,7 +294,26 @@ class StompQueueManager extends QueueManager StatusNet::init($site); } - $item = $this->decode($frame->body); + if (is_numeric($frame->body)) { + $id = intval($frame->body); + $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; + + $notice = Notice::staticGet('id', $id); + if (empty($notice)) { + $this->_log(LOG_WARNING, "Skipping missing $info"); + $this->ack($frame); + $this->commit(); + $this->begin(); + $this->stats('badnotice', $queue); + return false; + } + + $item = $notice; + } else { + // @fixme should we serialize, or json, or what here? + $info = "string posted at {$frame->headers['created']} in queue $queue"; + $item = $frame->body; + } $handler = $this->getHandler($queue); if (!$handler) { diff --git a/lib/util.php b/lib/util.php index 01b159ac1..6c9f6316a 100644 --- a/lib/util.php +++ b/lib/util.php @@ -980,7 +980,7 @@ function common_redirect($url, $code=307) function common_broadcast_notice($notice, $remote=false) { - return common_enqueue_notice($notice); + // DO NOTHING! } // Stick the notice on the queue diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index 299175dd7..985e7c32e 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -101,7 +101,7 @@ class XmppManager extends IoManager $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100); return !is_null($this->conn); } @@ -233,7 +233,7 @@ class XmppManager extends IoManager common_log(LOG_NOTICE, 'XMPP reconnected'); $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', -1); + $this->conn->presence(null, 'available', null, 'available', 100); } diff --git a/plugins/Facebook/facebookaction.php b/plugins/Facebook/facebookaction.php index bf9c037a5..815fee094 100644 --- a/plugins/Facebook/facebookaction.php +++ b/plugins/Facebook/facebookaction.php @@ -397,7 +397,7 @@ class FacebookAction extends Action return; } - common_broadcast_notice($notice); + } diff --git a/plugins/Imap/ImapPlugin.php b/plugins/Imap/ImapPlugin.php index 89a775a16..d1e920b00 100644 --- a/plugins/Imap/ImapPlugin.php +++ b/plugins/Imap/ImapPlugin.php @@ -86,7 +86,7 @@ class ImapPlugin extends Plugin } } - function onStartIoManagerClasses(&$classes) + function onStartQueueDaemonIoManagers(&$classes) { $classes[] = new ImapManager($this); } diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index d426fc282..5c913836d 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -414,7 +414,15 @@ class MobileProfilePlugin extends WAP20Plugin return $proto.'://'.$serverpart.'/'.$pathpart.$relative; } -} - -?> + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'MobileProfile', + 'version' => STATUSNET_VERSION, + 'author' => 'Sarven Capadisli', + 'homepage' => 'http://status.net/wiki/Plugin:MobileProfile', + 'rawdescription' => + _m('XHTML MobileProfile output for supporting user agents.')); + return true; + } +} diff --git a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php index c59fcca89..14d1608d3 100644 --- a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php +++ b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php @@ -46,8 +46,9 @@ class PoweredByStatusNetPlugin extends Plugin function onEndAddressData($action) { $action->elementStart('span', 'poweredby'); - $action->text(_('powered by')); - $action->element('a', array('href' => 'http://status.net/'), 'StatusNet'); + $action->raw(sprintf(_m('powered by %s'), + sprintf('<a href="http://status.net/">%s</a>', + _m('StatusNet')))); $action->elementEnd('span'); return true; diff --git a/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po new file mode 100644 index 000000000..bd39124ef --- /dev/null +++ b/plugins/PoweredByStatusNet/locale/PoweredByStatusNet.po @@ -0,0 +1,32 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-01-22 15:03-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: PoweredByStatusNetPlugin.php:49 +#, php-format +msgid "powered by %s" +msgstr "" + +#: PoweredByStatusNetPlugin.php:51 +msgid "StatusNet" +msgstr "" + +#: PoweredByStatusNetPlugin.php:64 +msgid "" +"Outputs powered by <a href=\"http://status.net/\">StatusNet</a> after site " +"name." +msgstr "" diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index 56f869354..31e7c2ae6 100644 --- a/plugins/Realtime/realtimeupdate.css +++ b/plugins/Realtime/realtimeupdate.css @@ -18,7 +18,8 @@ display:none; } .realtime-popup #form_notice label[for=notice_data-attach], -.realtime-popup #form_notice #notice_data-attach { +.realtime-popup #form_notice #notice_data-attach, +.realtime-popup #form_notice label[for=notice_data-geo] { top:0; } diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 57b3c1c99..c7f57ffc7 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -20,7 +20,8 @@ * @category Plugin * @package StatusNet * @author Zach Copley <zach@status.net> - * @copyright 2009 Control Yourself, Inc. + * @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/ */ @@ -41,6 +42,7 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9'); * @category Plugin * @package StatusNet * @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://twitter.com/ @@ -72,6 +74,27 @@ class TwitterBridgePlugin extends Plugin $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); $m->connect('settings/twitter', array('action' => 'twittersettings')); + $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); + + return true; + } + + /* + * Add a login tab for 'Sign in with Twitter' + * + * @param Action &action the current action + * + * @return void + */ + function onEndLoginGroupNav(&$action) + { + + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('twitterlogin'), + _('Twitter'), + _('Login or register using Twitter'), + 'twitterlogin' === $action_name); return true; } @@ -108,6 +131,7 @@ class TwitterBridgePlugin extends Plugin switch ($cls) { case 'TwittersettingsAction': case 'TwitterauthorizationAction': + case 'TwitterloginAction': include_once INSTALLDIR . '/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 4af2f0394..b2657ff61 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -19,10 +19,11 @@ * 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 TwitterauthorizationAction + * @category Plugin * @package StatusNet - * @author Zach Copely <zach@status.net> - * @copyright 2009 StatusNet, Inc. + * @author Zach Copley <zach@status.net> + * @author Julien C <chaumond@gmail.com> + * @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/ */ @@ -41,15 +42,21 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * (Foreign_link) between the StatusNet user and Twitter user and stores the * access token and secret in the link. * - * @category Twitter + * @category Plugin * @package StatusNet * @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/ * */ class TwitterauthorizationAction extends Action { + var $twuid = null; + var $tw_fields = null; + var $access_token = null; + var $signin = null; + /** * Initialize class members. Looks for 'oauth_token' parameter. * @@ -61,6 +68,7 @@ class TwitterauthorizationAction extends Action { parent::prepare($args); + $this->signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); return true; @@ -77,28 +85,61 @@ class TwitterauthorizationAction extends Action { parent::handle($args); - if (!common_logged_in()) { - $this->clientError(_m('Not logged in.'), 403); + if (common_logged_in()) { + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + + // If there's already a foreign link record, it means we already + // have an access token, and this is unecessary. So go back. + + if (isset($flink)) { + common_redirect(common_local_url('twittersettings')); + } } - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + if ($_SERVER['REQUEST_METHOD'] == 'POST') { - // If there's already a foreign link record, it means we already - // have an access token, and this is unecessary. So go back. + // User was not logged in to StatusNet before - if (isset($flink)) { - common_redirect(common_local_url('twittersettings')); - } + $this->twuid = $this->trimmed('twuid'); + + $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), + 'fullname' => $this->trimmed('tw_fields_fullname')); + + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); + + $token = $this->trimmed('token'); - // $this->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. Try again, please.')); + return; + } - if (empty($this->oauth_token)) { - $this->authorizeRequestToken(); + if ($this->arg('create')) { + if (!$this->boolean('license')) { + $this->showForm(_('You can\'t register if you don\'t agree to the license.'), + $this->trimmed('newname')); + return; + } + $this->createNewUser(); + } else if ($this->arg('connect')) { + $this->connectNewUser(); + } else { + common_debug('Twitter Connect Plugin - ' . + print_r($this->args, true)); + $this->showForm(_('Something weird happened.'), + $this->trimmed('newname')); + } } else { - $this->saveAccessToken(); + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + + if (empty($this->oauth_token)) { + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } } } @@ -123,7 +164,7 @@ class TwitterauthorizationAction extends Action $_SESSION['twitter_request_token'] = $req_tok->key; $_SESSION['twitter_request_token_secret'] = $req_tok->secret; - $auth_link = $client->getAuthorizeLink($req_tok); + $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', @@ -150,6 +191,8 @@ class TwitterauthorizationAction extends Action $this->serverError(_m('Couldn\'t link your Twitter account.')); } + $twitter_user = null; + try { $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], @@ -165,40 +208,54 @@ class TwitterauthorizationAction extends Action $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s', + $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s', $e->getCode(), $e->getMessage()); $this->serverError(_m('Couldn\'t link your Twitter account.')); } - // Save the access token and Twitter user info + if (common_logged_in()) { + + // Save the access token and Twitter user info + + $user = common_current_user(); + $this->saveForeignLink($user->id, $twitter_user->id, $atok); + save_twitter_user($twitter_user->id, $twitter_user->name); + + } else { - $this->saveForeignLink($atok, $twitter_user); + $this->twuid = $twitter_user->id; + $this->tw_fields = array("screen_name" => $twitter_user->screen_name, + "name" => $twitter_user->name); + $this->access_token = $atok; + $this->tryLogin(); + } // Clean up the the mess we made in the session unset($_SESSION['twitter_request_token']); unset($_SESSION['twitter_request_token_secret']); - common_redirect(common_local_url('twittersettings')); + if (common_logged_in()) { + common_redirect(common_local_url('twittersettings')); + } } /** * Saves a Foreign_link between Twitter user and local user, * which includes the access token and secret. * - * @param OAuthToken $access_token the access token to save - * @param mixed $twitter_user twitter API user object + * @param int $user_id StatusNet user ID + * @param int $twuid Twitter user ID + * @param OAuthToken $token the access token to save * * @return nothing */ - function saveForeignLink($access_token, $twitter_user) + function saveForeignLink($user_id, $twuid, $access_token) { - $user = common_current_user(); - $flink = new Foreign_link(); - $flink->user_id = $user->id; - $flink->foreign_id = $twitter_user->id; + $flink->user_id = $user_id; + $flink->foreign_id = $twuid; $flink->service = TWITTER_SERVICE; $creds = TwitterOAuthClient::packToken($access_token); @@ -214,10 +271,325 @@ class TwitterauthorizationAction extends Action if (empty($flink_id)) { common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $this->serverError(_('Couldn\'t link your Twitter account.')); + } + + return $flink_id; + } + + function showPageNotice() + { + if ($this->error) { + $this->element('div', array('class' => 'error'), $this->error); + } else { + $this->element('div', 'instructions', + sprintf(_('This is the first time you\'ve logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); + } + } + + function title() + { + return _('Twitter Account Setup'); + } + + function showForm($error=null, $username=null) + { + $this->error = $error; + $this->username = $username; + + $this->showPage(); + } + + function showPage() + { + parent::showPage(); + } + + function showContent() + { + if (!empty($this->message_text)) { + $this->element('p', null, $this->message); + return; + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_twitter_connect', + 'class' => 'form_settings', + 'action' => common_local_url('twitterauthorization'))); + $this->elementStart('fieldset', array('id' => 'settings_twitter_connect_options')); + $this->element('legend', null, _('Connection options')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->element('input', array('type' => 'checkbox', + 'id' => 'license', + 'class' => 'checkbox', + 'name' => 'license', + 'value' => 'true')); + $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); + $this->text(_('My text and files are available under ')); + $this->element('a', array('href' => common_config('license', 'url')), + common_config('license', 'title')); + $this->text(_(' except this private data: password, email address, IM address, phone number.')); + $this->elementEnd('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->hidden('access_token_key', $this->access_token->key); + $this->hidden('access_token_secret', $this->access_token->secret); + $this->hidden('twuid', $this->twuid); + $this->hidden('tw_fields_screen_name', $this->tw_fields['screen_name']); + $this->hidden('tw_fields_name', $this->tw_fields['name']); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, + _('Create new account')); + $this->element('p', null, + _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('newname', _('New nickname'), + ($this->username) ? $this->username : '', + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('create', _('Create')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + $this->element('legend', null, + _('Connect existing account')); + $this->element('p', null, + _('If you already have an account, login with your username and password to connect it to your Twitter account.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); + + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function message($msg) + { + $this->message_text = $msg; + $this->showPage(); + } + + function createNewUser() + { + if (common_config('site', 'closed')) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = null; + + if (common_config('site', 'inviteonly')) { + $code = $_SESSION['invitecode']; + if (empty($code)) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = Invitation::staticGet($code); + + if (empty($invite)) { + $this->clientError(_('Not a valid invitation code.')); + return; + } + } + + $nickname = $this->trimmed('newname'); + + if (!Validate::string($nickname, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); + return; + } + + if (!User::allowed_nickname($nickname)) { + $this->showForm(_('Nickname not allowed.')); + return; + } + + if (User::staticGet('nickname', $nickname)) { + $this->showForm(_('Nickname already in use. Try another one.')); + return; + } + + $fullname = trim($this->tw_fields['name']); + + $args = array('nickname' => $nickname, 'fullname' => $fullname); + + if (!empty($invite)) { + $args['code'] = $invite->code; + } + + $user = User::register($args); + + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_set_user($user); + common_real_login(true); + + common_debug('TwitterBridge Plugin - ' . + "Registered new user $user->id from Twitter user $this->twuid"); + + common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), + 303); + } + + function connectNewUser() + { + $nickname = $this->trimmed('nickname'); + $password = $this->trimmed('password'); + + if (!common_check_user($nickname, $password)) { + $this->showForm(_('Invalid username or password.')); + return; + } + + $user = User::staticGet('nickname', $nickname); + + if (!empty($user)) { + common_debug('TwitterBridge Plugin - ' . + "Legit user to connect to Twitter: $nickname"); + } + + $result = $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); + + save_twitter_user($this->twuid, $this->tw_fields['screen_name']); + + if (!$result) { + $this->serverError(_('Error connecting user to Twitter.')); + return; + } + + common_debug('TwitterBridge Plugin - ' . + "Connected Twitter user $this->twuid to local user $user->id"); + + common_set_user($user); + common_real_login(true); + + $this->goHome($user->nickname); + } + + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->twuid); + + if (empty($result)) { + $this->serverError(_('Error connecting user to Twitter.')); + return; } - save_twitter_user($twitter_user->id, $twitter_user->screen_name); + common_debug('TwitterBridge Plugin - ' . + "Connected Twitter user $this->twuid to local user $user->id"); + + // Return to Twitter connection settings tab + common_redirect(common_local_url('twittersettings'), 303); + } + + function tryLogin() + { + common_debug('TwitterBridge Plugin - ' . + "Trying login for Twitter user $this->twuid."); + + $flink = Foreign_link::getByForeignID($this->twuid, + TWITTER_SERVICE); + + if (!empty($flink)) { + $user = $flink->getUser(); + + if (!empty($user)) { + + common_debug('TwitterBridge Plugin - ' . + "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); + + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); + } + + } else { + + common_debug('TwitterBridge Plugin - ' . + "No flink found for twuid: $this->twuid - new user"); + + $this->showForm(null, $this->bestNewNickname()); + } + } + + function goHome($nickname) + { + $url = common_get_returnto(); + if ($url) { + // We don't have to return to it again + common_set_returnto(null); + } else { + $url = common_local_url('all', + array('nickname' => + $nickname)); + } + + common_redirect($url, 303); + } + + function bestNewNickname() + { + if (!empty($this->tw_fields['name'])) { + $nickname = $this->nicknamize($this->tw_fields['name']); + if ($this->isNewNickname($nickname)) { + return $nickname; + } + } + + return null; + } + + // Given a string, try to make it work as a nickname + + function nicknamize($str) + { + $str = preg_replace('/\W/', '', $str); + $str = str_replace(array('-', '_'), '', $str); + return strtolower($str); + } + + function isNewNickname($str) + { + if (!Validate::string($str, array('min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT))) { + return false; + } + if (!User::allowed_nickname($str)) { + return false; + } + if (User::staticGet('nickname', $str)) { + return false; + } + return true; } } diff --git a/plugins/TwitterBridge/twitterlogin.php b/plugins/TwitterBridge/twitterlogin.php new file mode 100644 index 000000000..79421fb27 --- /dev/null +++ b/plugins/TwitterBridge/twitterlogin.php @@ -0,0 +1,97 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * 'Sign in with Twitter' login page + * + * 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 Login + * @package StatusNet + * @author Julien Chaumond <chaumond@gmail.com> + * @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') && !defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; + +/** + * Page for logging in with Twitter + * + * @category Login + * @package StatusNet + * @author Julien Chaumond <chaumond@gmail.com> + * @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/ + * + * @see SettingsAction + */ + +class TwitterloginAction extends Action +{ + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + $this->clientError(_('Already logged in.')); + } + + $this->showPage(); + } + + function title() + { + return _('Twitter Login'); + } + + function getInstructions() + { + return _('Login with your Twitter account'); + } + + function showPageNotice() + { + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); + } + + function showContent() + { + $this->elementStart('a', array('href' => common_local_url('twitterauthorization', + null, + array('signin' => true)))); + $this->element('img', array('src' => common_path('plugins/TwitterBridge/Sign-in-with-Twitter-lighter.png'), + 'alt' => 'Sign in with Twitter')); + $this->elementEnd('a'); + } + + function showLocalNav() + { + $nav = new LoginGroupNav($this); + $nav->show(); + } +} diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index bad2b74ca..277e7ab40 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -45,6 +45,7 @@ class TwitterOAuthClient extends OAuthClient { public static $requestTokenURL = 'https://twitter.com/oauth/request_token'; public static $authorizeURL = 'https://twitter.com/oauth/authorize'; + public static $signinUrl = 'https://twitter.com/oauth/authenticate'; public static $accessTokenURL = 'https://twitter.com/oauth/access_token'; /** @@ -97,9 +98,11 @@ class TwitterOAuthClient extends OAuthClient * * @return the link */ - function getAuthorizeLink($request_token) + function getAuthorizeLink($request_token, $signin = false) { - return parent::getAuthorizeLink(self::$authorizeURL, + $url = ($signin) ? self::$signinUrl : self::$authorizeURL; + + return parent::getAuthorizeLink($url, $request_token, common_local_url('twitterauthorization')); } diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php index bc9a636a1..0137060e9 100644 --- a/plugins/TwitterBridge/twittersettings.php +++ b/plugins/TwitterBridge/twittersettings.php @@ -121,8 +121,35 @@ class TwittersettingsAction extends ConnectSettingsAction $this->elementEnd('p'); $this->element('p', 'form_note', _m('Connected Twitter account')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + + $this->element('legend', null, _m('Disconnect my account from Twitter')); - $this->submit('remove', _m('Remove')); + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_m('Disconnecting your Twitter ' . + 'could make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _m('set a password')); + + $this->text(_m(' first.')); + $this->elementEnd('p'); + } else { + + $note = _m('Keep your %1$s account but disconnect from Twitter. ' . + 'You can use your %1$s password to log in.'); + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site)); + + $this->submit('disconnect', _m('Disconnect')); + } $this->elementEnd('fieldset'); @@ -205,7 +232,7 @@ class TwittersettingsAction extends ConnectSettingsAction if ($this->arg('save')) { $this->savePreferences(); - } else if ($this->arg('remove')) { + } else if ($this->arg('disconnect')) { $this->removeTwitterAccount(); } else { $this->showForm(_m('Unexpected form submission.')); @@ -231,7 +258,7 @@ class TwittersettingsAction extends ConnectSettingsAction return; } - $this->showForm(_m('Twitter account removed.'), true); + $this->showForm(_m('Twitter account disconnected.'), true); } /** diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php index bc4251cf7..86b39160b 100644 --- a/plugins/UserFlag/User_flag_profile.php +++ b/plugins/UserFlag/User_flag_profile.php @@ -86,7 +86,7 @@ class User_flag_profile extends Memcached_DataObject function keys() { - return array('profile_id' => 'N', 'user_id' => 'N'); + return array('profile_id' => 'K', 'user_id' => 'K'); } /** @@ -130,6 +130,15 @@ class User_flag_profile extends Memcached_DataObject return !empty($ufp); } + /** + * Create a new flag + * + * @param integer $user_id ID of user who's flagging + * @param integer $profile_id ID of profile being flagged + * + * @return boolean success flag + */ + static function create($user_id, $profile_id) { $ufp = new User_flag_profile(); diff --git a/theme/README b/theme/README index 266a89fdf..e154a723c 100644 --- a/theme/README +++ b/theme/README @@ -2,37 +2,46 @@ * * @package StatusNet * @author Sarven Capadisli <csarven@status.net> - * @copyright 2009 StatusNet, Inc. + * @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/ */ -Location of key paths and files under theme/: +== Location of key paths and files == +<pre><nowiki> +base/css/ +base/css/display.css #layout, typography rules +base/images/ #common icons, illustrations +base/images/icons/icons-01.png #main icons file (combined into a single file) -./base/css/ -./base/css/display.css -./base/images/ +default/css/ +default/css/display.css #imports the base stylesheet for layout and adds background images and colour rules +default/logo.png #default site logo for this theme +default/mobilelogo.png #default logo for the mobile output +default/default-avatar-mini.png #24x24 default avatar for minilists +default/default-avatar-stream.png #48x48 default avatar for notice timelines +default/default-avatar-profile.png #96x96 default avatar for the profile page +</nowiki></pre> -./default/css/ -./default/css/display.css -./default/images/ -./base/display.css contains layout, typography rules: -Only alter this file if you want to change the layout of the site. Please note that, any updates to this in future statusnet releases may not be compatible with your version. +== How to create your own theme == -./default/css/display.css contains only the background images and colour rules: -This file is a good basis for creating your own theme. -Let's create a theme: +You probably want to do one of the following: -1. To start off, copy over the default theme: -cp -r default mytheme -2. Edit your mytheme stylesheet: -nano mytheme/css/display.css +* If you just want to change the text, link, background, content, sidebar colours, background image: +** Do this from the Admin->Design settings (recommended!). You could also create a directory and a file structure like the default theme, search and replace with your own values. This is more work, but, you can do this if you plan to make additional *minimal* changes. -a) Search and replace your colours and background images, or -b) Create your own layout either importing a separate stylesheet (e.g., change to @import url(base.css);) or simply place it before the rest of the rules. -4. Set /config.php to load 'mytheme': -$config['site']['theme'] = 'mytheme'; +* If you want to change the background images and colours: +# Create a directory and a file structure like the default theme. +# Have your stylesheet import base/css/display.css and add your own styles below. It is okay to add *minimal* changes here. + + +* If you want to create a different layout, typography, background images and colours: +** Create your own theme directory (base or default) with stylesheets and images like. + + +Finally, enable your theme by selecting it from the Admin->Design interface. You can set site's logo from here as well. + diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 84e9426c7..65dd15990 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1039,12 +1039,13 @@ overflow:visible; #showstream .notice div.entry-content { margin-left:0; } -#shownotice .notice .entry-title, -#shownotice .notice div.entry-content { -margin-left:110px; -} #shownotice .notice .entry-title { +margin-left:110px; font-size:2.2em; +min-height:123px; +} +#shownotice .notice div.entry-content { +margin-left:0; } .notice p.entry-content { |