From f78cf31303f9973447b85ecfe7557d452e97a21c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sun, 14 Feb 2010 12:12:47 -0500 Subject: [PATCH] update activity and salmon for previous commit --- plugins/OStatus/actions/salmon.php | 2 +- plugins/OStatus/lib/activity.php | 303 +++++++++++++++++++++++++++-- 2 files changed, 293 insertions(+), 12 deletions(-) diff --git a/plugins/OStatus/actions/salmon.php b/plugins/OStatus/actions/salmon.php index b616027a93..c79d09c95a 100644 --- a/plugins/OStatus/actions/salmon.php +++ b/plugins/OStatus/actions/salmon.php @@ -61,7 +61,7 @@ class SalmonAction extends Action // XXX: check that document element is Atom entry // XXX: check the signature - $this->act = Activity::fromAtomEntry($dom->documentElement); + $this->act = new Activity($dom->documentElement); } function handle($args) diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php index 36e2279134..11aab28486 100644 --- a/plugins/OStatus/lib/activity.php +++ b/plugins/OStatus/lib/activity.php @@ -31,7 +31,72 @@ if (!defined('STATUSNET')) { exit(1); } -class ActivityNoun +/** + * 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'; + + /** + * Get the permalink for an Activity object + * + * @param DOMElement $element A DOM element + * + * @return string related link, if any + */ + + static function getLink($element) + { + $links = $element->getElementsByTagnameNS(self::ATOM, self::LINK); + + foreach ($links as $link) { + if ($link->getAttributeNS(self::ATOM, self::REL) == 'alternate' && + $link->getAttributeNS(self::ATOM, self::TYPE) == 'text/html') { + return $link->getAttributeNS(self::ATOM, self::HREF); + } + } + + 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 + * @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'; @@ -47,19 +112,101 @@ class ActivityNoun 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 + const COMMENT = 'http://activitystrea.ms/schema/1.0/comment'; + // ^^^^^^^^^^ tea! + + // Atom elements we snarf + + const TITLE = 'title'; + const SUMMARY = 'summary'; + const CONTENT = 'content'; + const ID = 'id'; + const SOURCE = 'source'; + + const NAME = 'name'; + const URI = 'uri'; public $type; public $id; public $title; public $summary; public $content; + public $link; + public $source; + + /** + * 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) + { + $this->source = $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); + + } else { + + $this->type = $this->_childContent($element, Activity::OBJECTTYPE, + Activity::SPEC); + + $this->id = $this->_childContent($element, self::ID); + $this->title = $this->_childContent($element, self::TITLE); + $this->summary = $this->_childContent($element, self::SUMMARY); + $this->content = $this->_childContent($element, self::CONTENT); + $this->source = $this->_childContent($element, self::SOURCE); + + $this->link = ActivityUtils::getLink($element); + + // XXX: grab PoCo stuff + } + } + + /** + * 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 + */ + + private function _childContent($element, $tag, $namespace=Activity::ATOM) + { + $els = $element->getElementsByTagnameNS($namespace, $tag); + + if (empty($els) || $els->length == 0) { + return null; + } else { + $el = $els->item(0); + return $el->textContent; + } + } } -class Activity -{ - const NAMESPACE = 'http://activitystrea.ms/schema/1.0/'; +/** + * 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'; @@ -69,17 +216,151 @@ class Activity 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'; +} - public $actor; // an ActivityNoun - public $verb; // a string (the URL) - public $object; // an ActivityNoun - public $target; // an ActivityNoun +/** + * 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/ + */ - static function fromAtomEntry($domEntry) +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'; + + 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 + + /** + * 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, $feed = null) { + $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...? + $this->time = null; + } + + $this->link = ActivityUtils::getLink($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); + } + + $contextEl = $this->_child($entry, self::CONTEXT); + + if (!empty($contextEl)) { + $this->context = new ActivityObject($contextEl); + } + + $targetEl = $this->_child($entry, self::TARGET); + + if (!empty($targetEl)) { + $this->target = new ActivityObject($targetEl); + } } + /** + * Returns an Atom based on this activity + * + * @return DOMElement Atom entry + */ + function toAtomEntry() { + 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 + */ + + private function _child($element, $tag, $namespace=self::SPEC) + { + $els = $element->getElementsByTagnameNS($namespace, $tag); + + if (empty($els) || $els->length == 0) { + return null; + } else { + return $els->item(0); + } + } +} \ No newline at end of file