summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/action.php8
-rw-r--r--lib/activity.php1267
-rw-r--r--lib/apiaction.php (renamed from lib/api.php)47
-rw-r--r--lib/apiauth.php1
-rw-r--r--lib/atom10entry.php105
-rw-r--r--lib/atom10feed.php305
-rw-r--r--lib/atomgroupnoticefeed.php67
-rw-r--r--lib/atomnoticefeed.php115
-rw-r--r--lib/atomusernoticefeed.php71
-rw-r--r--lib/cache.php26
-rw-r--r--lib/command.php29
-rw-r--r--lib/common.php3
-rw-r--r--lib/dbqueuemanager.php7
-rw-r--r--lib/default.php28
-rw-r--r--lib/distribqueuehandler.php28
-rw-r--r--lib/error.php10
-rw-r--r--lib/grouplist.php4
-rw-r--r--lib/groupsection.php3
-rw-r--r--lib/htmloutputter.php17
-rw-r--r--lib/httpclient.php5
-rw-r--r--lib/iomanager.php5
-rw-r--r--lib/iomaster.php91
-rw-r--r--lib/joinform.php2
-rw-r--r--lib/leaveform.php2
-rw-r--r--lib/mysqlschema.php1
-rw-r--r--lib/noticelist.php62
-rw-r--r--lib/noticesection.php1
-rw-r--r--lib/oauthclient.php88
-rw-r--r--lib/omb.php8
-rw-r--r--lib/profilelist.php4
-rw-r--r--lib/profilequeuehandler.php52
-rw-r--r--lib/profilesection.php2
-rw-r--r--lib/queued_xmpp.php2
-rw-r--r--lib/queuemanager.php136
-rw-r--r--lib/router.php7
-rw-r--r--lib/spawningdaemon.php18
-rw-r--r--lib/statusnet.php67
-rw-r--r--lib/stompqueuemanager.php440
-rw-r--r--lib/subs.php144
-rw-r--r--lib/taguri.php96
-rw-r--r--lib/theme.php15
-rw-r--r--lib/userprofile.php9
-rw-r--r--lib/util.php271
-rw-r--r--lib/xmppmanager.php8
44 files changed, 2994 insertions, 683 deletions
diff --git a/lib/action.php b/lib/action.php
index 0a607b42d..a7e0eb33b 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -249,7 +249,7 @@ class Action extends HTMLOutputter // lawsuit
$this->script('jquery.min.js');
$this->script('jquery.form.js');
$this->script('jquery.cookie.js');
- $this->script('json2.js');
+ $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.js').'"); }');
$this->script('jquery.joverlay.min.js');
Event::handle('EndShowJQueryScripts', array($this));
}
@@ -259,8 +259,7 @@ class Action extends HTMLOutputter // lawsuit
$this->script('util.js');
$this->script('geometa.js');
// Frame-busting code to avoid clickjacking attacks.
- $this->element('script', array('type' => 'text/javascript'),
- 'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
+ $this->inlineScript('if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
Event::handle('EndShowStatusNetScripts', array($this));
Event::handle('EndShowLaconicaScripts', array($this));
}
@@ -405,6 +404,7 @@ class Action extends HTMLOutputter // lawsuit
'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : Theme::path('logo.png'),
'alt' => common_config('site', 'name')));
}
+ $this->text(' ');
$this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
$this->elementEnd('a');
Event::handle('EndAddressData', array($this));
@@ -822,12 +822,14 @@ class Action extends HTMLOutputter // lawsuit
'alt' => common_config('license', 'title'),
'width' => '80',
'height' => '15'));
+ $this->text(' ');
//TODO: This is dirty: i18n
$this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
$this->element('a', array('class' => 'license',
'rel' => 'external license',
'href' => common_config('license', 'url')),
common_config('license', 'title'));
+ $this->text(' ');
$this->text(_('license.'));
$this->elementEnd('p');
break;
diff --git a/lib/activity.php b/lib/activity.php
new file mode 100644
index 000000000..b20153213
--- /dev/null
+++ b/lib/activity.php
@@ -0,0 +1,1267 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * An activity
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class PoCoURL
+{
+ const URLS = 'urls';
+ const TYPE = 'type';
+ const VALUE = 'value';
+ const PRIMARY = 'primary';
+
+ public $type;
+ public $value;
+ public $primary;
+
+ function __construct($type, $value, $primary = false)
+ {
+ $this->type = $type;
+ $this->value = $value;
+ $this->primary = $primary;
+ }
+
+ function asString()
+ {
+ $xs = new XMLStringer(true);
+ $xs->elementStart('poco:urls');
+ $xs->element('poco:type', null, $this->type);
+ $xs->element('poco:value', null, $this->value);
+ if (!empty($this->primary)) {
+ $xs->element('poco:primary', null, 'true');
+ }
+ $xs->elementEnd('poco:urls');
+ return $xs->getString();
+ }
+}
+
+class PoCoAddress
+{
+ const ADDRESS = 'address';
+ const FORMATTED = 'formatted';
+
+ public $formatted;
+
+ // @todo Other address fields
+
+ function asString()
+ {
+ if (!empty($this->formatted)) {
+ $xs = new XMLStringer(true);
+ $xs->elementStart('poco:address');
+ $xs->element('poco:formatted', null, $this->formatted);
+ $xs->elementEnd('poco:address');
+ return $xs->getString();
+ }
+
+ return null;
+ }
+}
+
+class PoCo
+{
+ const NS = 'http://portablecontacts.net/spec/1.0';
+
+ const USERNAME = 'preferredUsername';
+ const DISPLAYNAME = 'displayName';
+ const NOTE = 'note';
+
+ public $preferredUsername;
+ public $displayName;
+ public $note;
+ public $address;
+ public $urls = array();
+
+ function __construct($element = null)
+ {
+ if (empty($element)) {
+ return;
+ }
+
+ $this->preferredUsername = ActivityUtils::childContent(
+ $element,
+ self::USERNAME,
+ self::NS
+ );
+
+ $this->displayName = ActivityUtils::childContent(
+ $element,
+ self::DISPLAYNAME,
+ self::NS
+ );
+
+ $this->note = ActivityUtils::childContent(
+ $element,
+ self::NOTE,
+ self::NS
+ );
+
+ $this->address = $this->_getAddress($element);
+ $this->urls = $this->_getURLs($element);
+ }
+
+ private function _getURLs($element)
+ {
+ $urlEls = $element->getElementsByTagnameNS(self::NS, PoCoURL::URLS);
+ $urls = array();
+
+ foreach ($urlEls as $urlEl) {
+
+ $type = ActivityUtils::childContent(
+ $urlEl,
+ PoCoURL::TYPE,
+ PoCo::NS
+ );
+
+ $value = ActivityUtils::childContent(
+ $urlEl,
+ PoCoURL::VALUE,
+ PoCo::NS
+ );
+
+ $primary = ActivityUtils::childContent(
+ $urlEl,
+ PoCoURL::PRIMARY,
+ PoCo::NS
+ );
+
+ $isPrimary = false;
+
+ if (isset($primary) && $primary == 'true') {
+ $isPrimary = true;
+ }
+
+ // @todo check to make sure a primary hasn't already been added
+
+ array_push($urls, new PoCoURL($type, $value, $isPrimary));
+ }
+ return $urls;
+ }
+
+ private function _getAddress($element)
+ {
+ $addressEl = ActivityUtils::child(
+ $element,
+ PoCoAddress::ADDRESS,
+ PoCo::NS
+ );
+
+ if (!empty($addressEl)) {
+ $formatted = ActivityUtils::childContent(
+ $addressEl,
+ PoCoAddress::FORMATTED,
+ self::NS
+ );
+
+ if (!empty($formatted)) {
+ $address = new PoCoAddress();
+ $address->formatted = $formatted;
+ return $address;
+ }
+ }
+
+ return null;
+ }
+
+ function fromProfile($profile)
+ {
+ if (empty($profile)) {
+ return null;
+ }
+
+ $poco = new PoCo();
+
+ $poco->preferredUsername = $profile->nickname;
+ $poco->displayName = $profile->getBestName();
+
+ $poco->note = $profile->bio;
+
+ $paddy = new PoCoAddress();
+ $paddy->formatted = $profile->location;
+ $poco->address = $paddy;
+
+ if (!empty($profile->homepage)) {
+ array_push(
+ $poco->urls,
+ new PoCoURL(
+ 'homepage',
+ $profile->homepage,
+ true
+ )
+ );
+ }
+
+ return $poco;
+ }
+
+ function fromGroup($group)
+ {
+ if (empty($group)) {
+ return null;
+ }
+
+ $poco = new PoCo();
+
+ $poco->preferredUsername = $group->nickname;
+ $poco->displayName = $group->getBestName();
+
+ $poco->note = $group->description;
+
+ $paddy = new PoCoAddress();
+ $paddy->formatted = $group->location;
+ $poco->address = $paddy;
+
+ if (!empty($group->homepage)) {
+ array_push(
+ $poco->urls,
+ new PoCoURL(
+ 'homepage',
+ $group->homepage,
+ true
+ )
+ );
+ }
+
+ return $poco;
+ }
+
+ function getPrimaryURL()
+ {
+ foreach ($this->urls as $url) {
+ if ($url->primary) {
+ return $url;
+ }
+ }
+ }
+
+ function asString()
+ {
+ $xs = new XMLStringer(true);
+ $xs->element(
+ 'poco:preferredUsername',
+ null,
+ $this->preferredUsername
+ );
+
+ $xs->element(
+ 'poco:displayName',
+ null,
+ $this->displayName
+ );
+
+ if (!empty($this->note)) {
+ $xs->element('poco:note', null, $this->note);
+ }
+
+ if (!empty($this->address)) {
+ $xs->raw($this->address->asString());
+ }
+
+ foreach ($this->urls as $url) {
+ $xs->raw($url->asString());
+ }
+
+ return $xs->getString();
+ }
+}
+
+/**
+ * Utilities for turning DOMish things into Activityish things
+ *
+ * Some common functions that I didn't have the bandwidth to try to factor
+ * into some kind of reasonable superclass, so just dumped here. Might
+ * be useful to have an ActivityObject parent class or something.
+ *
+ * @category OStatus
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class ActivityUtils
+{
+ const ATOM = 'http://www.w3.org/2005/Atom';
+
+ const LINK = 'link';
+ const REL = 'rel';
+ const TYPE = 'type';
+ const HREF = 'href';
+
+ const CONTENT = 'content';
+ const SRC = 'src';
+
+ /**
+ * Get the permalink for an Activity object
+ *
+ * @param DOMElement $element A DOM element
+ *
+ * @return string related link, if any
+ */
+
+ static function getPermalink($element)
+ {
+ return self::getLink($element, 'alternate', 'text/html');
+ }
+
+ /**
+ * Get the permalink for an Activity object
+ *
+ * @param DOMElement $element A DOM element
+ *
+ * @return string related link, if any
+ */
+
+ static function getLink(DOMNode $element, $rel, $type=null)
+ {
+ $links = $element->getElementsByTagnameNS(self::ATOM, self::LINK);
+
+ foreach ($links as $link) {
+
+ $linkRel = $link->getAttribute(self::REL);
+ $linkType = $link->getAttribute(self::TYPE);
+
+ if ($linkRel == $rel &&
+ (is_null($type) || $linkType == $type)) {
+ return $link->getAttribute(self::HREF);
+ }
+ }
+
+ return null;
+ }
+
+ static function getLinks(DOMNode $element, $rel, $type=null)
+ {
+ $links = $element->getElementsByTagnameNS(self::ATOM, self::LINK);
+ $out = array();
+
+ foreach ($links as $link) {
+
+ $linkRel = $link->getAttribute(self::REL);
+ $linkType = $link->getAttribute(self::TYPE);
+
+ if ($linkRel == $rel &&
+ (is_null($type) || $linkType == $type)) {
+ $out[] = $link;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Gets the first child element with the given tag
+ *
+ * @param DOMElement $element element to pick at
+ * @param string $tag tag to look for
+ * @param string $namespace Namespace to look under
+ *
+ * @return DOMElement found element or null
+ */
+
+ static function child(DOMNode $element, $tag, $namespace=self::ATOM)
+ {
+ $els = $element->childNodes;
+ if (empty($els) || $els->length == 0) {
+ return null;
+ } else {
+ for ($i = 0; $i < $els->length; $i++) {
+ $el = $els->item($i);
+ if ($el->localName == $tag && $el->namespaceURI == $namespace) {
+ return $el;
+ }
+ }
+ }
+ }
+
+ /**
+ * Grab the text content of a DOM element child of the current element
+ *
+ * @param DOMElement $element Element whose children we examine
+ * @param string $tag Tag to look up
+ * @param string $namespace Namespace to use, defaults to Atom
+ *
+ * @return string content of the child
+ */
+
+ static function childContent(DOMNode $element, $tag, $namespace=self::ATOM)
+ {
+ $el = self::child($element, $tag, $namespace);
+
+ if (empty($el)) {
+ return null;
+ } else {
+ return $el->textContent;
+ }
+ }
+
+ /**
+ * Get the content of an atom:entry-like object
+ *
+ * @param DOMElement $element The element to examine.
+ *
+ * @return string unencoded HTML content of the element, like "This -&lt; is <b>HTML</b>."
+ *
+ * @todo handle remote content
+ * @todo handle embedded XML mime types
+ * @todo handle base64-encoded non-XML and non-text mime types
+ */
+
+ static function getContent($element)
+ {
+ $contentEl = ActivityUtils::child($element, self::CONTENT);
+
+ if (!empty($contentEl)) {
+
+ $src = $contentEl->getAttribute(self::SRC);
+
+ if (!empty($src)) {
+ throw new ClientException(_("Can't handle remote content yet."));
+ }
+
+ $type = $contentEl->getAttribute(self::TYPE);
+
+ // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3
+
+ if ($type == 'text') {
+ return $contentEl->textContent;
+ } else if ($type == 'html') {
+ $text = $contentEl->textContent;
+ return htmlspecialchars_decode($text, ENT_QUOTES);
+ } else if ($type == 'xhtml') {
+ $divEl = ActivityUtils::child($contentEl, 'div');
+ if (empty($divEl)) {
+ return null;
+ }
+ $doc = $divEl->ownerDocument;
+ $text = '';
+ $children = $divEl->childNodes;
+
+ for ($i = 0; $i < $children->length; $i++) {
+ $child = $children->item($i);
+ $text .= $doc->saveXML($child);
+ }
+ return trim($text);
+ } else if (in_array(array('text/xml', 'application/xml'), $type) ||
+ preg_match('#(+|/)xml$#', $type)) {
+ throw new ClientException(_("Can't handle embedded XML content yet."));
+ } else if (strncasecmp($type, 'text/', 5)) {
+ return $contentEl->textContent;
+ } else {
+ throw new ClientException(_("Can't handle embedded Base64 content yet."));
+ }
+ }
+ }
+}
+
+// XXX: Arg! This wouldn't be necessary if we used Avatars conistently
+class AvatarLink
+{
+ public $url;
+ public $type;
+ public $size;
+ public $width;
+ public $height;
+
+ function __construct($element=null)
+ {
+ if ($element) {
+ // @fixme use correct namespaces
+ $this->url = $element->getAttribute('href');
+ $this->type = $element->getAttribute('type');
+ $width = $element->getAttribute('media:width');
+ if ($width != null) {
+ $this->width = intval($width);
+ }
+ $height = $element->getAttribute('media:height');
+ if ($height != null) {
+ $this->height = intval($height);
+ }
+ }
+ }
+
+ static function fromAvatar($avatar)
+ {
+ if (empty($avatar)) {
+ return null;
+ }
+ $alink = new AvatarLink();
+ $alink->type = $avatar->mediatype;
+ $alink->height = $avatar->height;
+ $alink->width = $avatar->width;
+ $alink->url = $avatar->displayUrl();
+ return $alink;
+ }
+
+ static function fromFilename($filename, $size)
+ {
+ $alink = new AvatarLink();
+ $alink->url = $filename;
+ $alink->height = $size;
+ if (!empty($filename)) {
+ $alink->width = $size;
+ $alink->type = self::mediatype($filename);
+ } else {
+ $alink->url = User_group::defaultLogo($size);
+ $alink->type = 'image/png';
+ }
+ return $alink;
+ }
+
+ // yuck!
+ static function mediatype($filename) {
+ $ext = strtolower(end(explode('.', $filename)));
+ if ($ext == 'jpeg') {
+ $ext = 'jpg';
+ }
+ // hope we don't support any others
+ $types = array('png', 'gif', 'jpg', 'jpeg');
+ if (in_array($ext, $types)) {
+ return 'image/' . $ext;
+ }
+ return null;
+ }
+}
+
+/**
+ * A noun-ish thing in the activity universe
+ *
+ * The activity streams spec talks about activity objects, while also having
+ * a tag activity:object, which is in fact an activity object. Aaaaaah!
+ *
+ * This is just a thing in the activity universe. Can be the subject, object,
+ * or indirect object (target!) of an activity verb. Rotten name, and I'm
+ * propagating it. *sigh*
+ *
+ * @category OStatus
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class ActivityObject
+{
+ const ARTICLE = 'http://activitystrea.ms/schema/1.0/article';
+ const BLOGENTRY = 'http://activitystrea.ms/schema/1.0/blog-entry';
+ const NOTE = 'http://activitystrea.ms/schema/1.0/note';
+ const STATUS = 'http://activitystrea.ms/schema/1.0/status';
+ const FILE = 'http://activitystrea.ms/schema/1.0/file';
+ const PHOTO = 'http://activitystrea.ms/schema/1.0/photo';
+ const ALBUM = 'http://activitystrea.ms/schema/1.0/photo-album';
+ const PLAYLIST = 'http://activitystrea.ms/schema/1.0/playlist';
+ const VIDEO = 'http://activitystrea.ms/schema/1.0/video';
+ const AUDIO = 'http://activitystrea.ms/schema/1.0/audio';
+ const BOOKMARK = 'http://activitystrea.ms/schema/1.0/bookmark';
+ const PERSON = 'http://activitystrea.ms/schema/1.0/person';
+ const GROUP = 'http://activitystrea.ms/schema/1.0/group';
+ const PLACE = 'http://activitystrea.ms/schema/1.0/place';
+ const COMMENT = 'http://activitystrea.ms/schema/1.0/comment';
+ // ^^^^^^^^^^ tea!
+
+ // Atom elements we snarf
+
+ const TITLE = 'title';
+ const SUMMARY = 'summary';
+ const ID = 'id';
+ const SOURCE = 'source';
+
+ const NAME = 'name';
+ const URI = 'uri';
+ const EMAIL = 'email';
+
+ public $element;
+ public $type;
+ public $id;
+ public $title;
+ public $summary;
+ public $content;
+ public $link;
+ public $source;
+ public $avatarLinks = array();
+ public $geopoint;
+ public $poco;
+ public $displayName;
+
+ /**
+ * Constructor
+ *
+ * This probably needs to be refactored
+ * to generate a local class (ActivityPerson, ActivityFile, ...)
+ * based on the object type.
+ *
+ * @param DOMElement $element DOM thing to turn into an Activity thing
+ */
+
+ function __construct($element = null)
+ {
+ if (empty($element)) {
+ return;
+ }
+
+ $this->element = $element;
+
+ $this->geopoint = $this->_childContent(
+ $element,
+ ActivityContext::POINT,
+ ActivityContext::GEORSS
+ );
+
+ if ($element->tagName == 'author') {
+
+ $this->type = self::PERSON; // XXX: is this fair?
+ $this->title = $this->_childContent($element, self::NAME);
+ $this->id = $this->_childContent($element, self::URI);
+
+ if (empty($this->id)) {
+ $email = $this->_childContent($element, self::EMAIL);
+ if (!empty($email)) {
+ // XXX: acct: ?
+ $this->id = 'mailto:'.$email;
+ }
+ }
+
+ } else {
+
+ $this->type = $this->_childContent($element, Activity::OBJECTTYPE,
+ Activity::SPEC);
+
+ if (empty($this->type)) {
+ $this->type = ActivityObject::NOTE;
+ }
+
+ $this->id = $this->_childContent($element, self::ID);
+ $this->title = $this->_childContent($element, self::TITLE);
+ $this->summary = $this->_childContent($element, self::SUMMARY);
+
+ $this->source = $this->_getSource($element);
+
+ $this->content = ActivityUtils::getContent($element);
+
+ $this->link = ActivityUtils::getPermalink($element);
+
+ }
+
+ // Some per-type attributes...
+ if ($this->type == self::PERSON || $this->type == self::GROUP) {
+ $this->displayName = $this->title;
+
+ $avatars = ActivityUtils::getLinks($element, 'avatar');
+ foreach ($avatars as $link) {
+ $this->avatarLinks[] = new AvatarLink($link);
+ }
+
+ $this->poco = new PoCo($element);
+ }
+ }
+
+ private function _childContent($element, $tag, $namespace=ActivityUtils::ATOM)
+ {
+ return ActivityUtils::childContent($element, $tag, $namespace);
+ }
+
+ // Try to get a unique id for the source feed
+
+ private function _getSource($element)
+ {
+ $sourceEl = ActivityUtils::child($element, 'source');
+
+ if (empty($sourceEl)) {
+ return null;
+ } else {
+ $href = ActivityUtils::getLink($sourceEl, 'self');
+ if (!empty($href)) {
+ return $href;
+ } else {
+ return ActivityUtils::childContent($sourceEl, 'id');
+ }
+ }
+ }
+
+ static function fromNotice($notice)
+ {
+ $object = new ActivityObject();
+
+ $object->type = ActivityObject::NOTE;
+
+ $object->id = $notice->uri;
+ $object->title = $notice->content;
+ $object->content = $notice->rendered;
+ $object->link = $notice->bestUrl();
+
+ return $object;
+ }
+
+ static function fromProfile($profile)
+ {
+ $object = new ActivityObject();
+
+ $object->type = ActivityObject::PERSON;
+ $object->id = $profile->getUri();
+ $object->title = $profile->getBestName();
+ $object->link = $profile->profileurl;
+
+ $orig = $profile->getOriginalAvatar();
+
+ if (!empty($orig)) {
+ $object->avatarLinks[] = AvatarLink::fromAvatar($orig);
+ }
+
+ $sizes = array(
+ AVATAR_PROFILE_SIZE,
+ AVATAR_STREAM_SIZE,
+ AVATAR_MINI_SIZE
+ );
+
+ foreach ($sizes as $size) {
+
+ $alink = null;
+ $avatar = $profile->getAvatar($size);
+
+ if (!empty($avatar)) {
+ $alink = AvatarLink::fromAvatar($avatar);
+ } else {
+ $alink = new AvatarLink();
+ $alink->type = 'image/png';
+ $alink->height = $size;
+ $alink->width = $size;
+ $alink->url = Avatar::defaultImage($size);
+ }
+
+ $object->avatarLinks[] = $alink;
+ }
+
+ if (isset($profile->lat) && isset($profile->lon)) {
+ $object->geopoint = (float)$profile->lat
+ . ' ' . (float)$profile->lon;
+ }
+
+ $object->poco = PoCo::fromProfile($profile);
+
+ return $object;
+ }
+
+ static function fromGroup($group)
+ {
+ $object = new ActivityObject();
+
+ $object->type = ActivityObject::GROUP;
+ $object->id = $group->getUri();
+ $object->title = $group->getBestName();
+ $object->link = $group->getUri();
+
+ $object->avatarLinks[] = AvatarLink::fromFilename(
+ $group->homepage_logo,
+ AVATAR_PROFILE_SIZE
+ );
+
+ $object->avatarLinks[] = AvatarLink::fromFilename(
+ $group->stream_logo,
+ AVATAR_STREAM_SIZE
+ );
+
+ $object->avatarLinks[] = AvatarLink::fromFilename(
+ $group->mini_logo,
+ AVATAR_MINI_SIZE
+ );
+
+ $object->poco = PoCo::fromGroup($group);
+
+ return $object;
+ }
+
+
+ function asString($tag='activity:object')
+ {
+ $xs = new XMLStringer(true);
+
+ $xs->elementStart($tag);
+
+ $xs->element('activity:object-type', null, $this->type);
+
+ $xs->element(self::ID, null, $this->id);
+
+ if (!empty($this->title)) {
+ $xs->element(self::TITLE, null, $this->title);
+ }
+
+ if (!empty($this->summary)) {
+ $xs->element(self::SUMMARY, null, $this->summary);
+ }
+
+ if (!empty($this->content)) {
+ // XXX: assuming HTML content here
+ $xs->element(ActivityUtils::CONTENT, array('type' => 'html'), $this->content);
+ }
+
+ if (!empty($this->link)) {
+ $xs->element(
+ 'link',
+ array(
+ 'rel' => 'alternate',
+ 'type' => 'text/html',
+ 'href' => $this->link
+ ),
+ null
+ );
+ }
+
+ if ($this->type == ActivityObject::PERSON
+ || $this->type == ActivityObject::GROUP) {
+
+ foreach ($this->avatarLinks as $avatar) {
+ $xs->element(
+ 'link', array(
+ 'rel' => 'avatar',
+ 'type' => $avatar->type,
+ 'media:width' => $avatar->width,
+ 'media:height' => $avatar->height,
+ 'href' => $avatar->url
+ ),
+ null
+ );
+ }
+ }
+
+ if (!empty($this->geopoint)) {
+ $xs->element(
+ 'georss:point',
+ null,
+ $this->geopoint
+ );
+ }
+
+ if (!empty($this->poco)) {
+ $xs->raw($this->poco->asString());
+ }
+
+ $xs->elementEnd($tag);
+
+ return $xs->getString();
+ }
+}
+
+/**
+ * Utility class to hold a bunch of constant defining default verb types
+ *
+ * @category OStatus
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class ActivityVerb
+{
+ const POST = 'http://activitystrea.ms/schema/1.0/post';
+ const SHARE = 'http://activitystrea.ms/schema/1.0/share';
+ const SAVE = 'http://activitystrea.ms/schema/1.0/save';
+ const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite';
+ const PLAY = 'http://activitystrea.ms/schema/1.0/play';
+ const FOLLOW = 'http://activitystrea.ms/schema/1.0/follow';
+ const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend';
+ const JOIN = 'http://activitystrea.ms/schema/1.0/join';
+ const TAG = 'http://activitystrea.ms/schema/1.0/tag';
+
+ // Custom OStatus verbs for the flipside until they're standardized
+ const DELETE = 'http://ostatus.org/schema/1.0/unfollow';
+ const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite';
+ const UNFOLLOW = 'http://ostatus.org/schema/1.0/unfollow';
+ const LEAVE = 'http://ostatus.org/schema/1.0/leave';
+
+ // For simple profile-update pings; no content to share.
+ const UPDATE_PROFILE = 'http://ostatus.org/schema/1.0/update-profile';
+}
+
+class ActivityContext
+{
+ public $replyToID;
+ public $replyToUrl;
+ public $location;
+ public $attention = array();
+ public $conversation;
+
+ const THR = 'http://purl.org/syndication/thread/1.0';
+ const GEORSS = 'http://www.georss.org/georss';
+ const OSTATUS = 'http://ostatus.org/schema/1.0';
+
+ const INREPLYTO = 'in-reply-to';
+ const REF = 'ref';
+ const HREF = 'href';
+
+ const POINT = 'point';
+
+ const ATTENTION = 'ostatus:attention';
+ const CONVERSATION = 'ostatus:conversation';
+
+ function __construct($element)
+ {
+ $replyToEl = ActivityUtils::child($element, self::INREPLYTO, self::THR);
+
+ if (!empty($replyToEl)) {
+ $this->replyToID = $replyToEl->getAttribute(self::REF);
+ $this->replyToUrl = $replyToEl->getAttribute(self::HREF);
+ }
+
+ $this->location = $this->getLocation($element);
+
+ $this->conversation = ActivityUtils::getLink($element, self::CONVERSATION);
+
+ // Multiple attention links allowed
+
+ $links = $element->getElementsByTagNameNS(ActivityUtils::ATOM, ActivityUtils::LINK);
+
+ for ($i = 0; $i < $links->length; $i++) {
+
+ $link = $links->item($i);
+
+ $linkRel = $link->getAttribute(ActivityUtils::REL);
+
+ if ($linkRel == self::ATTENTION) {
+ $this->attention[] = $link->getAttribute(self::HREF);
+ }
+ }
+ }
+
+ /**
+ * 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(self::GEORSS, self::POINT);
+
+ for ($i = 0; $i < $points->length; $i++) {
+ $point = $points->item($i)->textContent;
+ return self::locationFromPoint($point);
+ }
+
+ return null;
+ }
+
+ // XXX: Move to ActivityUtils or Location?
+ static function locationFromPoint($point)
+ {
+ $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 point");
+ return Location::fromLatLon($lat, $lon);
+ }
+ }
+ common_log(LOG_ERR, "Ignoring bogus georss:point value $point");
+ return null;
+ }
+}
+
+/**
+ * An activity in the ActivityStrea.ms world
+ *
+ * An activity is kind of like a sentence: someone did something
+ * to something else.
+ *
+ * 'someone' is the 'actor'; 'did something' is the verb;
+ * 'something else' is the object.
+ *
+ * @category OStatus
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class Activity
+{
+ const SPEC = 'http://activitystrea.ms/spec/1.0/';
+ const SCHEMA = 'http://activitystrea.ms/schema/1.0/';
+
+ const VERB = 'verb';
+ const OBJECT = 'object';
+ const ACTOR = 'actor';
+ const SUBJECT = 'subject';
+ const OBJECTTYPE = 'object-type';
+ const CONTEXT = 'context';
+ const TARGET = 'target';
+
+ const ATOM = 'http://www.w3.org/2005/Atom';
+
+ const AUTHOR = 'author';
+ const PUBLISHED = 'published';
+ const UPDATED = 'updated';
+
+ public $actor; // an ActivityObject
+ public $verb; // a string (the URL)
+ public $object; // an ActivityObject
+ public $target; // an ActivityObject
+ public $context; // an ActivityObject
+ public $time; // Time of the activity
+ public $link; // an ActivityObject
+ public $entry; // the source entry
+ public $feed; // the source feed
+
+ public $summary; // summary of activity
+ public $content; // HTML content of activity
+ public $id; // ID of the activity
+ public $title; // title of the activity
+ public $categories = array(); // list of AtomCategory objects
+
+ /**
+ * Turns a regular old Atom <entry> into a magical activity
+ *
+ * @param DOMElement $entry Atom entry to poke at
+ * @param DOMElement $feed Atom feed, for context
+ */
+
+ function __construct($entry = null, $feed = null)
+ {
+ if (is_null($entry)) {
+ return;
+ }
+
+ $this->entry = $entry;
+ $this->feed = $feed;
+
+ $pubEl = $this->_child($entry, self::PUBLISHED, self::ATOM);
+
+ if (!empty($pubEl)) {
+ $this->time = strtotime($pubEl->textContent);
+ } else {
+ // XXX technically an error; being liberal. Good idea...?
+ $updateEl = $this->_child($entry, self::UPDATED, self::ATOM);
+ if (!empty($updateEl)) {
+ $this->time = strtotime($updateEl->textContent);
+ } else {
+ $this->time = null;
+ }
+ }
+
+ $this->link = ActivityUtils::getPermalink($entry);
+
+ $verbEl = $this->_child($entry, self::VERB);
+
+ if (!empty($verbEl)) {
+ $this->verb = trim($verbEl->textContent);
+ } else {
+ $this->verb = ActivityVerb::POST;
+ // XXX: do other implied stuff here
+ }
+
+ $objectEl = $this->_child($entry, self::OBJECT);
+
+ if (!empty($objectEl)) {
+ $this->object = new ActivityObject($objectEl);
+ } else {
+ $this->object = new ActivityObject($entry);
+ }
+
+ $actorEl = $this->_child($entry, self::ACTOR);
+
+ if (!empty($actorEl)) {
+
+ $this->actor = new ActivityObject($actorEl);
+
+ } else if (!empty($feed) &&
+ $subjectEl = $this->_child($feed, self::SUBJECT)) {
+
+ $this->actor = new ActivityObject($subjectEl);
+
+ } else if ($authorEl = $this->_child($entry, self::AUTHOR, self::ATOM)) {
+
+ $this->actor = new ActivityObject($authorEl);
+
+ } else if (!empty($feed) && $authorEl = $this->_child($feed, self::AUTHOR,
+ self::ATOM)) {
+
+ $this->actor = new ActivityObject($authorEl);
+ }
+
+ $contextEl = $this->_child($entry, self::CONTEXT);
+
+ if (!empty($contextEl)) {
+ $this->context = new ActivityContext($contextEl);
+ } else {
+ $this->context = new ActivityContext($entry);
+ }
+
+ $targetEl = $this->_child($entry, self::TARGET);
+
+ if (!empty($targetEl)) {
+ $this->target = new ActivityObject($targetEl);
+ }
+
+ $this->summary = ActivityUtils::childContent($entry, 'summary');
+ $this->id = ActivityUtils::childContent($entry, 'id');
+ $this->content = ActivityUtils::getContent($entry);
+
+ $catEls = $entry->getElementsByTagNameNS(self::ATOM, 'category');
+ if ($catEls) {
+ for ($i = 0; $i < $catEls->length; $i++) {
+ $catEl = $catEls->item($i);
+ $this->categories[] = new AtomCategory($catEl);
+ }
+ }
+ }
+
+ /**
+ * Returns an Atom <entry> based on this activity
+ *
+ * @return DOMElement Atom entry
+ */
+
+ function toAtomEntry()
+ {
+ return null;
+ }
+
+ function asString($namespace=false)
+ {
+ $xs = new XMLStringer(true);
+
+ if ($namespace) {
+ $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
+ 'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
+ 'xmlns:georss' => 'http://www.georss.org/georss',
+ 'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
+ 'xmlns:poco' => 'http://portablecontacts.net/spec/1.0',
+ 'xmlns:media' => 'http://purl.org/syndication/atommedia');
+ } else {
+ $attrs = array();
+ }
+
+ $xs->elementStart('entry', $attrs);
+
+ $xs->element('id', null, $this->id);
+ $xs->element('title', null, $this->title);
+ $xs->element('published', null, common_date_iso8601($this->time));
+ $xs->element('content', array('type' => 'html'), $this->content);
+
+ if (!empty($this->summary)) {
+ $xs->element('summary', null, $this->summary);
+ }
+
+ if (!empty($this->link)) {
+ $xs->element('link', array('rel' => 'alternate',
+ 'type' => 'text/html'),
+ $this->link);
+ }
+
+ // XXX: add context
+
+ $xs->elementStart('author');
+ $xs->element('uri', array(), $this->actor->id);
+ if ($this->actor->title) {
+ $xs->element('name', array(), $this->actor->title);
+ }
+ $xs->elementEnd('author');
+ $xs->raw($this->actor->asString('activity:actor'));
+
+ $xs->element('activity:verb', null, $this->verb);
+
+ if ($this->object) {
+ $xs->raw($this->object->asString());
+ }
+
+ if ($this->target) {
+ $xs->raw($this->target->asString('activity:target'));
+ }
+
+ foreach ($this->categories as $cat) {
+ $xs->raw($cat->asString());
+ }
+
+ $xs->elementEnd('entry');
+
+ return $xs->getString();
+ }
+
+ private function _child($element, $tag, $namespace=self::SPEC)
+ {
+ return ActivityUtils::child($element, $tag, $namespace);
+ }
+}
+
+class AtomCategory
+{
+ public $term;
+ public $scheme;
+ public $label;
+
+ function __construct($element=null)
+ {
+ if ($element && $element->attributes) {
+ $this->term = $this->extract($element, 'term');
+ $this->scheme = $this->extract($element, 'scheme');
+ $this->label = $this->extract($element, 'label');
+ }
+ }
+
+ protected function extract($element, $attrib)
+ {
+ $node = $element->attributes->getNamedItemNS(Activity::ATOM, $attrib);
+ if ($node) {
+ return trim($node->textContent);
+ }
+ $node = $element->attributes->getNamedItem($attrib);
+ if ($node) {
+ return trim($node->textContent);
+ }
+ return null;
+ }
+
+ function asString()
+ {
+ $attribs = array();
+ if ($this->term !== null) {
+ $attribs['term'] = $this->term;
+ }
+ if ($this->scheme !== null) {
+ $attribs['scheme'] = $this->scheme;
+ }
+ if ($this->label !== null) {
+ $attribs['label'] = $this->label;
+ }
+ $xs = new XMLStringer();
+ $xs->element('category', $attribs);
+ return $xs->asString();
+ }
+}
diff --git a/lib/api.php b/lib/apiaction.php
index f81975216..d79dc327e 100644
--- a/lib/api.php
+++ b/lib/apiaction.php
@@ -77,6 +77,7 @@ class ApiAction extends Action
function prepare($args)
{
+ StatusNet::setApi(true); // reduce exception reports to aid in debugging
parent::prepare($args);
$this->format = $this->arg('format');
@@ -357,7 +358,7 @@ class ApiAction extends Action
$entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
$entry['published'] = common_date_iso8601($notice->created);
- $taguribase = common_config('integration', 'taguri');
+ $taguribase = TagURI::base();
$entry['id'] = "tag:$taguribase:$entry[link]";
$entry['updated'] = $entry['published'];
@@ -801,7 +802,7 @@ class ApiAction extends Action
$entry['link'] = common_local_url('showmessage', array('message' => $message->id));
$entry['published'] = common_date_iso8601($message->created);
- $taguribase = common_config('integration', 'taguri');
+ $taguribase = TagURI::base();
$entry['id'] = "tag:$taguribase:$entry[link]";
$entry['updated'] = $entry['published'];
@@ -1102,7 +1103,7 @@ class ApiAction extends Action
}
}
- function serverError($msg, $code = 500, $content_type = 'json')
+ function serverError($msg, $code = 500, $content_type = 'xml')
{
$action = $this->trimmed('action');
@@ -1153,7 +1154,6 @@ class ApiAction extends Action
$this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
'xml:lang' => 'en-US',
'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
- Event::handle('StartApiAtom', array($this));
}
function endTwitterAtom()
@@ -1218,7 +1218,12 @@ class ApiAction extends Action
return User_group::staticGet($this->arg('id'));
} else if ($this->arg('id')) {
$nickname = common_canonical_nickname($this->arg('id'));
- return User_group::staticGet('nickname', $nickname);
+ $local = Local_group::staticGet('nickname', $nickname);
+ if (empty($local)) {
+ return null;
+ } else {
+ return User_group::staticGet('id', $local->id);
+ }
} else if ($this->arg('group_id')) {
// This is to ensure that a non-numeric user_id still
// overrides screen_name even if it doesn't get used
@@ -1227,14 +1232,24 @@ class ApiAction extends Action
}
} else if ($this->arg('group_name')) {
$nickname = common_canonical_nickname($this->arg('group_name'));
- return User_group::staticGet('nickname', $nickname);
+ $local = Local_group::staticGet('nickname', $nickname);
+ if (empty($local)) {
+ return null;
+ } else {
+ return User_group::staticGet('id', $local->id);
+ }
}
} else if (is_numeric($id)) {
return User_group::staticGet($id);
} else {
$nickname = common_canonical_nickname($id);
- return User_group::staticGet('nickname', $nickname);
+ $local = Local_group::staticGet('nickname', $nickname);
+ if (empty($local)) {
+ return null;
+ } else {
+ return User_group::staticGet('id', $local->id);
+ }
}
}
@@ -1320,4 +1335,22 @@ class ApiAction extends Action
}
}
+ function getSelfUri($action, $aargs)
+ {
+ parse_str($_SERVER['QUERY_STRING'], $params);
+ $pstring = '';
+ if (!empty($params)) {
+ unset($params['p']);
+ $pstring = http_build_query($params);
+ }
+
+ $uri = common_local_url($action, $aargs);
+
+ if (!empty($pstring)) {
+ $uri .= '?' . $pstring;
+ }
+
+ return $uri;
+ }
+
}
diff --git a/lib/apiauth.php b/lib/apiauth.php
index 25e2196cf..5090871cf 100644
--- a/lib/apiauth.php
+++ b/lib/apiauth.php
@@ -38,7 +38,6 @@ if (!defined('STATUSNET')) {
exit(1);
}
-require_once INSTALLDIR . '/lib/api.php';
require_once INSTALLDIR . '/lib/apioauth.php';
/**
diff --git a/lib/atom10entry.php b/lib/atom10entry.php
new file mode 100644
index 000000000..f8f16d594
--- /dev/null
+++ b/lib/atom10entry.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building / manipulating an Atom entry in memory
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+class Atom10EntryException extends Exception
+{
+}
+
+/**
+ * Class for manipulating an Atom entry in memory. Get the entry as an XML
+ * string with Atom10Entry::getString().
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class Atom10Entry extends XMLStringer
+{
+ private $namespaces;
+ private $categories;
+ private $content;
+ private $contributors;
+ private $id;
+ private $links;
+ private $published;
+ private $rights;
+ private $source;
+ private $summary;
+ private $title;
+
+ function __construct($indent = true) {
+ parent::__construct($indent);
+ $this->namespaces = array();
+ }
+
+ function addNamespace($namespace, $uri)
+ {
+ $ns = array($namespace => $uri);
+ $this->namespaces = array_merge($this->namespaces, $ns);
+ }
+
+ function initEntry()
+ {
+
+ }
+
+ function endEntry()
+ {
+
+ }
+
+ /**
+ * Check that all required elements have been set, etc.
+ * Throws an Atom10EntryException if something's missing.
+ *
+ * @return void
+ */
+ function validate()
+ {
+
+ }
+
+ function getString()
+ {
+ $this->validate();
+
+ $this->initEntry();
+ $this->renderEntries();
+ $this->endEntry();
+
+ return $this->xw->outputMemory();
+ }
+
+} \ No newline at end of file
diff --git a/lib/atom10feed.php b/lib/atom10feed.php
new file mode 100644
index 000000000..8842840d5
--- /dev/null
+++ b/lib/atom10feed.php
@@ -0,0 +1,305 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building an Atom feed in memory
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET'))
+{
+ exit(1);
+}
+
+class Atom10FeedException extends Exception
+{
+}
+
+/**
+ * Class for building an Atom feed in memory. Get the finished doc
+ * as a string with Atom10Feed::getString().
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class Atom10Feed extends XMLStringer
+{
+ public $xw;
+ private $namespaces;
+ private $authors;
+ private $subject;
+ private $categories;
+ private $contributors;
+ private $generator;
+ private $icon;
+ private $links;
+ private $logo;
+ private $rights;
+ private $subtitle;
+ private $title;
+ private $published;
+ private $updated;
+ private $entries;
+
+ /**
+ * Constructor
+ *
+ * @param boolean $indent flag to turn indenting on or off
+ *
+ * @return void
+ */
+ function __construct($indent = true) {
+ parent::__construct($indent);
+ $this->namespaces = array();
+ $this->authors = array();
+ $this->links = array();
+ $this->entries = array();
+ $this->addNamespace('', 'http://www.w3.org/2005/Atom');
+ }
+
+ /**
+ * Add another namespace to the feed
+ *
+ * @param string $namespace the namespace
+ * @param string $uri namspace uri
+ *
+ * @return void
+ */
+ function addNamespace($namespace, $uri)
+ {
+ $ns = array($namespace => $uri);
+ $this->namespaces = array_merge($this->namespaces, $ns);
+ }
+
+ function addAuthor($name, $uri = null, $email = null)
+ {
+ $xs = new XMLStringer(true);
+
+ $xs->elementStart('author');
+
+ if (!empty($name)) {
+ $xs->element('name', null, $name);
+ } else {
+ throw new Atom10FeedException(
+ 'author element must contain a name element.'
+ );
+ }
+
+ if (isset($uri)) {
+ $xs->element('uri', null, $uri);
+ }
+
+ if (isset($email)) {
+ $xs->element('email', null, $email);
+ }
+
+ $xs->elementEnd('author');
+
+ array_push($this->authors, $xs->getString());
+ }
+
+ /**
+ * Add an Author to the feed via raw XML string
+ *
+ * @param string $xmlAuthor An XML string representation author
+ *
+ * @return void
+ */
+ function addAuthorRaw($xmlAuthor)
+ {
+ array_push($this->authors, $xmlAuthor);
+ }
+
+ function renderAuthors()
+ {
+ foreach ($this->authors as $author) {
+ $this->raw($author);
+ }
+ }
+
+ /**
+ * Add a activity feed subject via raw XML string
+ *
+ * @param string $xmlSubject An XML string representation of the subject
+ *
+ * @return void
+ */
+ function setActivitySubject($xmlSubject)
+ {
+ $this->subject = $xmlSubject;
+ }
+
+ function getNamespaces()
+ {
+ return $this->namespaces;
+ }
+
+ function initFeed()
+ {
+ $this->xw->startDocument('1.0', 'UTF-8');
+ $commonAttrs = array('xml:lang' => 'en-US');
+ foreach ($this->namespaces as $prefix => $uri) {
+ if ($prefix == '') {
+ $attr = 'xmlns';
+ } else {
+ $attr = 'xmlns:' . $prefix;
+ }
+ $commonAttrs[$attr] = $uri;
+ }
+ $this->elementStart('feed', $commonAttrs);
+
+ $this->element('id', null, $this->id);
+ $this->element('title', null, $this->title);
+ $this->element('subtitle', null, $this->subtitle);
+
+ if (!empty($this->logo)) {
+ $this->element('logo', null, $this->logo);
+ }
+
+ $this->element('updated', null, $this->updated);
+
+ $this->renderAuthors();
+
+ $this->renderLinks();
+ }
+
+ /**
+ * Check that all required elements have been set, etc.
+ * Throws an Atom10FeedException if something's missing.
+ *
+ * @return void
+ */
+ function validate()
+ {
+ }
+
+ function renderLinks()
+ {
+ foreach ($this->links as $attrs)
+ {
+ $this->element('link', $attrs, null);
+ }
+ }
+
+ function addEntryRaw($xmlEntry)
+ {
+ array_push($this->entries, $xmlEntry);
+ }
+
+ function addEntry($entry)
+ {
+ array_push($this->entries, $entry->getString());
+ }
+
+ function renderEntries()
+ {
+ foreach ($this->entries as $entry) {
+ $this->raw($entry);
+ }
+ }
+
+ function endFeed()
+ {
+ $this->elementEnd('feed');
+ $this->xw->endDocument();
+ }
+
+ function getString()
+ {
+ if (Event::handle('StartApiAtom', array($this))) {
+
+ $this->validate();
+ $this->initFeed();
+
+ if (!empty($this->subject)) {
+ $this->raw($this->subject);
+ }
+
+ $this->renderEntries();
+ $this->endFeed();
+
+ Event::handle('EndApiAtom', array($this));
+ }
+
+ return $this->xw->outputMemory();
+ }
+
+ function setId($id)
+ {
+ $this->id = $id;
+ }
+
+ function setTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ function setSubtitle($subtitle)
+ {
+ $this->subtitle = $subtitle;
+ }
+
+ function setLogo($logo)
+ {
+ $this->logo = $logo;
+ }
+
+ function setUpdated($dt)
+ {
+ $this->updated = common_date_iso8601($dt);
+ }
+
+ function setPublished($dt)
+ {
+ $this->published = common_date_iso8601($dt);
+ }
+
+ /**
+ * Adds a link element into the Atom document
+ *
+ * Assumes you want rel="alternate" and type="text/html" unless
+ * you send in $otherAttrs.
+ *
+ * @param string $uri the uri the href needs to point to
+ * @param array $otherAttrs other attributes to stick in
+ *
+ * @return void
+ */
+ function addLink($uri, $otherAttrs = null) {
+ $attrs = array('href' => $uri);
+
+ if (is_null($otherAttrs)) {
+ $attrs['rel'] = 'alternate';
+ $attrs['type'] = 'text/html';
+ } else {
+ $attrs = array_merge($attrs, $otherAttrs);
+ }
+
+ array_push($this->links, $attrs);
+ }
+
+}
diff --git a/lib/atomgroupnoticefeed.php b/lib/atomgroupnoticefeed.php
new file mode 100644
index 000000000..52ee4c7d6
--- /dev/null
+++ b/lib/atomgroupnoticefeed.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building an in-memory Atom feed for a particular group's
+ * timeline.
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET'))
+{
+ exit(1);
+}
+
+/**
+ * Class for group notice feeds. May contains a reference to the group.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class AtomGroupNoticeFeed extends AtomNoticeFeed
+{
+ private $group;
+
+ /**
+ * Constructor
+ *
+ * @param Group $group the group for the feed (optional)
+ * @param boolean $indent flag to turn indenting on or off
+ *
+ * @return void
+ */
+ function __construct($group = null, $indent = true) {
+ parent::__construct($indent);
+ $this->group = $group;
+ }
+
+ function getGroup()
+ {
+ return $this->group;
+ }
+
+}
diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php
new file mode 100644
index 000000000..3c3556cb9
--- /dev/null
+++ b/lib/atomnoticefeed.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building an Atom feed from a collection of notices
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET'))
+{
+ exit(1);
+}
+
+/**
+ * Class for creating a feed that represents a collection of notices. Builds the
+ * feed in memory. Get the feed as a string with AtomNoticeFeed::getString().
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class AtomNoticeFeed extends Atom10Feed
+{
+ function __construct($indent = true) {
+ parent::__construct($indent);
+
+ // Feeds containing notice info use these namespaces
+
+ $this->addNamespace(
+ 'thr',
+ 'http://purl.org/syndication/thread/1.0'
+ );
+
+ $this->addNamespace(
+ 'georss',
+ 'http://www.georss.org/georss'
+ );
+
+ $this->addNamespace(
+ 'activity',
+ 'http://activitystrea.ms/spec/1.0/'
+ );
+
+ $this->addNamespace(
+ 'media',
+ 'http://purl.org/syndication/atommedia'
+ );
+
+ $this->addNamespace(
+ 'poco',
+ 'http://portablecontacts.net/spec/1.0'
+ );
+
+ // XXX: What should the uri be?
+ $this->addNamespace(
+ 'ostatus',
+ 'http://ostatus.org/schema/1.0'
+ );
+ }
+
+ /**
+ * Add more than one Notice to the feed
+ *
+ * @param mixed $notices an array of Notice objects or handle
+ *
+ */
+ function addEntryFromNotices($notices)
+ {
+ if (is_array($notices)) {
+ foreach ($notices as $notice) {
+ $this->addEntryFromNotice($notice);
+ }
+ } else {
+ while ($notices->fetch()) {
+ $this->addEntryFromNotice($notices);
+ }
+ }
+ }
+
+ /**
+ * Add a single Notice to the feed
+ *
+ * @param Notice $notice a Notice to add
+ */
+ function addEntryFromNotice($notice)
+ {
+ $this->addEntryRaw($notice->asAtomEntry());
+ }
+
+}
+
+
diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php
new file mode 100644
index 000000000..2ad8de455
--- /dev/null
+++ b/lib/atomusernoticefeed.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for building an in-memory Atom feed for a particular user's
+ * timeline.
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET'))
+{
+ exit(1);
+}
+
+/**
+ * Class for user notice feeds. May contain a reference to the user.
+ *
+ * @category Feed
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class AtomUserNoticeFeed extends AtomNoticeFeed
+{
+ private $user;
+
+ /**
+ * Constructor
+ *
+ * @param User $user the user for the feed (optional)
+ * @param boolean $indent flag to turn indenting on or off
+ *
+ * @return void
+ */
+
+ function __construct($user = null, $indent = true) {
+ parent::__construct($indent);
+ $this->user = $user;
+ if (!empty($user)) {
+ $profile = $user->getProfile();
+ $this->addAuthor($profile->nickname, $user->uri);
+ }
+ }
+
+ function getUser()
+ {
+ return $this->user;
+ }
+}
diff --git a/lib/cache.php b/lib/cache.php
index df6fc3649..c09a1dd9f 100644
--- a/lib/cache.php
+++ b/lib/cache.php
@@ -160,6 +160,32 @@ class Cache
}
/**
+ * Atomically increment an existing numeric value.
+ * Existing expiration time should remain unchanged, if any.
+ *
+ * @param string $key The key to use for lookups
+ * @param int $step Amount to increment (default 1)
+ *
+ * @return mixed incremented value, or false if not set.
+ */
+ function increment($key, $step=1)
+ {
+ $value = false;
+ if (Event::handle('StartCacheIncrement', array(&$key, &$step, &$value))) {
+ // Fallback is not guaranteed to be atomic,
+ // and may original expiry value.
+ $value = $this->get($key);
+ if ($value !== false) {
+ $value += $step;
+ $ok = $this->set($key, $value);
+ $got = $this->get($key);
+ }
+ Event::handle('EndCacheIncrement', array($key, $step, $value));
+ }
+ return $value;
+ }
+
+ /**
* Delete the value associated with a key
*
* @param string $key Key to delete
diff --git a/lib/command.php b/lib/command.php
index 2a51fd687..ea7b60372 100644
--- a/lib/command.php
+++ b/lib/command.php
@@ -548,12 +548,19 @@ class SubCommand extends Command
return;
}
- $result = subs_subscribe_user($this->user, $this->other);
+ $otherUser = User::staticGet('nickname', $this->other);
- if ($result == 'true') {
+ if (empty($otherUser)) {
+ $channel->error($this->user, _('No such user'));
+ return;
+ }
+
+ try {
+ Subscription::start($this->user->getProfile(),
+ $otherUser->getProfile());
$channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
- } else {
- $channel->error($this->user, $result);
+ } catch (Exception $e) {
+ $channel->error($this->user, $e->getMessage());
}
}
}
@@ -576,12 +583,18 @@ class UnsubCommand extends Command
return;
}
- $result=subs_unsubscribe_user($this->user, $this->other);
+ $otherUser = User::staticGet('nickname', $this->other);
- if ($result) {
+ if (empty($otherUser)) {
+ $channel->error($this->user, _('No such user'));
+ }
+
+ try {
+ Subscription::cancel($this->user->getProfile(),
+ $otherUser->getProfile());
$channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
- } else {
- $channel->error($this->user, $result);
+ } catch (Exception $e) {
+ $channel->error($this->user, $e->getMessage());
}
}
}
diff --git a/lib/common.php b/lib/common.php
index b95cd1175..2dbe3b3c5 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -22,7 +22,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
//exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
-define('STATUSNET_VERSION', '0.9.0beta5');
+define('STATUSNET_VERSION', '0.9.0beta6');
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Stand');
@@ -123,6 +123,7 @@ require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
+require_once INSTALLDIR.'/lib/activity.php';
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php
index c6350fc66..3032e4ec7 100644
--- a/lib/dbqueuemanager.php
+++ b/lib/dbqueuemanager.php
@@ -72,7 +72,7 @@ class DBQueueManager extends QueueManager
public function poll()
{
$this->_log(LOG_DEBUG, 'Checking for notices...');
- $qi = Queue_item::top($this->getQueues());
+ $qi = Queue_item::top($this->activeQueues());
if (empty($qi)) {
$this->_log(LOG_DEBUG, 'No notices waiting; idling.');
return false;
@@ -142,9 +142,4 @@ class DBQueueManager extends QueueManager
$this->stats('error', $queue);
}
-
- protected function _log($level, $msg)
- {
- common_log($level, 'DBQueueManager: '.$msg);
- }
}
diff --git a/lib/default.php b/lib/default.php
index 485a08ba4..d849055c2 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -81,15 +81,25 @@ $default =
'subsystem' => 'db', # default to database, or 'stomp'
'stomp_server' => null,
'queue_basename' => '/queue/statusnet/',
- 'control_channel' => '/topic/statusnet-control', // broadcasts to all queue daemons
+ 'control_channel' => '/topic/statusnet/control', // broadcasts to all queue daemons
'stomp_username' => null,
'stomp_password' => null,
'stomp_persistent' => true, // keep items across queue server restart, if persistence is enabled
'stomp_manual_failover' => true, // if multiple servers are listed, treat them as separate (enqueue on one randomly, listen on all)
'monitor' => null, // URL to monitor ping endpoint (work in progress)
'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully
+ '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(), // List queue specifiers to break out when using Stomp queue.
+ // Default will share all queues for all sites within each group.
+ // Specify as <group>/<queue> or <group>/<queue>/<site>,
+ // 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)
),
'license' =>
array('type' => 'cc', # can be 'cc', 'allrightsreserved', 'private'
@@ -110,11 +120,13 @@ $default =
'avatar' =>
array('server' => null,
'dir' => INSTALLDIR . '/avatar/',
- 'path' => $_path . '/avatar/'),
+ 'path' => $_path . '/avatar/',
+ 'ssl' => null),
'background' =>
array('server' => null,
'dir' => INSTALLDIR . '/background/',
- 'path' => $_path . '/background/'),
+ 'path' => $_path . '/background/',
+ 'ssl' => null),
'public' =>
array('localonly' => true,
'blacklist' => array(),
@@ -122,10 +134,12 @@ $default =
'theme' =>
array('server' => null,
'dir' => null,
- 'path'=> null),
+ 'path'=> null,
+ 'ssl' => null),
'javascript' =>
array('server' => null,
- 'path'=> null),
+ 'path'=> null,
+ 'ssl' => null),
'throttle' =>
array('enabled' => false, // whether to throttle edits; false by default
'count' => 20, // number of allowed messages in timespan
@@ -161,7 +175,7 @@ $default =
array('enabled' => false),
'integration' =>
array('source' => 'StatusNet', # source attribute for Twitter
- 'taguri' => $_server.',2009'), # base for tag URIs
+ 'taguri' => null), # base for tag URIs
'twitter' =>
array('enabled' => true,
'consumer_key' => null,
@@ -183,6 +197,7 @@ $default =
array('server' => null,
'dir' => INSTALLDIR . '/file/',
'path' => $_path . '/file/',
+ 'ssl' => null,
'supported' => array('image/png',
'image/jpeg',
'image/gif',
@@ -263,7 +278,6 @@ $default =
'TightUrl' => array('shortenerName' => '2tu.us', 'freeService' => true,'serviceUrl'=>'http://2tu.us/?save=y&url=%1$s'),
'Geonames' => null,
'Mapstraction' => null,
- 'Linkback' => null,
'WikiHashtags' => null,
'OpenID' => null),
),
diff --git a/lib/distribqueuehandler.php b/lib/distribqueuehandler.php
index 4477468d0..d2be7a92c 100644
--- a/lib/distribqueuehandler.php
+++ b/lib/distribqueuehandler.php
@@ -63,31 +63,7 @@ class DistribQueueHandler
// XXX: do we need to change this for remote users?
try {
- $notice->saveTags();
- } catch (Exception $e) {
- $this->logit($notice, $e);
- }
-
- try {
- $groups = $notice->saveGroups();
- } catch (Exception $e) {
- $this->logit($notice, $e);
- }
-
- try {
- $recipients = $notice->saveReplies();
- } catch (Exception $e) {
- $this->logit($notice, $e);
- }
-
- try {
- $notice->addToInboxes($groups, $recipients);
- } catch (Exception $e) {
- $this->logit($notice, $e);
- }
-
- try {
- $notice->saveUrls();
+ $notice->addToInboxes();
} catch (Exception $e) {
$this->logit($notice, $e);
}
@@ -107,7 +83,7 @@ class DistribQueueHandler
return true;
}
-
+
protected function logit($notice, $e)
{
common_log(LOG_ERR, "Distrib queue exception saving notice $notice->id: " .
diff --git a/lib/error.php b/lib/error.php
index 87a4d913b..a6a29119f 100644
--- a/lib/error.php
+++ b/lib/error.php
@@ -56,6 +56,7 @@ class ErrorAction extends Action
$this->code = $code;
$this->message = $message;
+ $this->minimal = StatusNet::isApi();
// XXX: hack alert: usually we aren't going to
// call this page directly, but because it's
@@ -102,7 +103,14 @@ class ErrorAction extends Action
function showPage()
{
- parent::showPage();
+ if ($this->minimal) {
+ // Even more minimal -- we're in a machine API
+ // and don't want to flood the output.
+ $this->extraHeaders();
+ $this->showContent();
+ } else {
+ parent::showPage();
+ }
// We don't want to have any more output after this
exit();
diff --git a/lib/grouplist.php b/lib/grouplist.php
index 99bff9cdc..854bc34e2 100644
--- a/lib/grouplist.php
+++ b/lib/grouplist.php
@@ -105,6 +105,7 @@ class GroupList extends Widget
'alt' =>
($this->group->fullname) ? $this->group->fullname :
$this->group->nickname));
+ $this->out->text(' ');
$hasFN = ($this->group->fullname) ? 'nickname' : 'fn org nickname';
$this->out->elementStart('span', $hasFN);
$this->out->raw($this->highlight($this->group->nickname));
@@ -112,16 +113,19 @@ class GroupList extends Widget
$this->out->elementEnd('a');
if ($this->group->fullname) {
+ $this->out->text(' ');
$this->out->elementStart('span', 'fn org');
$this->out->raw($this->highlight($this->group->fullname));
$this->out->elementEnd('span');
}
if ($this->group->location) {
+ $this->out->text(' ');
$this->out->elementStart('span', 'label');
$this->out->raw($this->highlight($this->group->location));
$this->out->elementEnd('span');
}
if ($this->group->homepage) {
+ $this->out->text(' ');
$this->out->elementStart('a', array('href' => $this->group->homepage,
'class' => 'url'));
$this->out->raw($this->highlight($this->group->homepage));
diff --git a/lib/groupsection.php b/lib/groupsection.php
index 7327f9e1a..3b0b3029d 100644
--- a/lib/groupsection.php
+++ b/lib/groupsection.php
@@ -85,9 +85,9 @@ class GroupSection extends Section
'href' => $group->homeUrl(),
'rel' => 'contact group',
'class' => 'url'));
+ $this->out->text(' ');
$logo = ($group->stream_logo) ?
$group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
-
$this->out->element('img', array('src' => $logo,
'width' => AVATAR_MINI_SIZE,
'height' => AVATAR_MINI_SIZE,
@@ -95,6 +95,7 @@ class GroupSection extends Section
'alt' => ($group->fullname) ?
$group->fullname :
$group->nickname));
+ $this->out->text(' ');
$this->out->element('span', 'fn org nickname', $group->nickname);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 317f5ea61..7315fe2ad 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -376,9 +376,20 @@ class HTMLOutputter extends XMLOutputter
$server = common_config('site', 'server');
}
- // XXX: protocol
+ $ssl = common_config('javascript', 'ssl');
+
+ if (is_null($ssl)) { // null -> guess
+ if (common_config('site', 'ssl') == 'always' &&
+ !common_config('javascript', 'server')) {
+ $ssl = true;
+ } else {
+ $ssl = false;
+ }
+ }
+
+ $protocol = ($ssl) ? 'https' : 'http';
- $src = 'http://'.$server.$path.$src . '?version=' . STATUSNET_VERSION;
+ $src = $protocol.'://'.$server.$path.$src . '?version=' . STATUSNET_VERSION;
}
$this->element('script', array('type' => $type,
@@ -428,7 +439,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/lib/httpclient.php b/lib/httpclient.php
index 3f8262076..4c3af8d7d 100644
--- a/lib/httpclient.php
+++ b/lib/httpclient.php
@@ -81,12 +81,13 @@ class HTTPResponse extends HTTP_Request2_Response
}
/**
- * Check if the response is OK, generally a 200 status code.
+ * Check if the response is OK, generally a 200 or other 2xx status code.
* @return bool
*/
function isOk()
{
- return ($this->getStatus() == 200);
+ $status = $this->getStatus();
+ return ($status >= 200 && $status < 300);
}
}
diff --git a/lib/iomanager.php b/lib/iomanager.php
index ee2ff958b..217599a6d 100644
--- a/lib/iomanager.php
+++ b/lib/iomanager.php
@@ -59,9 +59,10 @@ abstract class IoManager
* your manager about each site you'll have to handle so you
* can do any necessary per-site setup.
*
- * @param string $site target site server name
+ * The new site will be the currently live configuration during
+ * this call.
*/
- public function addSite($site)
+ public function addSite()
{
/* no-op */
}
diff --git a/lib/iomaster.php b/lib/iomaster.php
index bcab3542b..d20837ba5 100644
--- a/lib/iomaster.php
+++ b/lib/iomaster.php
@@ -55,84 +55,47 @@ abstract class IoMaster
if ($multiSite !== null) {
$this->multiSite = $multiSite;
}
- if ($this->multiSite) {
- $this->sites = $this->findAllSites();
- } else {
- $this->sites = array(common_config('site', 'server'));
- }
-
- if (empty($this->sites)) {
- throw new Exception("Empty status_network table, cannot init");
- }
- foreach ($this->sites as $site) {
- if ($site != common_config('site', 'server')) {
- StatusNet::init($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();
/**
- * Pull all local sites from status_network table.
- * @return array of hostnames
- */
- protected function findAllSites()
- {
- $hosts = array();
- $sn = new Status_network();
- $sn->find();
- while ($sn->fetch()) {
- $hosts[] = $sn->getServerName();
- }
- return $hosts;
- }
-
- /**
* Instantiate an i/o manager class for the current site.
* If a multi-site capable handler is already present,
* we don't need to build a new one.
*
- * @param string $class
+ * @param mixed $manager class name (to run $class::get()) or object
*/
- protected function instantiate($class)
+ protected function instantiate($manager)
{
- if (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'));
- return;
+ if (is_string($manager)) {
+ $manager = call_user_func(array($class, 'get'));
}
- $manager = $this->getManager($class);
-
- if ($this->multiSite) {
- $caps = $manager->multiSite();
- if ($caps == IoManager::SINGLE_ONLY) {
+ $caps = $manager->multiSite();
+ if ($caps == IoManager::SINGLE_ONLY) {
+ if ($this->multiSite) {
throw new Exception("$class can't run with --all; aborting.");
}
- if ($caps == IoManager::INSTANCE_PER_PROCESS) {
- // Save this guy for later!
- // We'll only need the one to cover multiple sites.
- $this->singletons[$class] = $manager;
- $manager->addSite(common_config('site', 'server'));
- }
+ } else if ($caps == IoManager::INSTANCE_PER_PROCESS) {
+ $manager->addSite();
}
- $this->managers[] = $manager;
- }
-
- protected function getManager($class)
- {
- return call_user_func(array($class, 'get'));
+ if (!in_array($manager, $this->managers, true)) {
+ // Only need to save singletons once
+ $this->managers[] = $manager;
+ }
}
/**
@@ -146,6 +109,7 @@ abstract class IoMaster
{
$this->logState('init');
$this->start();
+ $this->checkMemory(false);
while (!$this->shutdown) {
$timeouts = array_values($this->pollTimeouts);
@@ -209,17 +173,24 @@ abstract class IoMaster
/**
* Check runtime memory usage, possibly triggering a graceful shutdown
* and thread respawn if we've crossed the soft limit.
+ *
+ * @param boolean $respawn if false we'll shut down instead of respawning
*/
- protected function checkMemory()
+ protected function checkMemory($respawn=true)
{
$memoryLimit = $this->softMemoryLimit();
if ($memoryLimit > 0) {
$usage = memory_get_usage();
if ($usage > $memoryLimit) {
common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting.");
- $this->requestRestart();
+ if ($respawn) {
+ $this->requestRestart();
+ } else {
+ $this->requestShutdown();
+ }
} else if (common_config('queue', 'debug_memory')) {
- common_log(LOG_DEBUG, "Memory usage $usage");
+ $fmt = number_format($usage);
+ common_log(LOG_DEBUG, "Memory usage $fmt");
}
}
}
diff --git a/lib/joinform.php b/lib/joinform.php
index aefb553aa..aa8bc20e2 100644
--- a/lib/joinform.php
+++ b/lib/joinform.php
@@ -100,7 +100,7 @@ class JoinForm extends Form
function action()
{
return common_local_url('joingroup',
- array('nickname' => $this->group->nickname));
+ array('id' => $this->group->id));
}
/**
diff --git a/lib/leaveform.php b/lib/leaveform.php
index e63d96ee8..5469b5704 100644
--- a/lib/leaveform.php
+++ b/lib/leaveform.php
@@ -100,7 +100,7 @@ class LeaveForm extends Form
function action()
{
return common_local_url('leavegroup',
- array('nickname' => $this->group->nickname));
+ array('id' => $this->group->id));
}
/**
diff --git a/lib/mysqlschema.php b/lib/mysqlschema.php
index 1f7c3d092..485096ac4 100644
--- a/lib/mysqlschema.php
+++ b/lib/mysqlschema.php
@@ -213,6 +213,7 @@ class MysqlSchema extends Schema
$sql .= "); ";
+ common_log(LOG_INFO, $sql);
$res = $this->conn->query($sql);
if (PEAR::isError($res)) {
diff --git a/lib/noticelist.php b/lib/noticelist.php
index a4a0f2651..28a563d87 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -294,6 +294,7 @@ class NoticeListItem extends Widget
}
$this->out->elementStart('a', $attrs);
$this->showAvatar();
+ $this->out->text(' ');
$this->showNickname();
$this->out->elementEnd('a');
$this->out->elementEnd('span');
@@ -379,12 +380,12 @@ class NoticeListItem extends Widget
function showNoticeLink()
{
- if($this->notice->is_local == Notice::LOCAL_PUBLIC || $this->notice->is_local == Notice::LOCAL_NONPUBLIC){
- $noticeurl = common_local_url('shownotice',
- array('notice' => $this->notice->id));
- }else{
- $noticeurl = $this->notice->uri;
- }
+ $noticeurl = $this->notice->bestUrl();
+
+ // above should always return an URL
+
+ assert(!empty($noticeurl));
+
$this->out->elementStart('a', array('rel' => 'bookmark',
'class' => 'timestamp',
'href' => $noticeurl));
@@ -432,17 +433,20 @@ class NoticeListItem extends Widget
$url = $location->getUrl();
+ $this->out->text(' ');
$this->out->elementStart('span', array('class' => 'location'));
$this->out->text(_('at'));
+ $this->out->text(' ');
if (empty($url)) {
- $this->out->element('span', array('class' => 'geo',
+ $this->out->element('abbr', array('class' => 'geo',
'title' => $latlon),
$name);
} else {
- $this->out->element('a', array('class' => 'geo',
- 'title' => $latlon,
- 'href' => $url),
+ $this->out->elementStart('a', array('href' => $url));
+ $this->out->element('abbr', array('class' => 'geo',
+ 'title' => $latlon),
$name);
+ $this->out->elementEnd('a');
}
$this->out->elementEnd('span');
}
@@ -473,9 +477,11 @@ class NoticeListItem extends Widget
function showNoticeSource()
{
if ($this->notice->source) {
+ $this->out->text(' ');
$this->out->elementStart('span', 'source');
$this->out->text(_('from'));
$source_name = _($this->notice->source);
+ $this->out->text(' ');
switch ($this->notice->source) {
case 'web':
case 'xmpp':
@@ -487,30 +493,34 @@ class NoticeListItem extends Widget
break;
default:
- $name = null;
+ $name = $source_name;
$url = null;
- $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 (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;
+ }
}
}
+ 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;
}
@@ -540,6 +550,7 @@ class NoticeListItem extends Widget
}
}
if ($hasConversation){
+ $this->out->text(' ');
$convurl = common_local_url('conversation',
array('id' => $this->notice->conversation));
$this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id,
@@ -591,12 +602,14 @@ class NoticeListItem extends Widget
function showReplyLink()
{
if (common_logged_in()) {
+ $this->out->text(' ');
$reply_url = common_local_url('newnotice',
array('replyto' => $this->profile->nickname, 'inreplyto' => $this->notice->id));
$this->out->elementStart('a', array('href' => $reply_url,
'class' => 'notice_reply',
'title' => _('Reply to this notice')));
$this->out->text(_('Reply'));
+ $this->out->text(' ');
$this->out->element('span', 'notice_id', $this->notice->id);
$this->out->elementEnd('a');
}
@@ -616,7 +629,7 @@ class NoticeListItem extends Widget
if (!empty($user) &&
($todel->profile_id == $user->id || $user->hasRight(Right::DELETEOTHERSNOTICE))) {
-
+ $this->out->text(' ');
$deleteurl = common_local_url('deletenotice',
array('notice' => $todel->id));
$this->out->element('a', array('href' => $deleteurl,
@@ -635,6 +648,7 @@ class NoticeListItem extends Widget
{
$user = common_current_user();
if ($user && $user->id != $this->notice->profile_id) {
+ $this->out->text(' ');
$profile = $user->getProfile();
if ($profile->hasRepeated($this->notice->id)) {
$this->out->element('span', array('class' => 'repeated',
diff --git a/lib/noticesection.php b/lib/noticesection.php
index 24465f8ba..7157feafc 100644
--- a/lib/noticesection.php
+++ b/lib/noticesection.php
@@ -90,6 +90,7 @@ class NoticeSection extends Section
'alt' => ($profile->fullname) ?
$profile->fullname :
$profile->nickname));
+ $this->out->text(' ');
$this->out->element('span', 'fn nickname', $profile->nickname);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
diff --git a/lib/oauthclient.php b/lib/oauthclient.php
index b22fd7897..bc7587183 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/lib/omb.php b/lib/omb.php
index 0f38a4936..17132a594 100644
--- a/lib/omb.php
+++ b/lib/omb.php
@@ -29,11 +29,9 @@ require_once 'Auth/Yadis/Yadis.php';
function omb_oauth_consumer()
{
- static $con = null;
- if (is_null($con)) {
- $con = new OAuthConsumer(common_root_url(), '');
- }
- return $con;
+ // Don't try to make this static. Leads to issues in
+ // multi-site setups - Z
+ return new OAuthConsumer(common_root_url(), '');
}
function omb_oauth_server()
diff --git a/lib/profilelist.php b/lib/profilelist.php
index 3412d41d1..693cd6449 100644
--- a/lib/profilelist.php
+++ b/lib/profilelist.php
@@ -191,6 +191,7 @@ class ProfileListItem extends Widget
'alt' =>
($this->profile->fullname) ? $this->profile->fullname :
$this->profile->nickname));
+ $this->out->text(' ');
$hasFN = (!empty($this->profile->fullname)) ? 'nickname' : 'fn nickname';
$this->out->elementStart('span', $hasFN);
$this->out->raw($this->highlight($this->profile->nickname));
@@ -201,6 +202,7 @@ class ProfileListItem extends Widget
function showFullName()
{
if (!empty($this->profile->fullname)) {
+ $this->out->text(' ');
$this->out->elementStart('span', 'fn');
$this->out->raw($this->highlight($this->profile->fullname));
$this->out->elementEnd('span');
@@ -210,6 +212,7 @@ class ProfileListItem extends Widget
function showLocation()
{
if (!empty($this->profile->location)) {
+ $this->out->text(' ');
$this->out->elementStart('span', 'location');
$this->out->raw($this->highlight($this->profile->location));
$this->out->elementEnd('span');
@@ -219,6 +222,7 @@ class ProfileListItem extends Widget
function showHomepage()
{
if (!empty($this->profile->homepage)) {
+ $this->out->text(' ');
$this->out->elementStart('a', array('href' => $this->profile->homepage,
'class' => 'url'));
$this->out->raw($this->highlight($this->profile->homepage));
diff --git a/lib/profilequeuehandler.php b/lib/profilequeuehandler.php
new file mode 100644
index 000000000..6ce93229b
--- /dev/null
+++ b/lib/profilequeuehandler.php
@@ -0,0 +1,52 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @package QueueHandler
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+
+class ProfileQueueHandler extends QueueHandler
+{
+
+ function transport()
+ {
+ return 'profile';
+ }
+
+ function handle($profile)
+ {
+ if (!($profile instanceof Profile)) {
+ common_log(LOG_ERR, "Got a bogus profile, not broadcasting");
+ return true;
+ }
+
+ if (Event::handle('StartBroadcastProfile', array($profile))) {
+ require_once(INSTALLDIR.'/lib/omb.php');
+ try {
+ omb_broadcast_profile($profile);
+ } catch (Exception $e) {
+ common_log(LOG_ERR, "Failed sending OMB profiles: " . $e->getMessage());
+ }
+ }
+ Event::handle('EndBroadcastProfile', array($profile));
+ return true;
+ }
+
+}
diff --git a/lib/profilesection.php b/lib/profilesection.php
index 504b1b7f7..a9482cd63 100644
--- a/lib/profilesection.php
+++ b/lib/profilesection.php
@@ -85,6 +85,7 @@ class ProfileSection extends Section
'href' => $profile->profileurl,
'rel' => 'contact member',
'class' => 'url'));
+ $this->out->text(' ');
$avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
$this->out->element('img', array('src' => (($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_MINI_SIZE)),
'width' => AVATAR_MINI_SIZE,
@@ -93,6 +94,7 @@ class ProfileSection extends Section
'alt' => ($profile->fullname) ?
$profile->fullname :
$profile->nickname));
+ $this->out->text(' ');
$this->out->element('span', 'fn nickname', $profile->nickname);
$this->out->elementEnd('a');
$this->out->elementEnd('span');
diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php
index 4b890c4ca..fdd074db2 100644
--- a/lib/queued_xmpp.php
+++ b/lib/queued_xmpp.php
@@ -63,7 +63,7 @@ class Queued_XMPP extends XMPPHP_XMPP
*/
public function send($msg, $timeout=NULL)
{
- $qm = QueueManager::get();
+ $qm = QueueManager::get('xmppout');
$qm->enqueue(strval($msg), 'xmppout');
}
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
index afe710e88..9fdc80110 100644
--- a/lib/queuemanager.php
+++ b/lib/queuemanager.php
@@ -39,9 +39,10 @@ abstract class QueueManager extends IoManager
{
static $qm = null;
- public $master = null;
- public $handlers = array();
- public $groups = array();
+ protected $master = null;
+ protected $handlers = array();
+ protected $groups = array();
+ protected $activeGroups = array();
/**
* Factory function to pull the appropriate QueueManager object
@@ -155,26 +156,26 @@ abstract class QueueManager extends IoManager
}
/**
- * Encode an object for queued storage.
- * Next gen may use serialization.
+ * Encode an object or variable for queued storage.
+ * Notice objects are currently stored as an id reference;
+ * other items are serialized.
*
- * @param mixed $object
+ * @param mixed $item
* @return string
*/
- protected function encode($object)
+ protected function encode($item)
{
- if ($object instanceof Notice) {
- return $object->id;
- } else if (is_string($object)) {
- return $object;
+ if ($item instanceof Notice) {
+ // Backwards compat
+ return $item->id;
} else {
- throw new ServerException("Can't queue this type", 500);
+ return serialize($item);
}
}
/**
* Decode an object from queued storage.
- * Accepts back-compat notice reference entries and strings for now.
+ * Accepts notice reference entries and serialized items.
*
* @param string
* @return mixed
@@ -182,9 +183,23 @@ abstract class QueueManager extends IoManager
protected function decode($frame)
{
if (is_numeric($frame)) {
+ // Back-compat for notices...
return Notice::staticGet(intval($frame));
- } else {
+ } elseif (substr($frame, 0, 1) == '<') {
+ // Back-compat for XML source
return $frame;
+ } else {
+ // Deserialize!
+ #$old = error_reporting();
+ #error_reporting($old & ~E_NOTICE);
+ $out = unserialize($frame);
+ #error_reporting($old);
+
+ if ($out === false && $frame !== 'b:0;') {
+ common_log(LOG_ERR, "Couldn't unserialize queued frame: $frame");
+ return false;
+ }
+ return $out;
}
}
@@ -201,55 +216,67 @@ abstract class QueueManager extends IoManager
if (class_exists($class)) {
return new $class();
} else {
- common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
+ $this->_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
}
} else {
- common_log(LOG_ERR, "Requested handler for unkown queue '$queue'");
+ $this->_log(LOG_ERR, "Requested handler for unkown queue '$queue'");
}
return null;
}
/**
* Get a list of registered queue transport names to be used
- * for this daemon.
+ * for listening in this daemon.
*
* @return array of strings
*/
- function getQueues()
+ function activeQueues()
{
- $group = $this->activeGroup();
- return array_keys($this->groups[$group]);
+ $queues = array();
+ foreach ($this->activeGroups as $group) {
+ if (isset($this->groups[$group])) {
+ $queues = array_merge($queues, $this->groups[$group]);
+ }
+ }
+
+ return array_keys($queues);
}
/**
- * Initialize the list of queue handlers
+ * Initialize the list of queue handlers for the current site.
*
* @event StartInitializeQueueManager
* @event EndInitializeQueueManager
*/
function initialize()
{
- // @fixme we'll want to be able to listen to particular queues...
+ $this->handlers = array();
+ $this->groups = array();
+ $this->groupsByTransport = array();
+
if (Event::handle('StartInitializeQueueManager', array($this))) {
- $this->connect('plugin', 'PluginQueueHandler');
+ $this->connect('distrib', 'DistribQueueHandler');
$this->connect('omb', 'OmbQueueHandler');
$this->connect('ping', 'PingQueueHandler');
- $this->connect('distrib', 'DistribQueueHandler');
if (common_config('sms', 'enabled')) {
$this->connect('sms', 'SmsQueueHandler');
}
+ // Broadcasting profile updates to OMB remote subscribers
+ $this->connect('profile', 'ProfileQueueHandler');
+
// XMPP output handlers...
- $this->connect('jabber', 'JabberQueueHandler');
- $this->connect('public', 'PublicQueueHandler');
- // @fixme this should get an actual queue
- //$this->connect('confirm', 'XmppConfirmHandler');
+ if (common_config('xmpp', 'enabled')) {
+ // Delivery prep, read by queuedaemon.php:
+ $this->connect('jabber', 'JabberQueueHandler');
+ $this->connect('public', 'PublicQueueHandler');
+
+ // Raw output, read by xmppdaemon.php:
+ $this->connect('xmppout', 'XmppOutQueueHandler', 'xmpp');
+ }
// For compat with old plugins not registering their own handlers.
$this->connect('plugin', 'PluginQueueHandler');
-
- $this->connect('xmppout', 'XmppOutQueueHandler', 'xmppdaemon');
-
}
Event::handle('EndInitializeQueueManager', array($this));
}
@@ -262,25 +289,41 @@ abstract class QueueManager extends IoManager
* @param string $class
* @param string $group
*/
- public function connect($transport, $class, $group='queuedaemon')
+ public function connect($transport, $class, $group='main')
{
$this->handlers[$transport] = $class;
$this->groups[$group][$transport] = $class;
+ $this->groupsByTransport[$transport] = $group;
+ }
+
+ /**
+ * Set the active group which will be used for listening.
+ * @param string $group
+ */
+ function setActiveGroup($group)
+ {
+ $this->activeGroups = array($group);
}
/**
- * @return string queue group to use for this request
+ * Set the active group(s) which will be used for listening.
+ * @param array $groups
*/
- function activeGroup()
+ function setActiveGroups($groups)
{
- $group = 'queuedaemon';
- if ($this->master) {
- // hack hack
- if ($this->master instanceof XmppMaster) {
- return 'xmppdaemon';
- }
+ $this->activeGroups = $groups;
+ }
+
+ /**
+ * @return string queue group for this queue
+ */
+ function queueGroup($queue)
+ {
+ if (isset($this->groupsByTransport[$queue])) {
+ return $this->groupsByTransport[$queue];
+ } else {
+ throw new Exception("Requested group for unregistered transport $queue");
}
- return $group;
}
/**
@@ -304,4 +347,15 @@ abstract class QueueManager extends IoManager
$monitor->stats($key, $owners);
}
}
+
+ protected function _log($level, $msg)
+ {
+ $class = get_class($this);
+ if ($this->activeGroups) {
+ $groups = ' (' . implode(',', $this->activeGroups) . ')';
+ } else {
+ $groups = '';
+ }
+ common_log($level, "$class$groups: $msg");
+ }
}
diff --git a/lib/router.php b/lib/router.php
index 987d0152e..abbce041d 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -247,6 +247,9 @@ class Router
$m->connect('group/:nickname/'.$v,
array('action' => $v.'group'),
array('nickname' => '[a-zA-Z0-9]+'));
+ $m->connect('group/:id/id/'.$v,
+ array('action' => $v.'group'),
+ array('id' => '[0-9]+'));
}
foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
@@ -668,7 +671,7 @@ class Router
foreach (array('subscriptions', 'subscribers',
'all', 'foaf', 'xrds',
- 'replies', 'microsummary') as $a) {
+ 'replies', 'microsummary', 'hcard') as $a) {
$m->connect($a,
array('action' => $a,
'nickname' => $nickname));
@@ -734,7 +737,7 @@ class Router
foreach (array('subscriptions', 'subscribers',
'nudge', 'all', 'foaf', 'xrds',
- 'replies', 'inbox', 'outbox', 'microsummary') as $a) {
+ 'replies', 'inbox', 'outbox', 'microsummary', 'hcard') as $a) {
$m->connect(':nickname/'.$a,
array('action' => $a),
array('nickname' => '[a-zA-Z0-9]{1,64}'));
diff --git a/lib/spawningdaemon.php b/lib/spawningdaemon.php
index b1961d688..fd9ae4355 100644
--- a/lib/spawningdaemon.php
+++ b/lib/spawningdaemon.php
@@ -83,24 +83,31 @@ abstract class SpawningDaemon extends Daemon
$this->log(LOG_INFO, "Spawned thread $i as pid $pid");
$children[$i] = $pid;
}
+ sleep(common_config('queue', 'spawndelay'));
}
$this->log(LOG_INFO, "Waiting for children to complete.");
while (count($children) > 0) {
$status = null;
$pid = pcntl_wait($status);
- if ($pid > 0 && pcntl_wifexited($status)) {
- $exitCode = pcntl_wexitstatus($status);
-
+ if ($pid > 0) {
$i = array_search($pid, $children);
if ($i === false) {
- $this->log(LOG_ERR, "Unrecognized child pid $pid exited with status $exitCode");
+ $this->log(LOG_ERR, "Ignoring exit of unrecognized child pid $pid");
continue;
}
+ if (pcntl_wifexited($status)) {
+ $exitCode = pcntl_wexitstatus($status);
+ $info = "status $exitCode";
+ } else if (pcntl_wifsignaled($status)) {
+ $exitCode = self::EXIT_ERR;
+ $signal = pcntl_wtermsig($status);
+ $info = "signal $signal";
+ }
unset($children[$i]);
if ($this->shouldRespawn($exitCode)) {
- $this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; respawing.");
+ $this->log(LOG_INFO, "Thread $i pid $pid exited with $info; respawing.");
$pid = pcntl_fork();
if ($pid < 0) {
@@ -111,6 +118,7 @@ abstract class SpawningDaemon extends Daemon
$this->log(LOG_INFO, "Respawned thread $i as pid $pid");
$children[$i] = $pid;
}
+ sleep(common_config('queue', 'spawndelay'));
} else {
$this->log(LOG_INFO, "Thread $i pid $pid exited with status $exitCode; closing out thread.");
}
diff --git a/lib/statusnet.php b/lib/statusnet.php
index 29e903026..7c4df84b4 100644
--- a/lib/statusnet.php
+++ b/lib/statusnet.php
@@ -30,6 +30,7 @@ global $config, $_server, $_path;
class StatusNet
{
protected static $have_config;
+ protected static $is_api;
/**
* Configure and instantiate a plugin into the current configuration.
@@ -63,7 +64,7 @@ class StatusNet
}
}
if (!class_exists($pluginclass)) {
- throw new ServerException(500, "Plugin $name not found.");
+ throw new ServerException("Plugin $name not found.", 500);
}
}
@@ -102,6 +103,60 @@ class StatusNet
}
/**
+ * Get identifier of the currently active site configuration
+ * @return string
+ */
+ public static function currentSite()
+ {
+ return common_config('site', 'nickname');
+ }
+
+ /**
+ * Change site configuration to site specified by nickname,
+ * if set up via Status_network. If not, sites other than
+ * the current will fail horribly.
+ *
+ * May throw exception or trigger a fatal error if the given
+ * site is missing or configured incorrectly.
+ *
+ * @param string $nickname
+ */
+ public static function switchSite($nickname)
+ {
+ if ($nickname == StatusNet::currentSite()) {
+ return true;
+ }
+
+ $sn = Status_network::staticGet($nickname);
+ if (empty($sn)) {
+ return false;
+ throw new Exception("No such site nickname '$nickname'");
+ }
+
+ $server = $sn->getServerName();
+ StatusNet::init($server);
+ }
+
+ /**
+ * Pull all local sites from status_network table.
+ *
+ * Behavior undefined if site is not configured via Status_network.
+ *
+ * @return array of nicknames
+ */
+ public static function findAllSites()
+ {
+ $sites = array();
+ $sn = new Status_network();
+ $sn->find();
+ while ($sn->fetch()) {
+ $sites[] = $sn->nickname;
+ }
+ return $sites;
+ }
+
+
+ /**
* Fire initialization events for all instantiated plugins.
*/
protected static function initPlugins()
@@ -147,6 +202,16 @@ class StatusNet
return self::$have_config;
}
+ public function isApi()
+ {
+ return self::$is_api;
+ }
+
+ public function setApi($mode)
+ {
+ self::$is_api = $mode;
+ }
+
/**
* Build default configuration array
* @return array
diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php
index 6730cd213..9af8b2f48 100644
--- a/lib/stompqueuemanager.php
+++ b/lib/stompqueuemanager.php
@@ -63,6 +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->breakout = common_config('queue', 'breakout');
}
/**
@@ -75,20 +76,6 @@ class StompQueueManager extends QueueManager
}
/**
- * Record each site we'll be handling input for in this process,
- * so we can listen to the necessary queues for it.
- *
- * @fixme possibly actually do subscription here to save another
- * loop over all sites later?
- * @fixme possibly don't assume it's the current site
- */
- public function addSite($server)
- {
- $this->sites[] = $server;
- $this->initialize();
- }
-
- /**
* Optional; ping any running queue handler daemons with a notification
* such as announcing a new site to handle or requesting clean shutdown.
* This avoids having to restart all the daemons manually to update configs
@@ -107,9 +94,10 @@ class StompQueueManager extends QueueManager
$message .= ':' . $param;
}
$this->_connect();
- $result = $this->_send($this->control,
- $message,
- array ('created' => common_sql_now()));
+ $con = $this->cons[$this->defaultIdx];
+ $result = $con->send($this->control,
+ $message,
+ array ('created' => common_sql_now()));
if ($result) {
$this->_log(LOG_INFO, "Sent control ping to queue daemons: $message");
return true;
@@ -120,59 +108,11 @@ class StompQueueManager extends QueueManager
}
/**
- * Instantiate the appropriate QueueHandler class for the given queue.
+ * Saves an object into the queue item table.
*
+ * @param mixed $object
* @param string $queue
- * @return mixed QueueHandler or null
- */
- function getHandler($queue)
- {
- $handlers = $this->handlers[$this->currentSite()];
- if (isset($handlers[$queue])) {
- $class = $handlers[$queue];
- if (class_exists($class)) {
- return new $class();
- } else {
- common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'");
- }
- } else {
- common_log(LOG_ERR, "Requested handler for unkown queue '$queue'");
- }
- return null;
- }
-
- /**
- * Get a list of all registered queue transport names.
- *
- * @return array of strings
- */
- function getQueues()
- {
- $group = $this->activeGroup();
- $site = $this->currentSite();
- if (empty($this->groups[$site][$group])) {
- return array();
- } else {
- return array_keys($this->groups[$site][$group]);
- }
- }
-
- /**
- * Register a queue transport name and handler class for your plugin.
- * Only registered transports will be reliably picked up!
*
- * @param string $transport
- * @param string $class
- * @param string $group
- */
- public function connect($transport, $class, $group='queuedaemon')
- {
- $this->handlers[$this->currentSite()][$transport] = $class;
- $this->groups[$this->currentSite()][$group][$transport] = $class;
- }
-
- /**
- * Saves a notice object reference into the queue item table.
* @return boolean true on success
* @throws StompException on connection or send error
*/
@@ -191,8 +131,11 @@ class StompQueueManager extends QueueManager
*/
protected function _doEnqueue($object, $queue, $idx)
{
- $msg = $this->encode($object);
$rep = $this->logrep($object);
+ $envelope = array('site' => common_config('site', 'nickname'),
+ 'handler' => $queue,
+ 'payload' => $this->encode($object));
+ $msg = serialize($envelope);
$props = array('created' => common_sql_now());
if ($this->isPersistent($queue)) {
@@ -201,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) {
- common_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;
}
- common_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;
}
@@ -274,12 +218,14 @@ class StompQueueManager extends QueueManager
$idx = $this->connectionFromSocket($socket);
$con = $this->cons[$idx];
$host = $con->getServer();
+ $this->defaultIdx = $idx;
$ok = true;
try {
$frames = $con->readFrames();
} catch (StompException $e) {
- common_log(LOG_ERR, "Lost connection to $host: " . $e->getMessage());
+ $this->_log(LOG_ERR, "Lost connection to $host: " . $e->getMessage());
+ fclose($socket); // ???
$this->cons[$idx] = null;
$this->transaction[$idx] = null;
$this->disconnect[$idx] = time();
@@ -288,14 +234,17 @@ class StompQueueManager extends QueueManager
foreach ($frames as $frame) {
$dest = $frame->headers['destination'];
if ($dest == $this->control) {
- if (!$this->handleControlSignal($idx, $frame)) {
+ if (!$this->handleControlSignal($frame)) {
// We got a control event that requests a shutdown;
// close out and stop handling anything else!
break;
}
} else {
- $ok = $ok && $this->handleItem($idx, $frame);
+ $ok = $this->handleItem($frame) && $ok;
}
+ $this->ack($idx, $frame);
+ $this->commit($idx);
+ $this->begin($idx);
}
return $ok;
}
@@ -332,22 +281,9 @@ class StompQueueManager extends QueueManager
parent::start($master);
$this->_connectAll();
- common_log(LOG_INFO, "Subscribing to $this->control");
- foreach ($this->cons as $con) {
- if ($con) {
- $con->subscribe($this->control);
- }
- }
- if ($this->sites) {
- foreach ($this->sites as $server) {
- StatusNet::init($server);
- $this->doSubscribe();
- }
- } else {
- $this->doSubscribe();
- }
foreach ($this->cons as $i => $con) {
if ($con) {
+ $this->doSubscribe($con);
$this->begin($i);
}
}
@@ -355,9 +291,7 @@ class StompQueueManager extends QueueManager
}
/**
- * Subscribe to all the queues we're going to need to handle...
- *
- * Side effects: in multi-site mode, may reset site configuration.
+ * Close out any active connections.
*
* @return bool return false on failure
*/
@@ -368,30 +302,14 @@ class StompQueueManager extends QueueManager
foreach ($this->cons as $i => $con) {
if ($con) {
$this->rollback($i);
- $con->unsubscribe($this->control);
- }
- }
- if ($this->sites) {
- foreach ($this->sites as $server) {
- StatusNet::init($server);
- $this->doUnsubscribe();
+ $con->disconnect();
+ $this->cons[$i] = null;
}
- } else {
- $this->doUnsubscribe();
}
return true;
}
/**
- * Get identifier of the currently active site configuration
- * @return string
- */
- protected function currentSite()
- {
- return common_config('site', 'server'); // @fixme switch to nickname
- }
-
- /**
* Lazy open a single connection to Stomp queue server.
* If multiple servers are configured, we let the Stomp client library
* worry about finding a working connection among them.
@@ -447,6 +365,10 @@ class StompQueueManager extends QueueManager
}
}
+ /**
+ * Attempt to manually reconnect to the Stomp server for the given
+ * slot. If successful, set up our subscriptions on it.
+ */
protected function _reconnect($idx)
{
try {
@@ -459,17 +381,7 @@ class StompQueueManager extends QueueManager
$this->cons[$idx] = $con;
$this->disconnect[$idx] = null;
- // now we have to listen to everything...
- // @fixme refactor this nicer. :P
- $host = $con->getServer();
- $this->_log(LOG_INFO, "Resubscribing to $this->control on $host");
- $con->subscribe($this->control);
- foreach ($this->subscriptions as $site => $queues) {
- foreach ($queues as $queue) {
- $this->_log(LOG_INFO, "Resubscribing to $queue on $host");
- $con->subscribe($queue);
- }
- }
+ $this->doSubscribe($con);
$this->begin($idx);
} else {
// Try again later...
@@ -493,42 +405,47 @@ class StompQueueManager extends QueueManager
}
/**
- * Subscribe to all enabled notice queues for the current site.
+ * Set up all our raw queue subscriptions on the given connection
+ * @param LiberalStomp $con
*/
- protected function doSubscribe()
+ protected function doSubscribe(LiberalStomp $con)
{
- $site = $this->currentSite();
- $this->_connect();
- foreach ($this->getQueues() as $queue) {
- $rawqueue = $this->queueName($queue);
- $this->subscriptions[$site][$queue] = $rawqueue;
- $this->_log(LOG_INFO, "Subscribing to $rawqueue");
- foreach ($this->cons as $con) {
- if ($con) {
- $con->subscribe($rawqueue);
- }
- }
+ $host = $con->getServer();
+ foreach ($this->subscriptions() as $sub) {
+ $this->_log(LOG_INFO, "Subscribing to $sub on $host");
+ $con->subscribe($sub);
}
}
-
+
/**
- * Subscribe from all enabled notice queues for the current site.
+ * 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 doUnsubscribe()
+ protected function subscriptions()
{
- $site = $this->currentSite();
- $this->_connect();
- if (!empty($this->subscriptions[$site])) {
- foreach ($this->subscriptions[$site] as $queue => $rawqueue) {
- $this->_log(LOG_INFO, "Unsubscribing from $rawqueue");
- foreach ($this->cons as $con) {
- if ($con) {
- $con->unsubscribe($rawqueue);
- }
- }
- unset($this->subscriptions[$site][$queue]);
+ $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);
}
/**
@@ -540,55 +457,41 @@ class StompQueueManager extends QueueManager
* Side effects: in multi-site mode, may reset site configuration to
* match the site that queued the event.
*
- * @param int $idx connection index
* @param StompFrame $frame
- * @return bool
+ * @return bool success
*/
- protected function handleItem($idx, $frame)
+ protected function handleItem($frame)
{
- $this->defaultIdx = $idx;
+ $host = $this->cons[$this->defaultIdx]->getServer();
+ $message = unserialize($frame->body);
+ $site = $message['site'];
+ $queue = $message['handler'];
- list($site, $queue) = $this->parseDestination($frame->headers['destination']);
- if ($site != $this->currentSite()) {
- $this->stats('switch');
- StatusNet::init($site);
+ if ($this->isDeadletter($frame, $message)) {
+ $this->stats('deadletter', $queue);
+ return false;
}
- $host = $this->cons[$idx]->getServer();
- if (is_numeric($frame->body)) {
- $id = intval($frame->body);
- $info = "notice $id posted at {$frame->headers['created']} in queue $queue from $host";
-
- $notice = Notice::staticGet('id', $id);
- if (empty($notice)) {
- $this->_log(LOG_WARNING, "Skipping missing $info");
- $this->ack($idx, $frame);
- $this->commit($idx);
- $this->begin($idx);
- $this->stats('badnotice', $queue);
- return false;
- }
+ // @fixme detect failing site switches
+ $this->switchSite($site);
- $item = $notice;
- } else {
- // @fixme should we serialize, or json, or what here?
- $info = "string posted at {$frame->headers['created']} in queue $queue from $host";
- $item = $frame->body;
+ $item = $this->decode($message['payload']);
+ if (empty($item)) {
+ $this->_log(LOG_ERR, "Skipping empty or deleted item in queue $queue from $host");
+ $this->stats('baditem', $queue);
+ return false;
}
+ $info = $this->logrep($item) . " posted at " .
+ $frame->headers['created'] . " in queue $queue from $host";
+ $this->_log(LOG_DEBUG, "Dequeued $info");
$handler = $this->getHandler($queue);
if (!$handler) {
$this->_log(LOG_ERR, "Missing handler class; skipping $info");
- $this->ack($idx, $frame);
- $this->commit($idx);
- $this->begin($idx);
$this->stats('badhandler', $queue);
return false;
}
- // If there's an exception when handling,
- // log the error and let it get requeued.
-
try {
$ok = $handler->handle($item);
} catch (Exception $e) {
@@ -596,25 +499,80 @@ class StompQueueManager extends QueueManager
$ok = false;
}
- if (!$ok) {
+ if ($ok) {
+ $this->_log(LOG_INFO, "Successfully handled $info");
+ $this->stats('handled', $queue);
+ } else {
$this->_log(LOG_WARNING, "Failed handling $info");
- // FIXME we probably shouldn't have to do
- // this kind of queue management ourselves;
- // if we don't ack, it should resend...
- $this->ack($idx, $frame);
+ // Requeing moves the item to the end of the line for its next try.
+ // @fixme add a manual retry count
$this->enqueue($item, $queue);
- $this->commit($idx);
- $this->begin($idx);
$this->stats('requeued', $queue);
- return false;
}
- $this->_log(LOG_INFO, "Successfully handled $info");
- $this->ack($idx, $frame);
- $this->commit($idx);
- $this->begin($idx);
- $this->stats('handled', $queue);
- return true;
+ return $ok;
+ }
+
+ /**
+ * Check if a redelivered message has been run through enough
+ * that we're going to give up on it.
+ *
+ * @param StompFrame $frame
+ * @param array $message unserialized message body
+ * @return boolean true if we should discard
+ */
+ protected function isDeadLetter($frame, $message)
+ {
+ if (isset($frame->headers['redelivered']) && $frame->headers['redelivered'] == 'true') {
+ // Message was redelivered, possibly indicating a previous failure.
+ $msgId = $frame->headers['message-id'];
+ $site = $message['site'];
+ $queue = $message['handler'];
+ $msgInfo = "message $msgId for $site in queue $queue";
+
+ $deliveries = $this->incDeliveryCount($msgId);
+ if ($deliveries > common_config('queue', 'max_retries')) {
+ $info = "DEAD-LETTER FILE: Gave up after retry $deliveries on $msgInfo";
+
+ $outdir = common_config('queue', 'dead_letter_dir');
+ if ($outdir) {
+ $filename = $outdir . "/$site-$queue-" . rawurlencode($msgId);
+ $info .= ": dumping to $filename";
+ file_put_contents($filename, $message['payload']);
+ }
+
+ common_log(LOG_ERR, $info);
+ return true;
+ } else {
+ common_log(LOG_INFO, "retry $deliveries on $msgInfo");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Update count of times we've re-encountered this message recently,
+ * triggered when we get a message marked as 'redelivered'.
+ *
+ * Requires a CLI-friendly cache configuration.
+ *
+ * @param string $msgId message-id header from message
+ * @return int number of retries recorded
+ */
+ function incDeliveryCount($msgId)
+ {
+ $count = 0;
+ $cache = common_memcache();
+ if ($cache) {
+ $key = 'statusnet:stomp:message-retries:' . $msgId;
+ $count = $cache->increment($key);
+ if (!$count) {
+ $count = 1;
+ $cache->set($key, $count, null, 3600);
+ $got = $cache->get($key);
+ }
+ }
+ return $count;
}
/**
@@ -647,86 +605,90 @@ class StompQueueManager extends QueueManager
} else {
$this->_log(LOG_ERR, "Ignoring unrecognized control message: $message");
}
-
- $this->ack($idx, $frame);
- $this->commit($idx);
- $this->begin($idx);
return $shutdown;
}
/**
- * Set us up with queue subscriptions for a new site added at runtime,
+ * Switch site, if necessary, and reset current handler assignments
+ * @param string $site
+ */
+ function switchSite($site)
+ {
+ if ($site != StatusNet::currentSite()) {
+ $this->stats('switch');
+ StatusNet::switchSite($site);
+ $this->initialize();
+ }
+ }
+
+ /**
+ * (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'));
- $this->doUnsubscribe();
- $this->doSubscribe();
- } 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) {
- $server = $sn->getServerName(); // @fixme do config-by-nick
- StatusNet::init($server);
- if (empty($this->sites[$server])) {
- $this->addSite($server);
- }
- $this->_log(LOG_INFO, "(Re)subscribing to queues for site $nickname / $server");
- $this->doUnsubscribe();
- $this->doSubscribe();
- $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");
}
}
/**
* Combines the queue_basename from configuration with the
- * site server name and queue name to give eg:
+ * group name for this queue to give eg:
*
- * /queue/statusnet/identi.ca/sms
+ * /queue/statusnet/main
+ * /queue/statusnet/main/distrib
+ * /queue/statusnet/xmpp/xmppout/site01
*
* @param string $queue
* @return string
*/
protected function queueName($queue)
{
- return common_config('queue', 'queue_basename') .
- $this->currentSite() . '/' . $queue;
+ $group = $this->queueGroup($queue);
+ $site = StatusNet::currentSite();
+
+ $specs = array("$group/$queue/$site",
+ "$group/$queue");
+ foreach ($specs as $spec) {
+ if (in_array($spec, $this->breakout)) {
+ return $this->base . $spec;
+ }
+ }
+ return $this->base . $group;
}
/**
- * Returns the site and queue name from the server-side queue.
+ * Get the breakout mode for the given queue on the current site.
*
- * @param string queue destination (eg '/queue/statusnet/identi.ca/sms')
- * @return array of site and queue: ('identi.ca','sms') or false if unrecognized
+ * @param string $queue
+ * @return string one of 'shared', 'handler', 'site'
*/
- protected function parseDestination($dest)
+ protected function breakoutMode($queue)
{
- $prefix = common_config('queue', 'queue_basename');
- if (substr($dest, 0, strlen($prefix)) == $prefix) {
- $rest = substr($dest, strlen($prefix));
- return explode("/", $rest, 2);
+ $breakout = common_config('queue', 'breakout');
+ if (isset($breakout[$queue])) {
+ return $breakout[$queue];
+ } else if (isset($breakout['*'])) {
+ return $breakout['*'];
} else {
- common_log(LOG_ERR, "Got a message from unrecognized stomp queue: $dest");
- return array(false, false);
+ return 'shared';
}
}
- function _log($level, $msg)
- {
- common_log($level, 'StompQueueManager: '.$msg);
- }
-
protected function begin($idx)
{
if ($this->useTransactions) {
diff --git a/lib/subs.php b/lib/subs.php
index 5ac1a75a5..1c240c475 100644
--- a/lib/subs.php
+++ b/lib/subs.php
@@ -19,24 +19,6 @@
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-require_once('XMPPHP/XMPP.php');
-
-/* Subscribe $user to nickname $other_nickname
- Returns true or an error message.
-*/
-
-function subs_subscribe_user($user, $other_nickname)
-{
-
- $other = User::staticGet('nickname', $other_nickname);
-
- if (!$other) {
- return _('No such user.');
- }
-
- return subs_subscribe_to($user, $other);
-}
-
/* Subscribe user $user to other user $other.
* Note: $other must be a local user, not a remote profile.
* Because the other way is quite a bit more complicated.
@@ -44,136 +26,20 @@ function subs_subscribe_user($user, $other_nickname)
function subs_subscribe_to($user, $other)
{
- if (!$user->hasRight(Right::SUBSCRIBE)) {
- return _('You have been banned from subscribing.');
- }
-
- if ($user->isSubscribed($other)) {
- return _('Already subscribed!');
- }
-
- if ($other->hasBlocked($user)) {
- return _('User has blocked you.');
- }
-
try {
- if (Event::handle('StartSubscribe', array($user, $other))) {
-
- if (!$user->subscribeTo($other)) {
- return _('Could not subscribe.');
- return;
- }
-
- subs_notify($other, $user);
-
- $cache = common_memcache();
-
- if ($cache) {
- $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
- }
-
- $profile = $user->getProfile();
-
- $profile->blowSubscriptionsCount();
- $other->blowSubscribersCount();
-
- if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
- if (!$other->subscribeTo($user)) {
- return _('Could not subscribe other to you.');
- }
- $cache = common_memcache();
-
- if ($cache) {
- $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id));
- }
-
- subs_notify($user, $other);
- }
-
- Event::handle('EndSubscribe', array($user, $other));
- }
+ Subscription::start($user->getProfile(), $other);
+ return true;
} catch (Exception $e) {
return $e->getMessage();
}
-
- return true;
-}
-
-function subs_notify($listenee, $listener)
-{
- # XXX: add other notifications (Jabber, SMS) here
- # XXX: queue this and handle it offline
- # XXX: Whatever happens, do it in Twitter-like API, too
- subs_notify_email($listenee, $listener);
-}
-
-function subs_notify_email($listenee, $listener)
-{
- mail_subscribe_notify($listenee, $listener);
-}
-
-/* Unsubscribe $user from nickname $other_nickname
- Returns true or an error message.
-*/
-
-function subs_unsubscribe_user($user, $other_nickname)
-{
-
- $other = User::staticGet('nickname', $other_nickname);
-
- if (!$other) {
- return _('No such user.');
- }
-
- return subs_unsubscribe_to($user, $other->getProfile());
}
-/* Unsubscribe user $user from profile $other
- * NB: other can be a remote user. */
-
function subs_unsubscribe_to($user, $other)
{
- if (!$user->isSubscribed($other))
- return _('Not subscribed!');
-
- // Don't allow deleting self subs
-
- if ($user->id == $other->id) {
- return _('Couldn\'t delete self-subscription.');
- }
-
try {
- if (Event::handle('StartUnsubscribe', array($user, $other))) {
-
- $sub = DB_DataObject::factory('subscription');
-
- $sub->subscriber = $user->id;
- $sub->subscribed = $other->id;
-
- $sub->find(true);
-
- // note we checked for existence above
-
- if (!$sub->delete())
- return _('Couldn\'t delete subscription.');
-
- $cache = common_memcache();
-
- if ($cache) {
- $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
- }
-
- $profile = $user->getProfile();
-
- $profile->blowSubscriptionsCount();
- $other->blowSubscribersCount();
-
- Event::handle('EndUnsubscribe', array($user, $other));
- }
+ Subscription::cancel($user->getProfile(), $other);
+ return true;
} catch (Exception $e) {
return $e->getMessage();
}
-
- return true;
-}
-
+} \ No newline at end of file
diff --git a/lib/taguri.php b/lib/taguri.php
new file mode 100644
index 000000000..d8398eded
--- /dev/null
+++ b/lib/taguri.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Utility for creating new tag: URIs
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category URI
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Mint tag: URIs
+ *
+ * tag: URIs are unique identifiers according to http://tools.ietf.org/html/rfc4151.
+ *
+ * We use them for creating URIs for things that can't be HTTP retrieved.
+ *
+ * @category URI
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link http://status.net/
+ */
+
+class TagURI
+{
+ /**
+ * Return the base part of a tag URI
+ *
+ * Note: use mint() instead.
+ *
+ * @return string Tag URI base to use
+ */
+
+ static function base()
+ {
+ $base = common_config('integration', 'taguri');
+
+ if (empty($base)) {
+
+ $base = common_config('site', 'server').','.date('Y-m-d');
+
+ $pathPart = trim(common_config('site', 'path'), '/');
+
+ if (!empty($pathPart)) {
+ $base .= ':'.str_replace('/', ':', $pathPart);
+ }
+ }
+
+ return $base;
+ }
+
+ /**
+ * Make a new tag URI
+ *
+ * Builds the proper base and creates all the parts
+ *
+ * @return string minted URI
+ */
+
+ static function mint()
+ {
+ $base = self::base();
+
+ $args = func_get_args();
+
+ $format = array_shift($args);
+
+ $extra = vsprintf($format, $args);
+
+ return 'tag:'.$base.':'.$extra;
+ }
+}
diff --git a/lib/theme.php b/lib/theme.php
index 020ce1ac4..0be8c3b9d 100644
--- a/lib/theme.php
+++ b/lib/theme.php
@@ -110,9 +110,20 @@ class Theme
$server = common_config('site', 'server');
}
- // XXX: protocol
+ $ssl = common_config('theme', 'ssl');
+
+ if (is_null($ssl)) { // null -> guess
+ if (common_config('site', 'ssl') == 'always' &&
+ !common_config('theme', 'server')) {
+ $ssl = true;
+ } else {
+ $ssl = false;
+ }
+ }
+
+ $protocol = ($ssl) ? 'https' : 'http';
- $this->path = 'http://'.$server.$path.$name;
+ $this->path = $protocol . '://'.$server.$path.$name;
}
}
diff --git a/lib/userprofile.php b/lib/userprofile.php
index 07e575085..43dfd05be 100644
--- a/lib/userprofile.php
+++ b/lib/userprofile.php
@@ -238,9 +238,12 @@ class UserProfile extends Widget
if (Event::handle('StartProfilePageActionsElements', array(&$this->out, $this->profile))) {
if (empty($cur)) { // not logged in
- $this->out->elementStart('li', 'entity_subscribe');
- $this->showRemoteSubscribeLink();
- $this->out->elementEnd('li');
+ if (Event::handle('StartProfileRemoteSubscribe', array(&$this->out, $this->profile))) {
+ $this->out->elementStart('li', 'entity_subscribe');
+ $this->showRemoteSubscribeLink();
+ $this->out->elementEnd('li');
+ Event::handle('EndProfileRemoteSubscribe', array(&$this->out, $this->profile));
+ }
} else {
if ($cur->id == $this->profile->id) { // your own page
$this->out->elementStart('li', 'entity_edit');
diff --git a/lib/util.php b/lib/util.php
index f0f262dc5..8381bc63c 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -134,7 +134,7 @@ function common_check_user($nickname, $password)
$authenticatedUser = false;
if (Event::handle('StartCheckPassword', array($nickname, $password, &$authenticatedUser))) {
- $user = User::staticGet('nickname', $nickname);
+ $user = User::staticGet('nickname', common_canonical_nickname($nickname));
if (!empty($user)) {
if (!empty($password)) { // never allow login with blank password
if (0 == strcmp(common_munge_password($password, $user->id),
@@ -367,7 +367,8 @@ function common_current_user()
if ($_cur === false) {
- if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
+ if (isset($_COOKIE[session_name()]) || isset($_GET[session_name()])
+ || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
common_ensure_session();
$id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false;
if ($id) {
@@ -425,13 +426,148 @@ function common_render_content($text, $notice)
{
$r = common_render_text($text);
$id = $notice->profile_id;
- $r = preg_replace('/(^|\s+)@(['.NICKNAME_FMT.']{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
- $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
- $r = preg_replace('/(^|[\s\.\,\:\;]+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
+ $r = common_linkify_mentions($id, $r);
$r = preg_replace('/(^|[\s\.\,\:\;]+)!([A-Za-z0-9]{1,64})/e', "'\\1!'.common_group_link($id, '\\2')", $r);
return $r;
}
+function common_linkify_mentions($profile_id, $text)
+{
+ $mentions = common_find_mentions($profile_id, $text);
+
+ // We need to go through in reverse order by position,
+ // so our positions stay valid despite our fudging with the
+ // string!
+
+ $points = array();
+
+ foreach ($mentions as $mention)
+ {
+ $points[$mention['position']] = $mention;
+ }
+
+ krsort($points);
+
+ foreach ($points as $position => $mention) {
+
+ $linkText = common_linkify_mention($mention);
+
+ $text = substr_replace($text, $linkText, $position, mb_strlen($mention['text']));
+ }
+
+ return $text;
+}
+
+function common_linkify_mention($mention)
+{
+ $output = null;
+
+ if (Event::handle('StartLinkifyMention', array($mention, &$output))) {
+
+ $xs = new XMLStringer(false);
+
+ $attrs = array('href' => $mention['url'],
+ 'class' => 'url');
+
+ if (!empty($mention['title'])) {
+ $attrs['title'] = $mention['title'];
+ }
+
+ $xs->elementStart('span', 'vcard');
+ $xs->elementStart('a', $attrs);
+ $xs->element('span', 'fn nickname', $mention['text']);
+ $xs->elementEnd('a');
+ $xs->elementEnd('span');
+
+ $output = $xs->getString();
+
+ Event::handle('EndLinkifyMention', array($mention, &$output));
+ }
+
+ return $output;
+}
+
+function common_find_mentions($profile_id, $text)
+{
+ $mentions = array();
+
+ $sender = Profile::staticGet('id', $profile_id);
+
+ if (empty($sender)) {
+ return $mentions;
+ }
+
+ if (Event::handle('StartFindMentions', array($sender, $text, &$mentions))) {
+
+ preg_match_all('/^T ([A-Z0-9]{1,64}) /',
+ $text,
+ $tmatches,
+ PREG_OFFSET_CAPTURE);
+
+ preg_match_all('/(?:^|\s+)@(['.NICKNAME_FMT.']{1,64})/',
+ $text,
+ $atmatches,
+ PREG_OFFSET_CAPTURE);
+
+ $matches = array_merge($tmatches[1], $atmatches[1]);
+
+ foreach ($matches as $match) {
+
+ $nickname = common_canonical_nickname($match[0]);
+ $mentioned = common_relative_profile($sender, $nickname);
+
+ if (!empty($mentioned)) {
+
+ $user = User::staticGet('id', $mentioned->id);
+
+ if ($user) {
+ $url = common_local_url('userbyid', array('id' => $user->id));
+ } else {
+ $url = $mentioned->profileurl;
+ }
+
+ $mention = array('mentioned' => array($mentioned),
+ 'text' => $match[0],
+ 'position' => $match[1],
+ 'url' => $url);
+
+ if (!empty($mentioned->fullname)) {
+ $mention['title'] = $mentioned->fullname;
+ }
+
+ $mentions[] = $mention;
+ }
+ }
+
+ // @#tag => mention of all subscriptions tagged 'tag'
+
+ preg_match_all('/(?:^|[\s\.\,\:\;]+)@#([\pL\pN_\-\.]{1,64})/',
+ $text,
+ $hmatches,
+ PREG_OFFSET_CAPTURE);
+
+ foreach ($hmatches[1] as $hmatch) {
+
+ $tag = common_canonical_tag($hmatch[0]);
+
+ $tagged = Profile_tag::getTagged($sender->id, $tag);
+
+ $url = common_local_url('subscriptions',
+ array('nickname' => $sender->nickname,
+ 'tag' => $tag));
+
+ $mentions[] = array('mentioned' => $tagged,
+ 'text' => $hmatch[0],
+ 'position' => $hmatch[1],
+ 'url' => $url);
+ }
+
+ Event::handle('EndFindMentions', array($sender, $text, &$mentions));
+ }
+
+ return $mentions;
+}
+
function common_render_text($text)
{
$r = htmlspecialchars($text);
@@ -662,39 +798,11 @@ function common_valid_profile_tag($str)
return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
}
-function common_at_link($sender_id, $nickname)
-{
- $sender = Profile::staticGet($sender_id);
- $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
- if ($recipient) {
- $user = User::staticGet('id', $recipient->id);
- if ($user) {
- $url = common_local_url('userbyid', array('id' => $user->id));
- } else {
- $url = $recipient->profileurl;
- }
- $xs = new XMLStringer(false);
- $attrs = array('href' => $url,
- 'class' => 'url');
- if (!empty($recipient->fullname)) {
- $attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
- }
- $xs->elementStart('span', 'vcard');
- $xs->elementStart('a', $attrs);
- $xs->element('span', 'fn nickname', $nickname);
- $xs->elementEnd('a');
- $xs->elementEnd('span');
- return $xs->getString();
- } else {
- return $nickname;
- }
-}
-
function common_group_link($sender_id, $nickname)
{
$sender = Profile::staticGet($sender_id);
$group = User_group::getForNickname($nickname);
- if ($group && $sender->isMember($group)) {
+ if ($sender && $group && $sender->isMember($group)) {
$attrs = array('href' => $group->permalink(),
'class' => 'url');
if (!empty($group->fullname)) {
@@ -712,29 +820,6 @@ function common_group_link($sender_id, $nickname)
}
}
-function common_at_hash_link($sender_id, $tag)
-{
- $user = User::staticGet($sender_id);
- if (!$user) {
- return $tag;
- }
- $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
- if ($tagged) {
- $url = common_local_url('subscriptions',
- array('nickname' => $user->nickname,
- 'tag' => $tag));
- $xs = new XMLStringer();
- $xs->elementStart('span', 'tag');
- $xs->element('a', array('href' => $url,
- 'rel' => $tag),
- $tag);
- $xs->elementEnd('span');
- return $xs->getString();
- } else {
- return $tag;
- }
-}
-
function common_relative_profile($sender, $nickname, $dt=null)
{
// Try to find profiles this profile is subscribed to that have this nickname
@@ -1003,7 +1088,6 @@ function common_enqueue_notice($notice)
if (Event::hasHandler('HandleQueuedNotice')) {
$transports[] = 'plugin';
}
-
$xmpp = common_config('xmpp', 'enabled');
@@ -1035,12 +1119,16 @@ function common_enqueue_notice($notice)
return true;
}
-function common_broadcast_profile($profile)
+/**
+ * Broadcast profile updates to OMB and other remote subscribers.
+ *
+ * Since this may be slow with a lot of subscribers or bad remote sites,
+ * this is run through the background queues if possible.
+ */
+function common_broadcast_profile(Profile $profile)
{
- // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
- require_once(INSTALLDIR.'/lib/omb.php');
- omb_broadcast_profile($profile);
- // XXX: Other broadcasts...?
+ $qm = QueueManager::get();
+ $qm->enqueue($profile, "profile");
return true;
}
@@ -1518,6 +1606,7 @@ function common_database_tablename($tablename)
*/
function common_shorten_url($long_url)
{
+ $long_url = trim($long_url);
$user = common_current_user();
if (empty($user)) {
// common current user does not find a user when called from the XMPP daemon
@@ -1532,7 +1621,7 @@ function common_shorten_url($long_url)
return $long_url;
}else{
//URL was shortened, so return the result
- return $shortenedUrl;
+ return trim($shortenedUrl);
}
}
@@ -1570,3 +1659,57 @@ function common_client_ip()
return array($proxy, $ip);
}
+
+function common_url_to_nickname($url)
+{
+ static $bad = array('query', 'user', 'password', 'port', 'fragment');
+
+ $parts = parse_url($url);
+
+ # If any of these parts exist, this won't work
+
+ foreach ($bad as $badpart) {
+ if (array_key_exists($badpart, $parts)) {
+ return null;
+ }
+ }
+
+ # We just have host and/or path
+
+ # If it's just a host...
+ if (array_key_exists('host', $parts) &&
+ (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
+ {
+ $hostparts = explode('.', $parts['host']);
+
+ # Try to catch common idiom of nickname.service.tld
+
+ if ((count($hostparts) > 2) &&
+ (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
+ (strcmp($hostparts[0], 'www') != 0))
+ {
+ return common_nicknamize($hostparts[0]);
+ } else {
+ # Do the whole hostname
+ return common_nicknamize($parts['host']);
+ }
+ } else {
+ if (array_key_exists('path', $parts)) {
+ # Strip starting, ending slashes
+ $path = preg_replace('@/$@', '', $parts['path']);
+ $path = preg_replace('@^/@', '', $path);
+ $path = basename($path);
+ if ($path) {
+ return common_nicknamize($path);
+ }
+ }
+ }
+
+ return null;
+}
+
+function common_nicknamize($str)
+{
+ $str = preg_replace('/\W/', '', $str);
+ return strtolower($str);
+}
diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php
index 985e7c32e..f37635855 100644
--- a/lib/xmppmanager.php
+++ b/lib/xmppmanager.php
@@ -48,7 +48,7 @@ class XmppManager extends IoManager
public static function get()
{
if (common_config('xmpp', 'enabled')) {
- $site = common_config('site', 'server');
+ $site = StatusNet::currentSite();
if (empty(self::$singletons[$site])) {
self::$singletons[$site] = new XmppManager();
}
@@ -69,7 +69,7 @@ class XmppManager extends IoManager
function __construct()
{
- $this->site = common_config('site', 'server');
+ $this->site = StatusNet::currentSite();
$this->resource = common_config('xmpp', 'resource') . 'daemon';
}
@@ -476,10 +476,10 @@ class XmppManager extends IoManager
*/
protected function switchSite()
{
- if ($this->site != common_config('site', 'server')) {
+ if ($this->site != StatusNet::currentSite()) {
common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site");
$this->stats('switch');
- StatusNet::init($this->site);
+ StatusNet::switchSite($this->site);
}
}
}