From 6a711c6cdc5d1e1b1a64e5858b12e6964a0abe9c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 22 Feb 2010 17:10:50 -0800 Subject: Move ActivityObject and related stuff to core --- plugins/OStatus/lib/activity.php | 863 --------------------------------------- 1 file changed, 863 deletions(-) delete mode 100644 plugins/OStatus/lib/activity.php (limited to 'plugins/OStatus') diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php deleted file mode 100644 index 585028318..000000000 --- a/plugins/OStatus/lib/activity.php +++ /dev/null @@ -1,863 +0,0 @@ -. - * - * @category OStatus - * @package StatusNet - * @author Evan Prodromou - * @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 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 ($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; - - function __construct($formatted) - { - if (empty($formatted)) { - return null; - } - $this->formatted = $formatted; - } - - function asString() - { - $xs = new XMLStringer(true); - $xs->elementStart('poco:address'); - $xs->element('poco:formatted', null, $this->formatted); - $xs->elementEnd('poco:address'); - return $xs->getString(); - } -} - -class PoCo -{ - const NS = 'http://portablecontacts.net/spec/1.0'; - - const USERNAME = 'preferredUsername'; - const NOTE = 'note'; - const URLS = 'urls'; - - public $preferredUsername; - public $note; - public $address; - public $urls = array(); - - function __construct($profile) - { - $this->preferredUsername = $profile->nickname; - - $this->note = $profile->bio; - $this->address = new PoCoAddress($profile->location); - - if (!empty($profile->homepage)) { - array_push( - $this->urls, - new PoCoURL( - 'homepage', - $profile->homepage, - true - ) - ); - } - } - - function asString() - { - $xs = new XMLStringer(true); - $xs->element( - 'poco:preferredUsername', - null, - $this->preferredUsername - ); - - 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 - * @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($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; - } - - /** - * 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($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($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 -< is HTML." - * - * @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.")); - } - } - } -} - -/** - * 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 - * @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 $avatar; - public $geopoint; - - /** - * 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; - - 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); - - // XXX: grab PoCo stuff - } - - // Some per-type attributes... - if ($this->type == self::PERSON || $this->type == self::GROUP) { - $this->displayName = $this->title; - - // @fixme we may have multiple avatars with different resolutions specified - $this->avatar = ActivityUtils::getLink($element, 'avatar'); - $this->nickname = ActivityUtils::childContent($element, PoCo::USERNAME, PoCo::NS); - } - } - - 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; - $object->avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); - - if (isset($profile->lat) && isset($profile->lon)) { - $object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon; - } - - $object->poco = new PoCo($profile); - - 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'), - $this->link); - } - - if ($this->type == ActivityObject::PERSON) { - $xs->element( - 'link', array( - 'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype, - 'rel' => 'avatar', - 'href' => empty($this->avatar) - ? Avatar::defaultImage(AVATAR_PROFILE_SIZE) - : $this->avatar->displayUrl() - ), - '' - ); - } - - 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 - * @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'; -} - -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; - $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" - $point = preg_replace('/\s+/', ' ', $point); - $point = trim($point); - $coords = explode(' ', $point); - if (count($coords) == 2) { - list($lat, $lon) = $coords; - if (is_numeric($lat) && is_numeric($lon)) { - common_log(LOG_INFO, "Looking up location for $lat $lon from georss"); - return Location::fromLatLon($lat, $lon); - } - } - common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); - } - - return 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 - * @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 - - /** - * Turns a regular old Atom 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); - } - - /** - * Returns an Atom 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:ostatus' => 'http://ostatus.org/schema/1.0'); - } 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 - // XXX: add target - - $xs->raw($this->actor->asString('activity:actor')); - $xs->element('activity:verb', null, $this->verb); - $xs->raw($this->object->asString()); - - $xs->elementEnd('entry'); - - return $xs->getString(); - } - - private function _child($element, $tag, $namespace=self::SPEC) - { - return ActivityUtils::child($element, $tag, $namespace); - } -} \ No newline at end of file -- cgit v1.2.3-54-g00ecf