diff --git a/actions/apiaccountupdateprofile.php b/actions/apiaccountupdateprofile.php index fd4384a25c..9b371ea957 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 d9d4820c0e..128c881e25 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 f8bf7cf874..31c9b20ce2 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 a4ed87bb62..78480ababb 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 b75523498b..e112496bc1 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 086dba1c9d..26b27d2b58 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 33645a3e8b..43610dddbb 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 38b10db048..0966697e21 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)); + // Clear related records - //Null any notices that are repeats of this notice - //XXX: probably need to uncache these, too + $this->clearReplies(); + $this->clearRepeats(); + $this->clearFaves(); + $this->clearTags(); + $this->clearGroupInboxes(); - $this->query(sprintf("UPDATE notice set repeat_of = null WHERE repeat_of = %d", $this->id)); + // NOTE: we don't clear inboxes + // NOTE: we don't clear queue items - $related = array('Reply', - 'Fave', - 'Notice_tag', - 'Group_inbox', - 'Queue_item'); - - 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,29 +320,45 @@ class Notice extends Memcached_DataObject } } - // 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)); } # 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->blowCaches(); + $qm = QueueManager::get(); + + $qm->enqueue($notice, 'distrib'); return $notice; } + function blowOnInsert() + { + self::blow('profile:notice_ids:%d', $this->profile_id); + self::blow('public'); + + if ($this->conversation != $this->id) { + self::blow('notice:conversation_ids:%d', $this->conversation); + } + + if (!empty($this->repeat_of)) { + self::blow('notice:repeats:%d', $this->repeat_of); + } + + $original = Notice::staticGet('id', $this->repeat_of); + + if (!empty($original)) { + $originalUser = User::staticGet('id', $original->profile_id); + if (!empty($originalUser)) { + self::blow('user:repeats_of_me:%d', $originalUser->id); + } + } + + $profile = Profile::staticGet($this->profile_id); + $profile->blowNoticeCount(); + } + /** save all urls in the notice to the db * * follow redirects and save all available file information @@ -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 79231f0b0c..4fd76e8ea8 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 ef8e1ed431..445f8a5a3c 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 d6b52be017..6ea975202d 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 a7339010a7..b864867fd1 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 c0a32e1b1a..2a51fd6872 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 d258bbaf48..4f24a6d22e 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 0000000000..f458d238da --- /dev/null +++ b/lib/distribqueuehandler.php @@ -0,0 +1,84 @@ +. + */ + +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 004e92b3ee..94abdeefcc 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 85be89f186..890f6d5b49 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 df63cc1512..b30fb49d57 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 8921b02cc8..61e28085ab 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 4bbdeedc20..8f0091a138 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 01b159ac15..6c9f6316ad 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 299175dd7d..985e7c32e4 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 bf9c037a57..815fee094c 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 89a775a16a..d1e920b009 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 d426fc282b..5c913836dc 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 c59fcca890..14d1608d3c 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('%s', + _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 0000000000..bd39124efe --- /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 , 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 \n" +"Language-Team: LANGUAGE \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 StatusNet after site " +"name." +msgstr "" diff --git a/plugins/Realtime/realtimeupdate.css b/plugins/Realtime/realtimeupdate.css index 56f869354d..31e7c2ae66 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 57b3c1c995..c7f57ffc77 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -20,7 +20,8 @@ * @category Plugin * @package StatusNet * @author Zach Copley - * @copyright 2009 Control Yourself, Inc. + * @author Julien C + * @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 + * @author Julien C * @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 4af2f03941..b2657ff61f 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 . * - * @category TwitterauthorizationAction + * @category Plugin * @package StatusNet - * @author Zach Copely - * @copyright 2009 StatusNet, Inc. + * @author Zach Copley + * @author Julien C + * @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 + * @author Julien C * @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->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process + $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), + 'fullname' => $this->trimmed('tw_fields_fullname')); - if (empty($this->oauth_token)) { - $this->authorizeRequestToken(); + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. Try again, please.')); + return; + } + + 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()) { - $this->saveForeignLink($atok, $twitter_user); + // 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->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.')); } - save_twitter_user($twitter_user->id, $twitter_user->screen_name); + 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; + } + + 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 0000000000..79421fb27d --- /dev/null +++ b/plugins/TwitterBridge/twitterlogin.php @@ -0,0 +1,97 @@ +. + * + * @category Login + * @package StatusNet + * @author Julien Chaumond + * @author Zach Copley + * @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 + * @author Zach Copley + * @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 bad2b74ca3..277e7ab409 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 bc9a636a15..0137060e9c 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->submit('remove', _m('Remove')); + $this->elementStart('fieldset'); + + $this->element('legend', null, _m('Disconnect my account from Twitter')); + + 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 bc4251cf7f..86b39160bf 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 266a89fdf9..e154a723c4 100644 --- a/theme/README +++ b/theme/README @@ -2,37 +2,46 @@ * * @package StatusNet * @author Sarven Capadisli - * @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 == +

+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
+
-./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 84e9426c77..65dd159900 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 {