From a116cde1a401b1959515b8e6b512c41603387031 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Feb 2010 20:11:48 +0000 Subject: [PATCH 01/21] OStatus: fix for low-level remote subscribe --- plugins/OStatus/classes/Ostatus_profile.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index b750e18839..9b6ef2f163 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -225,7 +225,7 @@ class Ostatus_profile extends Memcached_DataObject try { $local = $munger->profile(); - if ($entity->isGroup()) { + if ($profile->isGroup()) { $group = new User_group(); $group->nickname = $local->nickname . '@remote'; // @fixme $group->fullname = $local->fullname; @@ -245,17 +245,17 @@ class Ostatus_profile extends Memcached_DataObject $profile->profile_id = $local->id; } - $profile->created = sql_common_date(); - $profile->lastupdate = sql_common_date(); + $profile->created = common_sql_now(); + $profile->lastupdate = common_sql_now(); $result = $profile->insert(); if (empty($result)) { throw new FeedDBException($profile); } - $entity->query('COMMIT'); + $profile->query('COMMIT'); } catch (FeedDBException $e) { common_log_db_error($e->obj, 'INSERT', __FILE__); - $entity->query('ROLLBACK'); + $profile->query('ROLLBACK'); return false; } @@ -269,7 +269,7 @@ class Ostatus_profile extends Memcached_DataObject } } - return $entity; + return $profile; } /** From 440ab9039178bfc58c55316eb9ba2e19551bd12b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Feb 2010 22:03:24 +0000 Subject: [PATCH 02/21] OStatus: fix up some recent regressions in subscription setup; fix state checks and verification token, and avatar save on setup. Needs updates for new atom code next... --- plugins/OStatus/actions/pushhub.php | 6 +- plugins/OStatus/classes/Ostatus_profile.php | 83 ++++++++++++++++----- plugins/OStatus/lib/feedmunger.php | 9 ++- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php index 901c18f702..13ec09d528 100644 --- a/plugins/OStatus/actions/pushhub.php +++ b/plugins/OStatus/actions/pushhub.php @@ -44,7 +44,7 @@ class PushHubAction extends Action // PHP converts '.'s in incoming var names to '_'s. // It also merges multiple values, which'll break hub.verify and hub.topic for publishing // @fixme handle multiple args - $arg = str_replace('.', '_', $arg); + $arg = str_replace('hub.', 'hub_', $arg); return parent::arg($arg, $def); } @@ -96,7 +96,11 @@ class PushHubAction extends Action $sub = new HubSub(); $sub->topic = $feed; $sub->callback = $callback; + $sub->verify_token = $this->arg('hub.verify_token', null); $sub->secret = $this->arg('hub.secret', null); + if (strlen($sub->secret) > 200) { + throw new ClientException("hub.secret must be no longer than 200 chars", 400); + } $sub->setLease(intval($this->arg('hub.lease_seconds'))); // @fixme check for feeds we don't manage diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 9b6ef2f163..243211c31f 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -262,7 +262,7 @@ class Ostatus_profile extends Memcached_DataObject $avatar = $munger->getAvatar(); if ($avatar) { try { - $this->updateAvatar($avatar); + $profile->updateAvatar($avatar); } catch (Exception $e) { common_log(LOG_ERR, "Exception setting OStatus avatar: " . $e->getMessage()); @@ -283,8 +283,10 @@ class Ostatus_profile extends Memcached_DataObject // ripped from oauthstore.php (for old OMB client) $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar'); copy($url, $temp_filename); - $imagefile = new ImageFile($profile->id, $temp_filename); - $filename = Avatar::filename($profile->id, + + // @fixme should we be using different ids? + $imagefile = new ImageFile($this->id, $temp_filename); + $filename = Avatar::filename($this->id, image_type_to_extension($imagefile->type), null, common_timestamp()); @@ -376,17 +378,56 @@ class Ostatus_profile extends Memcached_DataObject * The hub will later send us a confirmation POST to /main/push/callback. * * @return bool true on success, false on failure + * @throws ServerException if feed state is not valid */ public function subscribe($mode='subscribe') { - if (common_config('feedsub', 'nohub')) { - // Fake it! We're just testing remote feeds w/o hubs. - return true; + if ($this->sub_state != '') { + throw new ServerException("Attempting to start PuSH subscription to feed in state $this->sub_state"); } - // @fixme use the verification token - #$token = md5(mt_rand() . ':' . $this->feeduri); - #$this->verify_token = $token; - #$this->update(); // @fixme + if (empty($this->huburi)) { + if (common_config('feedsub', 'nohub')) { + // Fake it! We're just testing remote feeds w/o hubs. + return true; + } else { + throw new ServerException("Attempting to start PuSH subscription for feed with no hub"); + } + } + + return $this->doSubscribe('subscribe'); + } + + /** + * Send a PuSH unsubscription request to the hub for this feed. + * The hub will later send us a confirmation POST to /main/push/callback. + * + * @return bool true on success, false on failure + * @throws ServerException if feed state is not valid + */ + public function unsubscribe() { + if ($this->sub_state != 'active') { + throw new ServerException("Attempting to end PuSH subscription to feed in state $this->sub_state"); + } + if (empty($this->huburi)) { + if (common_config('feedsub', 'nohub')) { + // Fake it! We're just testing remote feeds w/o hubs. + return true; + } else { + throw new ServerException("Attempting to end PuSH subscription for feed with no hub"); + } + } + + return $this->doSubscribe('unsubscribe'); + } + + protected function doSubscribe($mode) + { + $orig = clone($this); + $this->verify_token = md5(mt_rand() . ':' . $this->feeduri); + $this->sub_state = $mode; + $this->update($orig); + unset($orig); + try { $callback = common_local_url('pushcallback', array('feed' => $this->id)); $headers = array('Content-Type: application/x-www-form-urlencoded'); @@ -416,6 +457,13 @@ class Ostatus_profile extends Memcached_DataObject } catch (Exception $e) { // wtf! common_log(LOG_ERR, __METHOD__ . ": error \"{$e->getMessage()}\" hitting hub $this->huburi subscribing to $this->feeduri"); + + $orig = clone($this); + $this->verify_token = null; + $this->sub_state = null; + $this->update($orig); + unset($orig); + return false; } } @@ -460,16 +508,6 @@ class Ostatus_profile extends Memcached_DataObject return $this->update($original); } - /** - * Send a PuSH unsubscription request to the hub for this feed. - * The hub will later send us a confirmation POST to /main/push/callback. - * - * @return bool true on success, false on failure - */ - public function unsubscribe() { - return $this->subscribe('unsubscribe'); - } - /** * Send an Activity Streams notification to the remote Salmon endpoint, * if so configured. @@ -568,6 +606,11 @@ class Ostatus_profile extends Memcached_DataObject { common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml"); + if ($this->sub_state != 'active') { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed $this->feeduri (in state '$this->sub_state')"); + return; + } + if ($this->secret) { if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { $their_hmac = strtolower($matches[1]); diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index c895b6ce24..e8c46de90e 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -258,11 +258,12 @@ class FeedMunger { // hack hack hack // should get profile for this entry's author... - $remote = Ostatus_profile::staticGet('feeduri', $this->getSelfLink()); - if ($feed) { - return $feed->profile_id; + $feeduri = $this->getSelfLink(); + $remote = Ostatus_profile::staticGet('feeduri', $feeduri); + if ($remote) { + return $remote->profile_id; } else { - throw new Exception("Can't find feed profile"); + throw new Exception("Can't find feed profile for $feeduri"); } } From 880acb05b0f51f873e72a7b4d322cefafe7e850c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Feb 2010 22:04:57 +0000 Subject: [PATCH 03/21] OStatus: temporary output mode hack for apitimelineuser until PuSH feed generation is updated to use the shared code instead of output buffering --- actions/apitimelineuser.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 24752e45fd..9f7ec4c236 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -196,7 +196,8 @@ class ApiTimelineUserAction extends ApiBareAuthAction $atom->addEntryFromNotices($this->notices); - $this->raw($atom->getString()); + #$this->raw($atom->getString()); + print $atom->getString(); // temporary for output buffering break; case 'json': From 014a32e6b873291bcd289a1ed25759a7a29221d7 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Feb 2010 23:04:39 +0000 Subject: [PATCH 04/21] OStatus: check only direct children in ActivityUtil::child; fixes pulling actor's info when we wanted post info --- plugins/OStatus/lib/activity.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php index 3d02e35848..5b1c4fa8fa 100644 --- a/plugins/OStatus/lib/activity.php +++ b/plugins/OStatus/lib/activity.php @@ -106,12 +106,16 @@ class ActivityUtils static function child($element, $tag, $namespace=self::ATOM) { - $els = $element->getElementsByTagnameNS($namespace, $tag); - + $els = $element->childNodes; if (empty($els) || $els->length == 0) { return null; } else { - return $els->item(0); + for ($i = 0; $i < $els->length; $i++) { + $el = $els->item($i); + if ($el->localName == $tag && $el->namespaceURI == $namespace) { + return $el; + } + } } } From 4a139d1cc861272b45812969878fa62f81ed9cfe Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Feb 2010 23:31:11 +0000 Subject: [PATCH 05/21] OStatus: migrated notice parsing to use Activity helper classes; on the way to killing FeedMunger --- plugins/OStatus/actions/pushcallback.php | 3 + plugins/OStatus/classes/Ostatus_profile.php | 240 ++++++++++++++------ 2 files changed, 173 insertions(+), 70 deletions(-) diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php index 2601a377a0..388c8f9c3d 100644 --- a/plugins/OStatus/actions/pushcallback.php +++ b/plugins/OStatus/actions/pushcallback.php @@ -59,6 +59,9 @@ class PushCallbackAction extends Action } $post = file_get_contents('php://input'); + + // @fixme Queue this to a background process; we should return + // as quickly as possible from a distribution POST. $profile->postUpdates($post, $hmac); } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 243211c31f..4376d64c66 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -599,89 +599,189 @@ class Ostatus_profile extends Memcached_DataObject * Currently assumes that all items in the feed are new, * coming from a PuSH hub. * - * @param string $xml source of Atom or RSS feed + * @param string $post source of Atom or RSS feed * @param string $hmac X-Hub-Signature header, if present */ - public function postUpdates($xml, $hmac) + public function postUpdates($post, $hmac) { - common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $xml"); + common_log(LOG_INFO, __METHOD__ . ": packet for \"$this->feeduri\"! $hmac $post"); if ($this->sub_state != 'active') { common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH for inactive feed $this->feeduri (in state '$this->sub_state')"); return; } - if ($this->secret) { - if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { - $their_hmac = strtolower($matches[1]); - $our_hmac = hash_hmac('sha1', $xml, $this->secret); - if ($their_hmac !== $our_hmac) { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac"); - return; - } - } else { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'"); - return; - } - } else if ($hmac) { - common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'"); + if ($post === '') { + common_log(LOG_ERR, __METHOD__ . ": ignoring empty post"); return; } - require_once "XML/Feed/Parser.php"; - $feed = new XML_Feed_Parser($xml, false, false, true); - $munger = new FeedMunger($feed); - - $hits = 0; - foreach ($feed as $index => $entry) { - // @fixme this might sort in wrong order if we get multiple updates - - $notice = $munger->notice($index); - - // Double-check for oldies - // @fixme this could explode horribly for multiple feeds on a blog. sigh - - $dupe = Notice::staticGet('uri', $notice->uri); - - if (!empty($dupe)) { - common_log(LOG_WARNING, __METHOD__ . ": tried to save dupe notice for entry {$notice->uri} of feed {$this->feeduri}"); - continue; - } - - // @fixme need to ensure that groups get handled correctly - $saved = Notice::saveNew($notice->profile_id, - $notice->content, - 'ostatus', - array('is_local' => Notice::REMOTE_OMB, - 'uri' => $notice->uri, - 'lat' => $notice->lat, - 'lon' => $notice->lon, - 'location_ns' => $notice->location_ns, - 'location_id' => $notice->location_id)); - - /* - common_log(LOG_DEBUG, "going to check group delivery..."); - if ($this->group_id) { - $group = User_group::staticGet($this->group_id); - if ($group) { - common_log(LOG_INFO, __METHOD__ . ": saving to local shadow group $group->id $group->nickname"); - $groups = array($group); - } else { - common_log(LOG_INFO, __METHOD__ . ": lost the local shadow group?"); - } - } else { - common_log(LOG_INFO, __METHOD__ . ": no local shadow groups"); - $groups = array(); - } - common_log(LOG_DEBUG, "going to add to inboxes..."); - $notice->addToInboxes($groups, array()); - common_log(LOG_DEBUG, "added to inboxes."); - */ - - $hits++; + if (!$this->validatePushSig($post, $hmac)) { + // Per spec we silently drop input with a bad sig, + // while reporting receipt to the server. + return; } - if ($hits == 0) { - common_log(LOG_INFO, __METHOD__ . ": no updates in packet for \"$this->feeduri\"! $xml"); + + $feed = new DOMDocument(); + if (!$feed->loadXML($post)) { + // @fixme might help to include the err message + common_log(LOG_ERR, __METHOD__ . ": ignoring invalid XML"); + return; + } + + $entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry'); + if ($entries->length == 0) { + common_log(LOG_ERR, __METHOD__ . ": no entries in feed update, ignoring"); + return; + } + + for ($i = 0; $i < $entries->length; $i++) { + $entry = $entries->item($i); + $this->processEntry($entry, $feed); } } + + /** + * Validate the given Atom chunk and HMAC signature against our + * shared secret that was set up at subscription time. + * + * If we don't have a shared secret, there should be no signature. + * If we we do, our the calculated HMAC should match theirs. + * + * @param string $post raw XML source as POSTed to us + * @param string $hmac X-Hub-Signature HTTP header value, or empty + * @return boolean true for a match + */ + protected function validatePushSig($post, $hmac) + { + if ($this->secret) { + if (preg_match('/^sha1=([0-9a-fA-F]{40})$/', $hmac, $matches)) { + $their_hmac = strtolower($matches[1]); + $our_hmac = hash_hmac('sha1', $post, $this->secret); + if ($their_hmac === $our_hmac) { + return true; + } + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bad SHA-1 HMAC: got $their_hmac, expected $our_hmac"); + } else { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with bogus HMAC '$hmac'"); + } + } else { + if (empty($hmac)) { + return true; + } else { + common_log(LOG_ERR, __METHOD__ . ": ignoring PuSH with unexpected HMAC '$hmac'"); + } + } + return false; + } + + /** + * Process a posted entry from this feed source. + * + * @param DOMElement $entry + * @param DOMElement $feed for context + */ + protected function processEntry($entry, $feed) + { + $activity = new Activity($entry, $feed); + + $debug = var_export($activity, true); + common_log(LOG_DEBUG, $debug); + + if ($activity->verb == ActivityVerb::POST) { + $this->processPost($activity); + } else { + common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); + } + } + + /** + * Process an incoming post activity from this remote feed. + * @param Activity $activity + */ + protected function processPost($activity) + { + // @fixme pull profile reference from actor for group feeds + $actor = $this; + $localProfile = $actor->localProfile(); + if (empty($localProfile)) { + common_log(LOG_INFO, "OStatus: ignoring post with invalid author"); + return; + } + + if (empty($activity->object)) { + // This shouldn't happen! + common_log(LOG_INFO, "OStatus: ignoring post with missing post object."); + return; + } + + if ($activity->object->link) { + $sourceUri = $activity->object->link; + } else if (preg_match('!^https?://!', $activity->object->id)) { + $sourceUri = $activity->object->id; + } else { + common_log(LOG_INFO, "OStatus: ignoring post with no source link: id $activity->object->id"); + return; + } + + $dupe = Notice::staticGet('uri', $sourceUri); + if ($dupe) { + common_log(LOG_INFO, "OStatus: ignoring duplicate post: $noticeLink"); + return; + } + + // @fixme sanitize and save HTML content if available + $content = $activity->object->title; + + $params = array('is_local' => Notice::REMOTE_OMB, + 'uri' => $sourceUri); + + $location = $this->getEntryLocation($activity->entry); + if ($location) { + $params['lat'] = $location->lat; + $params['lon'] = $location->lon; + if ($location->location_id) { + $params['location_ns'] = $location->location_ns; + $params['location_id'] = $location->location_id; + } + } + + // @fixme save detailed ostatus source info + // @fixme ensure that groups get handled correctly + + $saved = Notice::saveNew($localProfile->id, + $content, + 'ostatus', + $params); + } + + /** + * Parse location given as a GeoRSS-simple point, if provided. + * http://www.georss.org/simple + * + * @param feed item $entry + * @return mixed Location or false + */ + function getLocation($dom) + { + $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point'); + + for ($i = 0; $i < $points->length; $i++) { + $point = $points->item(0)->textContent; + $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" + $point = preg_replace('/\s+/', ' ', $point); + $point = trim($point); + $coords = explode(' ', $point); + if (count($coords) == 2) { + list($lat, $lon) = $coords; + if (is_numeric($lat) && is_numeric($lon)) { + common_log(LOG_INFO, "Looking up location for $lat $lon from georss"); + return Location::fromLatLon($lat, $lon); + } + } + common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); + } + + return false; + } } From c892726c80b4e466b2bbad0f7b396cf0c7a137d9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 16 Feb 2010 16:22:58 -0800 Subject: [PATCH 06/21] Take remote profiles into account when looking up canonical profile URIs --- EVENTS.txt | 5 ++++- classes/Notice.php | 2 +- classes/Profile.php | 21 +++++++++++++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 69fe2ddccb..f333c5442f 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -1,4 +1,4 @@ -\InitializePlugin: a chance to initialize a plugin in a complete environment +InitializePlugin: a chance to initialize a plugin in a complete environment CleanupPlugin: a chance to cleanup a plugin at the end of a program @@ -722,3 +722,6 @@ StartRobotsTxt: Before outputting the robots.txt page EndRobotsTxt: After the default robots.txt page (good place for customization) - &$action: RobotstxtAction being shown +GetProfileUri: When determining the canonical URI for a given profile +- &$profile: the current profile + diff --git a/classes/Notice.php b/classes/Notice.php index 73b22d58a0..f184b9c52c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1036,7 +1036,7 @@ class Notice extends Memcached_DataObject $xs->element( 'link', array( 'rel' => 'ostatus:attention', - 'href' => $profile->getAcctUri() + 'href' => $profile->getUri() ) ); } diff --git a/classes/Profile.php b/classes/Profile.php index 8f578c95a3..5a86619fd2 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -810,10 +810,7 @@ class Profile extends Memcached_DataObject $xs->element( 'id', null, - common_local_url( - 'userbyid', - array('id' => $this->id) - ) + $this->getUri() ); $xs->element('title', null, $this->getBestName()); @@ -835,9 +832,21 @@ class Profile extends Memcached_DataObject return $xs->getString(); } - function getAcctUri() + function getUri() { - return $this->nickname . '@' . common_config('site', 'server'); + if (Event::handle('GetProfileUri', array($this))) { + + $remote = Remote_profile::staticGet('id', $this->id); + + if (!empty($remote)) { + return $remote->uri; + } else { + return common_local_url( + 'userbyid', + array('id' => $this->id) + ); + } + } } } From 454d0b5738a1b79510b998b6da6d0df129a182ab Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Feb 2010 01:49:49 +0000 Subject: [PATCH 07/21] OStatus: moving parts of profile processing to Activity from feedmunger. Pausing before refactoring DB schema a bit to clean up feed vs person vs group info --- plugins/OStatus/classes/Ostatus_profile.php | 185 ++++++++++++++++++-- plugins/OStatus/lib/activity.php | 8 + 2 files changed, 175 insertions(+), 18 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 4376d64c66..be01cdfe19 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -218,10 +218,6 @@ class Ostatus_profile extends Memcached_DataObject $profile->query('BEGIN'); - // Awful hack! Awful hack! - $profile->verify = common_good_rand(16); - $profile->secret = common_good_rand(32); - try { $local = $munger->profile(); @@ -423,7 +419,10 @@ class Ostatus_profile extends Memcached_DataObject protected function doSubscribe($mode) { $orig = clone($this); - $this->verify_token = md5(mt_rand() . ':' . $this->feeduri); + $this->verify_token = common_good_rand(16); + if ($mode == 'subscribe') { + $this->secret = common_good_rand(32); + } $this->sub_state = $mode; $this->update($orig); unset($orig); @@ -701,18 +700,19 @@ class Ostatus_profile extends Memcached_DataObject */ protected function processPost($activity) { - // @fixme pull profile reference from actor for group feeds - $actor = $this; - $localProfile = $actor->localProfile(); - if (empty($localProfile)) { - common_log(LOG_INFO, "OStatus: ignoring post with invalid author"); - return; - } - - if (empty($activity->object)) { - // This shouldn't happen! - common_log(LOG_INFO, "OStatus: ignoring post with missing post object."); - return; + if ($this->isGroup()) { + // @fixme validate these profiles in some way! + $oprofile = $this->ensureActorProfile($activity); + } else { + $actorUri = $this->getActorProfileURI($activity); + if ($actorUri == $this->homeuri) { + // @fixme check if profile info has changed and update it + } else { + // @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely + common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->homeuri"); + //return; + } + $oprofile = $this; } if ($activity->object->link) { @@ -749,7 +749,7 @@ class Ostatus_profile extends Memcached_DataObject // @fixme save detailed ostatus source info // @fixme ensure that groups get handled correctly - $saved = Notice::saveNew($localProfile->id, + $saved = Notice::saveNew($oprofile->localProfile()->id, $content, 'ostatus', $params); @@ -784,4 +784,153 @@ class Ostatus_profile extends Memcached_DataObject return false; } + + /** + * Get an appropriate avatar image source URL, if available. + * + * @param ActivityObject $actor + * @param DOMElement $feed + * @return string + */ + function getAvatar($actor, $feed) + { + $url = ''; + $icon = ''; + if ($actor->avatar) { + $url = trim($actor->avatar); + } + if (!$url) { + // Check and on the feed + $els = $feed->childNodes(); + if ($els && $els->length) { + for ($i = 0; $i < $els->length; $i++) { + $el = $els->item($i); + if ($el->namespaceURI == Activity::ATOM) { + if (empty($url) && $el->localName == 'logo') { + $url = trim($el->textContent); + break; + } + if (empty($icon) && $el->localName == 'icon') { + // Use as a fallback + $icon = trim($el->textContent); + } + } + } + } + if ($icon && !$url) { + $url = $icon; + } + } + if ($url) { + $opts = array('allowed_schemes' => array('http', 'https')); + if (Validate::uri($url, $opts)) { + return $url; + } + } + return common_path('plugins/OStatus/images/96px-Feed-icon.svg.png'); + } + + /** + * @fixme move off of ostatus_profile or static? + */ + function ensureActorProfile($activity) + { + $profile = $this->getActorProfile($activity); + if (!$profile) { + $profile = $this->createActorProfile($activity); + } + return $profile; + } + + /** + * @param Activity $activity + * @return mixed matching Ostatus_profile or false if none known + */ + function getActorProfile($activity) + { + $homeuri = $this->getActorProfileURI($activity); + return Ostatus_profile::staticGet('homeuri', $homeuri); + } + + /** + * @param Activity $activity + * @return string + * @throws ServerException + */ + function getActorProfileURI($activity) + { + $opts = array('allowed_schemes' => array('http', 'https')); + $actor = $activity->actor; + if ($actor->id && Validate::uri($actor->id, $opts)) { + return $actor->id; + } + if ($actor->link && Validate::uri($actor->link, $opts)) { + return $actor->link; + } + throw new ServerException("No author ID URI found"); + } + + /** + * + */ + function createActorProfile($activity) + { + $actor = $activity->actor(); + $homeuri = $this->getActivityProfileURI($activity); + $nickname = $this->getAuthorNick($activity); + $avatar = $this->getAvatar($actor, $feed); + + $profile = new Profile(); + $profile->nickname = $nickname; + $profile->fullname = $actor->displayName; + $profile->homepage = $actor->link; // @fixme + $profile->profileurl = $homeuri; + // @fixme bio + // @fixme tags/categories + // @fixme location? + // @todo tags from categories + // @todo lat/lon/location? + + $ok = $profile->insert(); + if ($ok) { + $this->updateAvatar($profile, $avatar); + } else { + throw new ServerException("Can't save local profile"); + } + + // @fixme either need to do feed discovery here + // or need to split out some of the feed stuff + // so we can leave it empty until later. + $oprofile = new Ostatus_profile(); + $oprofile->homeuri = $homeuri; + $oprofile->profile_id = $profile->id; + + $ok = $oprofile->insert(); + if ($ok) { + return $oprofile; + } else { + throw new ServerException("Can't save OStatus profile"); + } + } + + /** + * @fixme move this into Activity? + * @param Activity $activity + * @return string + */ + function getAuthorNick($activity) + { + // @fixme not technically part of the actor? + foreach (array($activity->entry, $activity->feed) as $source) { + $author = ActivityUtil::child($source, 'author', Activity::ATOM); + if ($author) { + $name = ActivityUtil::child($author, 'name', Activity::ATOM); + if ($name) { + return trim($name->textContent); + } + } + } + return false; + } + } diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php index 5b1c4fa8fa..f137946ab4 100644 --- a/plugins/OStatus/lib/activity.php +++ b/plugins/OStatus/lib/activity.php @@ -247,6 +247,14 @@ class ActivityObject // XXX: grab PoCo stuff } + + // Some per-type attributes... + if ($this->type == self::PERSON || $this->type == self::GROUP) { + $this->displayName = $this->title; + + // @fixme we may have multiple avatars with different resolutions specified + $this->avatar = ActivityUtils::getLink($element, 'avatar'); + } } private function _childContent($element, $tag, $namespace=ActivityUtils::ATOM) From e51e96d7248b281e7d0e59f5a9bdcd4e7e651e82 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Feb 2010 02:16:03 +0000 Subject: [PATCH 08/21] OStatus: override source link with the source domain and link to original message --- lib/noticelist.php | 28 ++++++++++++++++------------ plugins/OStatus/OStatusPlugin.php | 13 +++++++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/noticelist.php b/lib/noticelist.php index c05b990245..837cb90faa 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -492,30 +492,34 @@ class NoticeListItem extends Widget break; default: - $name = null; + $name = $source_name; $url = null; - $ns = Notice_source::staticGet($this->notice->source); + if (Event::handle('StartNoticeSourceLink', array($this->notice, &$name, &$url, &$title))) { + $ns = Notice_source::staticGet($this->notice->source); - if ($ns) { - $name = $ns->name; - $url = $ns->url; - } else { - $app = Oauth_application::staticGet('name', $this->notice->source); - if ($app) { - $name = $app->name; - $url = $app->source_url; + if ($ns) { + $name = $ns->name; + $url = $ns->url; + } else { + $app = Oauth_application::staticGet('name', $this->notice->source); + if ($app) { + $name = $app->name; + $url = $app->source_url; + } } } + Event::handle('EndNoticeSourceLink', array($this->notice, &$name, &$url, &$title)); if (!empty($name) && !empty($url)) { $this->out->elementStart('span', 'device'); $this->out->element('a', array('href' => $url, - 'rel' => 'external'), + 'rel' => 'external', + 'title' => $title), $name); $this->out->elementEnd('span'); } else { - $this->out->element('span', 'device', $source_name); + $this->out->element('span', 'device', $name); } break; } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 3b1329d6c5..b6c9fa1d4c 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -289,4 +289,17 @@ class OStatusPlugin extends Plugin $action->script(common_path('plugins/OStatus/js/ostatus.js')); return true; } + + function onStartNoticeSourceLink($notice, &$name, &$url, &$title) + { + if ($notice->source == 'ostatus') { + $bits = parse_url($notice->uri); + $domain = $bits['host']; + + $name = $domain; + $url = $notice->uri; + $title = sprintf(_m("Sent from %s via OStatus"), $domain); + return false; + } + } } From 2cb243808c2c1540f2690bff5a2d9932fa428923 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 16 Feb 2010 20:13:39 -0800 Subject: [PATCH 09/21] More sensical profile::getUri() --- EVENTS.txt | 8 ++++++-- classes/Profile.php | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index f333c5442f..90242fa133 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -722,6 +722,10 @@ StartRobotsTxt: Before outputting the robots.txt page EndRobotsTxt: After the default robots.txt page (good place for customization) - &$action: RobotstxtAction being shown -GetProfileUri: When determining the canonical URI for a given profile -- &$profile: the current profile +StartGetProfileUri: When determining the canonical URI for a given profile +- $profile: the current profile +- &$uri: the URI +EndGetProfileUri: After determining the canonical URI for a given profile +- $profile: the current profile +- &$uri: the URI diff --git a/classes/Profile.php b/classes/Profile.php index 5a86619fd2..494c697e42 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -769,7 +769,7 @@ class Profile extends Memcached_DataObject $xs->elementStart('author'); $xs->element('name', null, $this->nickname); - $xs->element('uri', null, $this->profileurl); + $xs->element('uri', null, $this->getUri()); $xs->elementEnd('author'); return $xs->getString(); @@ -832,21 +832,40 @@ class Profile extends Memcached_DataObject return $xs->getString(); } + /** + * Returns the best URI for a profile. Plugins may override. + * + * @return string $uri + */ function getUri() { - if (Event::handle('GetProfileUri', array($this))) { + $uri = null; - $remote = Remote_profile::staticGet('id', $this->id); + // check for a local user first + $user = User::staticGet('id', $this->id); - if (!empty($remote)) { - return $remote->uri; - } else { - return common_local_url( - 'userbyid', - array('id' => $this->id) - ); + if (!empty($user)) { + $uri = common_local_url( + 'userbyid', + array('id' => $user->id) + ); + } else { + + // give plugins a chance to set the URI + if (Event::handle('StartGetProfileUri', array($this, &$uri))) { + + // return OMB profile if any + $remote = Remote_profile::staticGet('id', $this->id); + + if (!empty($remote)) { + $uri = $remote->uri; + } + + Event::handle('EndGetProfileUri', array($this, &$uri)); } } + + return $uri; } } From a2f8c5da171d23790811677affd7ca5301a995a5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 16 Feb 2010 23:30:08 -0800 Subject: [PATCH 10/21] New Conversation DO to handle remote notices as conversation roots --- classes/Conversation.php | 49 ++++++++++++++++++++++++++++++++++++++++ classes/statusnet.ini | 9 ++++++++ db/statusnet.sql | 8 +++++++ 3 files changed, 66 insertions(+) create mode 100755 classes/Conversation.php diff --git a/classes/Conversation.php b/classes/Conversation.php new file mode 100755 index 0000000000..929b06c149 --- /dev/null +++ b/classes/Conversation.php @@ -0,0 +1,49 @@ +. + * + * @category Data + * @package StatusNet + * @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/ + */ + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; + +class Conversation extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'conversation'; // table name + public $id; // int(4) primary_key not_null + public $uri; // varchar(225) unique_key not_null + public $created; // datetime not_null + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Session',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE +} + diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 5f8da7cf51..7a9ae07e70 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -47,6 +47,15 @@ modified = 384 [consumer__keys] consumer_key = K +[conversation] +id = 129 +uri = 130 +created = 142 +modified = 384 + +[conversation__keys] +id = N + [deleted_notice] id = 129 profile_id = 129 diff --git a/db/statusnet.sql b/db/statusnet.sql index 3434648016..74e5b69547 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -633,3 +633,11 @@ create table inbox ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table conversation ( + id integer auto_increment primary key comment 'unique identifier', + uri varchar(225) not null unique comment 'URI of the conversation', + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified' +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + From ed46a38ecfea0a87e01aacfcde181087d5e0f19f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 01:11:14 -0800 Subject: [PATCH 11/21] - conversation.uri needs to be nullable - factory method for creating new local conversations --- classes/Conversation.php | 33 +++++++++++++++++++++++++++++++-- classes/statusnet.ini | 3 ++- db/statusnet.sql | 2 +- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/classes/Conversation.php b/classes/Conversation.php index 929b06c149..ea8bd87b56 100755 --- a/classes/Conversation.php +++ b/classes/Conversation.php @@ -36,14 +36,43 @@ class Conversation extends Memcached_DataObject public $__table = 'conversation'; // table name public $id; // int(4) primary_key not_null - public $uri; // varchar(225) unique_key not_null + public $uri; // varchar(225) unique_key public $created; // datetime not_null public $modified; // timestamp not_null default_CURRENT_TIMESTAMP /* Static get */ - function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Session',$k,$v); } + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('conversation',$k,$v); } /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + + /** + * Factory method for creating a new conversation + * + * @return Conversation the new conversation DO + */ + static function create() + { + $conv = new Conversation(); + $conv->created = common_sql_now(); + $id = $conv->insert(); + + if (empty($id)) { + common_log_db_error($conv, 'INSERT', __FILE__); + return null; + } + + $orig = clone($conv); + $orig->uri = common_local_url('conversation', array('id' => $id)); + $result = $orig->update($conv); + + if (empty($result)) { + common_log_db_error($conv, 'UPDATE', __FILE__); + return null; + } + + return $conv; + } + } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 7a9ae07e70..81c1b68b23 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -49,12 +49,13 @@ consumer_key = K [conversation] id = 129 -uri = 130 +uri = 2 created = 142 modified = 384 [conversation__keys] id = N +uri = U [deleted_notice] id = 129 diff --git a/db/statusnet.sql b/db/statusnet.sql index 74e5b69547..97117c80aa 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -636,7 +636,7 @@ create table inbox ( create table conversation ( id integer auto_increment primary key comment 'unique identifier', - uri varchar(225) not null unique comment 'URI of the conversation', + uri varchar(225) unique comment 'URI of the conversation', created datetime not null comment 'date this record was created', modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; From 198c046c896c2a1c4dc9037fa538c14179e827ce Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 01:12:13 -0800 Subject: [PATCH 12/21] - Set the root of a new local conversation to a new conversation.id - Output conversation URIs from conversation.uri --- classes/Notice.php | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index f184b9c52c..b0edb6de60 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -309,7 +309,8 @@ class Notice extends Memcached_DataObject // the beginning of a new conversation. if (empty($notice->conversation)) { - $notice->conversation = $notice->id; + $conv = Conversation::create(); + $notice->conversation = $conv->id; $changed = true; } @@ -331,14 +332,15 @@ class Notice extends Memcached_DataObject return $notice; } - function blowOnInsert() + function blowOnInsert($conversation = false) { 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); - } + // XXX: Before we were blowing the casche only if the notice id + // was not the root of the conversation. What to do now? + + self::blow('notice:conversation_ids:%d', $this->conversation); if (!empty($this->repeat_of)) { self::blow('notice:repeats:%d', $this->repeat_of); @@ -1015,24 +1017,25 @@ class Notice extends Memcached_DataObject } } - if (!empty($this->conversation) - && $this->conversation != $this->id) { - $xs->element( - 'link', array( - 'rel' => 'ostatus:conversation', - 'href' => common_local_url( - 'conversation', - array('id' => $this->conversation) - ) + if (!empty($this->conversation)) { + + $conv = Conversation::staticGet('id', $this->conversation); + + if (!empty($conv)) { + $xs->element( + 'link', array( + 'rel' => 'ostatus:conversation', + 'href' => $conv->uri ) ); + } } $reply_ids = $this->getReplies(); foreach ($reply_ids as $id) { $profile = Profile::staticGet('id', $id); - if (!empty($profile)) { + if (!empty($profile)) { $xs->element( 'link', array( 'rel' => 'ostatus:attention', From 9f3246124dc9702ff3a7e422df4adff687e62f9c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Feb 2010 09:58:34 -0800 Subject: [PATCH 13/21] PostDebug plugin - saves POST data to debug log or directory to help debug form submission and server-to-server communications. Some sensitive items are sanitized but not all - don't just shove out the log results publicly! --- plugins/PostDebug/PostDebugPlugin.php | 150 ++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 plugins/PostDebug/PostDebugPlugin.php diff --git a/plugins/PostDebug/PostDebugPlugin.php b/plugins/PostDebug/PostDebugPlugin.php new file mode 100644 index 0000000000..48fe28eabd --- /dev/null +++ b/plugins/PostDebug/PostDebugPlugin.php @@ -0,0 +1,150 @@ +. + * + * @category Sample + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class PostDebugPlugin extends Plugin +{ + /** + * Set to a directory to dump individual items instead of + * sending to the debug log + */ + public $dir=false; + + public function onArgsInitialize(&$args) + { + if (isset($_SERVER['REQUEST_METHOD']) && + $_SERVER['REQUEST_METHOD'] == 'POST') { + $this->doDebug(); + } + } + + public function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'PostDebug', + 'version' => STATUSNET_VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:PostDebug', + 'rawdescription' => + _m('Debugging tool to record request details on POST.')); + return true; + } + + protected function doDebug() + { + $data = array('timestamp' => gmdate('r'), + 'remote_addr' => @$_SERVER['REMOTE_ADDR'], + 'url' => @$_SERVER['REQUEST_URI'], + 'have_session' => common_have_session(), + 'logged_in' => common_logged_in(), + 'is_real_login' => common_is_real_login(), + 'user' => common_logged_in() ? common_current_user()->nickname : null, + 'headers' => $this->getHttpHeaders(), + 'post_data' => $this->sanitizePostData($_POST)); + $this->saveDebug($data); + } + + protected function saveDebug($data) + { + $output = var_export($data, true); + if ($this->dir) { + $file = $this->dir . DIRECTORY_SEPARATOR . $this->logFileName(); + file_put_contents($file, $output); + } else { + common_log(LOG_DEBUG, "PostDebug: $output"); + } + } + + protected function logFileName() + { + $base = common_request_id(); + $base = preg_replace('/^(.+?) .*$/', '$1', $base); + $base = str_replace(':', '-', $base); + $base = rawurlencode($base); + return $base; + } + + protected function getHttpHeaders() + { + if (function_exists('getallheaders')) { + $headers = getallheaders(); + } else { + $headers = array(); + $prefix = 'HTTP_'; + $prefixLen = strlen($prefix); + foreach ($_SERVER as $key => $val) { + if (substr($key, 0, $prefixLen) == $prefix) { + $header = $this->normalizeHeader(substr($key, $prefixLen)); + $headers[$header] = $val; + } + } + } + foreach ($headers as $header => $val) { + if (strtolower($header) == 'cookie') { + $headers[$header] = $this->sanitizeCookies($val); + } + } + return $headers; + } + + protected function normalizeHeader($key) + { + return implode('-', + array_map('ucfirst', + explode("_", + strtolower($key)))); + } + + function sanitizeCookies($val) + { + $blacklist = array(session_name(), 'rememberme'); + foreach ($blacklist as $name) { + $val = preg_replace("/(^|;\s*)({$name}=)(.*?)(;|$)/", + "$1$2########$4", + $val); + } + return $val; + } + + function sanitizePostData($data) + { + $blacklist = array('password', 'confirm', 'token'); + foreach ($data as $key => $val) { + if (in_array($key, $blacklist)) { + $data[$key] = '########'; + } + } + return $data; + } + +} + From 8e07926a9c0736b5a3f408465dded92676ac1862 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 17 Feb 2010 12:02:59 -0500 Subject: [PATCH 14/21] parse_url returns an associative array - not an object --- lib/htmloutputter.php | 2 +- plugins/Minify/MinifyPlugin.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 317f5ea612..4a88337bc5 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -428,7 +428,7 @@ class HTMLOutputter extends XMLOutputter { if(Event::handle('StartCssLinkElement', array($this,&$src,&$theme,&$media))) { $url = parse_url($src); - if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) + if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) { if(file_exists(Theme::file($src,$theme))){ $src = Theme::path($src, $theme); diff --git a/plugins/Minify/MinifyPlugin.php b/plugins/Minify/MinifyPlugin.php index b49b6a4bad..fe1883ded4 100644 --- a/plugins/Minify/MinifyPlugin.php +++ b/plugins/Minify/MinifyPlugin.php @@ -96,7 +96,7 @@ class MinifyPlugin extends Plugin && is_null(common_config('theme', 'path')) && is_null(common_config('theme', 'server')); $url = parse_url($src); - if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment)) + if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment'])) { if(!isset($theme)) { $theme = common_config('site', 'theme'); From e8275aa60abd381de9e95c29fd00819c81d3db79 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Feb 2010 10:12:37 -0800 Subject: [PATCH 15/21] Fix exception on bad plugin load --- lib/statusnet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/statusnet.php b/lib/statusnet.php index 9c7ede5a5d..257bd861da 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -63,7 +63,7 @@ class StatusNet } } if (!class_exists($pluginclass)) { - throw new ServerException(500, "Plugin $name not found."); + throw new ServerException("Plugin $name not found.", 500); } } From 6b887728b2c84fec39a576e6f2a76909b6318774 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 19:24:38 +0000 Subject: [PATCH 16/21] Better logging for Twitter bridge account linking process --- .../TwitterBridge/twitterauthorization.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index c154932bbc..8bfdacee91 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -131,8 +131,7 @@ class TwitterauthorizationAction extends Action } else if ($this->arg('connect')) { $this->connectNewUser(); } else { - common_debug('Twitter Connect Plugin - ' . - print_r($this->args, true)); + common_debug('Twitter bridge - ' . print_r($this->args, true)); $this->showForm(_('Something weird happened.'), $this->trimmed('newname')); } @@ -172,9 +171,15 @@ class TwitterauthorizationAction extends Action $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client error - code: %1s, msg: %2s', - $e->getCode(), $e->getMessage()); - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $msg = sprintf( + 'OAuth client error - code: %1s, msg: %2s', + $e->getCode(), + $e->getMessage() + ); + common_log(LOG_INFO, 'Twitter bridge - ' . $msg); + $this->serverError( + _m('Couldn\'t link your Twitter account: ') . $e->getMessage() + ); } common_redirect($auth_link); @@ -192,7 +197,9 @@ class TwitterauthorizationAction extends Action // token we sent them if ($_SESSION['twitter_request_token'] != $this->oauth_token) { - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $this->serverError( + _m('Couldn\'t link your Twitter account: oauth_token mismatch.') + ); } $twitter_user = null; @@ -212,9 +219,15 @@ class TwitterauthorizationAction extends Action $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s', - $e->getCode(), $e->getMessage()); - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $msg = sprintf( + 'OAuth client error - code: %1$s, msg: %2$s', + $e->getCode(), + $e->getMessage() + ); + common_log(LOG_INFO, 'Twitter bridge - ' . $msg); + $this->serverError( + _m('Couldn\'t link your Twitter account: ') . $e-getMessage() + ); } if (common_logged_in()) { From c498f6e1ba50d8c17cd1d8698f05f7604d5cfcf8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 20:53:16 +0000 Subject: [PATCH 17/21] Twitter bridge - fix for Ticket #2192 --- plugins/TwitterBridge/twitter.php | 14 +++++++++----- plugins/TwitterBridge/twitterauthorization.php | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index e5afde62ca..ceb83b037f 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -1,7 +1,7 @@ delete(); - if ($result != false) { - common_log(LOG_INFO, - "Twitter bridge - removed old Twitter user: $screen_name ($twitter_id)."); + if (!empty($luser)) { + $result = $luser->delete(); + if ($result != false) { + common_log( + LOG_INFO, + "Twitter bridge - removed old Twitter user: $screen_name ($twitter_id)." + ); + } } $fuser = new Foreign_user(); diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 8bfdacee91..cabf69d7a8 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -178,7 +178,7 @@ class TwitterauthorizationAction extends Action ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); $this->serverError( - _m('Couldn\'t link your Twitter account: ') . $e->getMessage() + _m('Couldn\'t link your Twitter account.') ); } @@ -226,7 +226,7 @@ class TwitterauthorizationAction extends Action ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); $this->serverError( - _m('Couldn\'t link your Twitter account: ') . $e-getMessage() + _m('Couldn\'t link your Twitter account.') ); } @@ -292,7 +292,7 @@ class TwitterauthorizationAction extends Action if (empty($flink_id)) { common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_('Couldn\'t link your Twitter account.')); + $this->serverError(_('Couldn\'t link your Twitter account.')); } return $flink_id; From c201baffbfbf812ecba504e6829dd9e9d17a4bac Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 16 Feb 2010 06:12:08 +0000 Subject: [PATCH 18/21] Upgrade Twitter bridge to use OAuth 1.0a. It's more secure, and allows us to automatically send in a callback url instead of having to manually configure one for each StatusNet instance. --- lib/oauthclient.php | 88 ++++++++++++++----- .../TwitterBridge/twitterauthorization.php | 10 +-- plugins/TwitterBridge/twitteroauthclient.php | 28 ++++++ 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/lib/oauthclient.php b/lib/oauthclient.php index b22fd78974..bc7587183b 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -90,20 +90,47 @@ class OAuthClient /** * Gets a request token from the given url * - * @param string $url OAuth endpoint for grabbing request tokens + * @param string $url OAuth endpoint for grabbing request tokens + * @param string $callback authorized request token callback * * @return OAuthToken $token the request token */ - function getRequestToken($url) + function getRequestToken($url, $callback = null) { - $response = $this->oAuthGet($url); + $params = null; + + if (!is_null($callback)) { + $params['oauth_callback'] = $callback; + } + + $response = $this->oAuthGet($url, $params); + $arr = array(); parse_str($response, $arr); - if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) { - $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']); + + $token = $arr['oauth_token']; + $secret = $arr['oauth_token_secret']; + $confirm = $arr['oauth_callback_confirmed']; + + if (isset($token) && isset($secret)) { + + $token = new OAuthToken($token, $secret); + + if (isset($confirm)) { + if ($confirm == 'true') { + common_debug('Twitter bridge - callback confirmed.'); + return $token; + } else { + throw new OAuthClientException( + 'Callback was not confirmed by Twitter.' + ); + } + } return $token; } else { - throw new OAuthClientException(); + throw new OAuthClientException( + 'Could not get a request token from Twitter.' + ); } } @@ -113,49 +140,64 @@ class OAuthClient * * @param string $url endpoint for authorizing request tokens * @param OAuthToken $request_token the request token to be authorized - * @param string $oauth_callback optional callback url * * @return string $authorize_url the url to redirect to */ - function getAuthorizeLink($url, $request_token, $oauth_callback = null) + function getAuthorizeLink($url, $request_token) { $authorize_url = $url . '?oauth_token=' . $request_token->key; - if (isset($oauth_callback)) { - $authorize_url .= '&oauth_callback=' . urlencode($oauth_callback); - } - return $authorize_url; } /** * Fetches an access token * - * @param string $url OAuth endpoint for exchanging authorized request tokens - * for access tokens + * @param string $url OAuth endpoint for exchanging authorized request tokens + * for access tokens + * @param string $verifier 1.0a verifier * * @return OAuthToken $token the access token */ - function getAccessToken($url) + function getAccessToken($url, $verifier = null) { - $response = $this->oAuthPost($url); - parse_str($response); - $token = new OAuthToken($oauth_token, $oauth_token_secret); - return $token; + $params = array(); + + if (!is_null($verifier)) { + $params['oauth_verifier'] = $verifier; + } + + $response = $this->oAuthPost($url, $params); + + $arr = array(); + parse_str($response, $arr); + + $token = $arr['oauth_token']; + $secret = $arr['oauth_token_secret']; + + if (isset($token) && isset($secret)) { + $token = new OAuthToken($token, $secret); + return $token; + } else { + throw new OAuthClientException( + 'Could not get a access token from Twitter.' + ); + } } /** - * Use HTTP GET to make a signed OAuth request + * Use HTTP GET to make a signed OAuth requesta * - * @param string $url OAuth endpoint + * @param string $url OAuth request token endpoint + * @param array $params additional parameters * * @return mixed the request */ - function oAuthGet($url) + function oAuthGet($url, $params = null) { $request = OAuthRequest::from_consumer_and_token($this->consumer, - $this->token, 'GET', $url, null); + $this->token, 'GET', $url, $params); $request->sign_request($this->sha1_method, $this->consumer, $this->token); diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 6822d33dd1..c154932bbc 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -56,6 +56,7 @@ class TwitterauthorizationAction extends Action var $tw_fields = null; var $access_token = null; var $signin = null; + var $verifier = null; /** * Initialize class members. Looks for 'oauth_token' parameter. @@ -70,6 +71,7 @@ class TwitterauthorizationAction extends Action $this->signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); + $this->verifier = $this->arg('oauth_verifier'); return true; } @@ -160,8 +162,7 @@ class TwitterauthorizationAction extends Action // Get a new request token and authorize it $client = new TwitterOAuthClient(); - $req_tok = - $client->getRequestToken(TwitterOAuthClient::$requestTokenURL); + $req_tok = $client->getRequestToken(); // Sock the request token away in the session temporarily @@ -171,7 +172,7 @@ class TwitterauthorizationAction extends Action $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', + $msg = sprintf('OAuth client error - code: %1s, msg: %2s', $e->getCode(), $e->getMessage()); $this->serverError(_m('Couldn\'t link your Twitter account.')); } @@ -187,7 +188,6 @@ class TwitterauthorizationAction extends Action */ function saveAccessToken() { - // Check to make sure Twitter returned the same request // token we sent them @@ -204,7 +204,7 @@ class TwitterauthorizationAction extends Action // Exchange the request token for an access token - $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL); + $atok = $client->getAccessToken($this->verifier); // Test the access token and get the user's Twitter info diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index 277e7ab409..ba45b533dc 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -91,6 +91,19 @@ class TwitterOAuthClient extends OAuthClient } } + /** + * Gets a request token from Twitter + * + * @return OAuthToken $token the request token + */ + function getRequestToken() + { + return parent::getRequestToken( + self::$requestTokenURL, + common_local_url('twitterauthorization') + ); + } + /** * Builds a link to Twitter's endpoint for authorizing a request token * @@ -107,6 +120,21 @@ class TwitterOAuthClient extends OAuthClient common_local_url('twitterauthorization')); } + /** + * Fetches an access token from Twitter + * + * @param string $verifier 1.0a verifier + * + * @return OAuthToken $token the access token + */ + function getAccessToken($verifier = null) + { + return parent::getAccessToken( + self::$accessTokenURL, + $verifier + ); + } + /** * Calls Twitter's /account/verify_credentials API method * From 05c50499c31b80d2a1c41e5256fae96cc06252ad Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 19:24:38 +0000 Subject: [PATCH 19/21] Better logging for Twitter bridge account linking process --- .../TwitterBridge/twitterauthorization.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index c154932bbc..8bfdacee91 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -131,8 +131,7 @@ class TwitterauthorizationAction extends Action } else if ($this->arg('connect')) { $this->connectNewUser(); } else { - common_debug('Twitter Connect Plugin - ' . - print_r($this->args, true)); + common_debug('Twitter bridge - ' . print_r($this->args, true)); $this->showForm(_('Something weird happened.'), $this->trimmed('newname')); } @@ -172,9 +171,15 @@ class TwitterauthorizationAction extends Action $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client error - code: %1s, msg: %2s', - $e->getCode(), $e->getMessage()); - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $msg = sprintf( + 'OAuth client error - code: %1s, msg: %2s', + $e->getCode(), + $e->getMessage() + ); + common_log(LOG_INFO, 'Twitter bridge - ' . $msg); + $this->serverError( + _m('Couldn\'t link your Twitter account: ') . $e->getMessage() + ); } common_redirect($auth_link); @@ -192,7 +197,9 @@ class TwitterauthorizationAction extends Action // token we sent them if ($_SESSION['twitter_request_token'] != $this->oauth_token) { - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $this->serverError( + _m('Couldn\'t link your Twitter account: oauth_token mismatch.') + ); } $twitter_user = null; @@ -212,9 +219,15 @@ class TwitterauthorizationAction extends Action $twitter_user = $client->verifyCredentials(); } catch (OAuthClientException $e) { - $msg = sprintf('OAuth client error - code: %1$s, msg: %2$s', - $e->getCode(), $e->getMessage()); - $this->serverError(_m('Couldn\'t link your Twitter account.')); + $msg = sprintf( + 'OAuth client error - code: %1$s, msg: %2$s', + $e->getCode(), + $e->getMessage() + ); + common_log(LOG_INFO, 'Twitter bridge - ' . $msg); + $this->serverError( + _m('Couldn\'t link your Twitter account: ') . $e-getMessage() + ); } if (common_logged_in()) { From 73ba26efe3d9d97c478a507d351ac92d28d82655 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 17 Feb 2010 20:53:16 +0000 Subject: [PATCH 20/21] Twitter bridge - fix for Ticket #2192 --- plugins/TwitterBridge/twitter.php | 14 +++++++++----- plugins/TwitterBridge/twitterauthorization.php | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index e5afde62ca..ceb83b037f 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -1,7 +1,7 @@ delete(); - if ($result != false) { - common_log(LOG_INFO, - "Twitter bridge - removed old Twitter user: $screen_name ($twitter_id)."); + if (!empty($luser)) { + $result = $luser->delete(); + if ($result != false) { + common_log( + LOG_INFO, + "Twitter bridge - removed old Twitter user: $screen_name ($twitter_id)." + ); + } } $fuser = new Foreign_user(); diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 8bfdacee91..cabf69d7a8 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -178,7 +178,7 @@ class TwitterauthorizationAction extends Action ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); $this->serverError( - _m('Couldn\'t link your Twitter account: ') . $e->getMessage() + _m('Couldn\'t link your Twitter account.') ); } @@ -226,7 +226,7 @@ class TwitterauthorizationAction extends Action ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); $this->serverError( - _m('Couldn\'t link your Twitter account: ') . $e-getMessage() + _m('Couldn\'t link your Twitter account.') ); } @@ -292,7 +292,7 @@ class TwitterauthorizationAction extends Action if (empty($flink_id)) { common_log_db_error($flink, 'INSERT', __FILE__); - $this->serverError(_('Couldn\'t link your Twitter account.')); + $this->serverError(_('Couldn\'t link your Twitter account.')); } return $flink_id; From ce6be4f83624d8c39a93d2b54567cc2f33580812 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 17 Feb 2010 16:49:00 -0800 Subject: [PATCH 21/21] Queues: redid the breakout control model so we can start up and subscribe to queues without running through the complete site list, which is ok at 1k sites but too slow at 10k. All breakout queues that we're going to need to listen to now need to be explicitly listed in $config['queue']['breakout']. Until XMPP is moved to component model, this setting will let the individual processes work with their own queues: $config['queue']['breakout'][] = 'xmpp/xmppout/' . $config['site']['nickname']; --- lib/default.php | 11 ++-- lib/iomaster.php | 23 +++----- lib/stompqueuemanager.php | 117 ++++++++++++++++++++------------------ scripts/queuedaemon.php | 3 +- 4 files changed, 76 insertions(+), 78 deletions(-) diff --git a/lib/default.php b/lib/default.php index a74cccae12..c969c3b337 100644 --- a/lib/default.php +++ b/lib/default.php @@ -91,10 +91,13 @@ $default = 'spawndelay' => 1, // Wait at least N seconds between (re)spawns of child processes to avoid slamming the queue server with subscription startup 'debug_memory' => false, // true to spit memory usage to log 'inboxes' => true, // true to do inbox distribution & output queueing from in background via 'distrib' queue - 'breakout' => array('*' => 'shared'), // set global or per-handler queue breakout - // 'shared': use a shared queue for all sites - // 'handler': share each/this handler over multiple sites - // 'site': break out for each/this handler on this site + 'breakout' => array(), // List queue specifiers to break out when using Stomp queue. + // Default will share all queues for all sites within each group. + // Specify as / or //, + // using nickname identifier as site. + // + // 'main/distrib' separate "distrib" queue covering all sites + // 'xmpp/xmppout/mysite' separate "xmppout" queue covering just 'mysite' 'max_retries' => 10, // drop messages after N failed attempts to process (Stomp) 'dead_letter_dir' => false, // set to directory to save dropped messages into (Stomp) ), diff --git a/lib/iomaster.php b/lib/iomaster.php index 54e2dfe841..d20837ba54 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -55,27 +55,18 @@ abstract class IoMaster if ($multiSite !== null) { $this->multiSite = $multiSite; } - if ($this->multiSite) { - $this->sites = StatusNet::findAllSites(); - } else { - $this->sites = array(StatusNet::currentSite()); - } - if (empty($this->sites)) { - throw new Exception("Empty status_network table, cannot init"); - } - - foreach ($this->sites as $site) { - StatusNet::switchSite($site); - $this->initManagers(); - } + $this->initManagers(); } /** - * Initialize IoManagers for the currently configured site - * which are appropriate to this instance. + * Initialize IoManagers which are appropriate to this instance; + * pass class names or instances into $this->instantiate(). * - * Pass class names into $this->instantiate() + * If setup and configuration may vary between sites in multi-site + * mode, it's the subclass's responsibility to set them up here. + * + * Switching site configurations is an acceptable side effect. */ abstract function initManagers(); diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index bfeeb23b7f..9af8b2f482 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -63,7 +63,7 @@ class StompQueueManager extends QueueManager $this->password = common_config('queue', 'stomp_password'); $this->base = common_config('queue', 'queue_basename'); $this->control = common_config('queue', 'control_channel'); - $this->subscriptions = array($this->control => $this->control); + $this->breakout = common_config('queue', 'breakout'); } /** @@ -75,28 +75,6 @@ class StompQueueManager extends QueueManager return IoManager::INSTANCE_PER_PROCESS; } - /** - * Record queue subscriptions we'll need to handle the current site. - */ - public function addSite() - { - $this->sites[] = StatusNet::currentSite(); - - // Set up handlers active for this site... - $this->initialize(); - - foreach ($this->activeGroups as $group) { - if (isset($this->groups[$group])) { - // Actual queues may be broken out or consolidated... - // Subscribe to all the target queues we'll need. - foreach ($this->groups[$group] as $transport => $class) { - $target = $this->queueName($transport); - $this->subscriptions[$target] = $target; - } - } - } - } - /** * Optional; ping any running queue handler daemons with a notification * such as announcing a new site to handle or requesting clean shutdown. @@ -166,14 +144,15 @@ class StompQueueManager extends QueueManager $con = $this->cons[$idx]; $host = $con->getServer(); - $result = $con->send($this->queueName($queue), $msg, $props); + $target = $this->queueName($queue); + $result = $con->send($target, $msg, $props); if (!$result) { - $this->_log(LOG_ERR, "Error sending $rep to $queue queue on $host"); + $this->_log(LOG_ERR, "Error sending $rep to $queue queue on $host $target"); return false; } - $this->_log(LOG_DEBUG, "complete remote queueing $rep for $queue on $host"); + $this->_log(LOG_DEBUG, "complete remote queueing $rep for $queue on $host $target"); $this->stats('enqueued', $queue); return true; } @@ -432,11 +411,42 @@ class StompQueueManager extends QueueManager protected function doSubscribe(LiberalStomp $con) { $host = $con->getServer(); - foreach ($this->subscriptions as $queue) { - $this->_log(LOG_INFO, "Subscribing to $queue on $host"); - $con->subscribe($queue); + foreach ($this->subscriptions() as $sub) { + $this->_log(LOG_INFO, "Subscribing to $sub on $host"); + $con->subscribe($sub); } } + + /** + * Grab a full list of stomp-side queue subscriptions. + * Will include: + * - control broadcast channel + * - shared group queues for active groups + * - per-handler and per-site breakouts from $config['queue']['breakout'] + * that are rooted in the active groups. + * + * @return array of strings + */ + protected function subscriptions() + { + $subs = array(); + $subs[] = $this->control; + + foreach ($this->activeGroups as $group) { + $subs[] = $this->base . $group; + } + + foreach ($this->breakout as $spec) { + $parts = explode('/', $spec); + if (count($parts) < 2 || count($parts) > 3) { + common_log(LOG_ERR, "Bad queue breakout specifier $spec"); + } + if (in_array($parts[0], $this->activeGroups)) { + $subs[] = $this->base . $spec; + } + } + return array_unique($subs); + } /** * Handle and acknowledge an event that's come in through a queue. @@ -612,32 +622,26 @@ class StompQueueManager extends QueueManager } /** - * Set us up with queue subscriptions for a new site added at runtime, + * (Re)load runtime configuration for a given site by nickname, * triggered by a broadcast to the 'statusnet-control' topic. * + * Configuration changes in database should update, but config + * files might not. + * * @param array $frame Stomp frame * @return bool true to continue; false to stop further processing. */ protected function updateSiteConfig($nickname) { - if (empty($this->sites)) { - if ($nickname == common_config('site', 'nickname')) { - StatusNet::init(common_config('site', 'server')); - } else { - $this->_log(LOG_INFO, "Ignoring update ping for other site $nickname"); + $sn = Status_network::staticGet($nickname); + if ($sn) { + $this->switchSite($nickname); + if (!in_array($nickname, $this->sites)) { + $this->addSite(); } + $this->stats('siteupdate'); } else { - $sn = Status_network::staticGet($nickname); - if ($sn) { - $this->switchSite($nickname); - if (!in_array($nickname, $this->sites)) { - $this->addSite(); - } - // @fixme update subscriptions, if applicable - $this->stats('siteupdate'); - } else { - $this->_log(LOG_ERR, "Ignoring ping for unrecognized new site $nickname"); - } + $this->_log(LOG_ERR, "Ignoring ping for unrecognized new site $nickname"); } } @@ -646,24 +650,25 @@ class StompQueueManager extends QueueManager * group name for this queue to give eg: * * /queue/statusnet/main + * /queue/statusnet/main/distrib + * /queue/statusnet/xmpp/xmppout/site01 * * @param string $queue * @return string */ protected function queueName($queue) { - $base = common_config('queue', 'queue_basename'); $group = $this->queueGroup($queue); - $breakout = $this->breakoutMode($queue); - if ($breakout == 'shared') { - return $base . "$group"; - } else if ($breakout == 'handler') { - return $base . "$group/$queue"; - } else if ($breakout == 'site') { - $site = StatusNet::currentSite(); - return $base . "$group/$queue/$site"; + $site = StatusNet::currentSite(); + + $specs = array("$group/$queue/$site", + "$group/$queue"); + foreach ($specs as $spec) { + if (in_array($spec, $this->breakout)) { + return $this->base . $spec; + } } - throw Exception("Unrecognized queue breakout mode '$breakout' for '$queue'"); + return $this->base . $group; } /** diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index d372d898fa..6dba16f953 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -126,8 +126,7 @@ class QueueDaemon extends SpawningDaemon class QueueMaster extends IoMaster { /** - * Initialize IoManagers for the currently configured site - * which are appropriate to this instance. + * Initialize IoManagers which are appropriate to this instance. */ function initManagers() {