. * * @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); } /** * 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) { $rel = $link->getAttribute(self::REL); $type = $link->getAttribute(self::TYPE); if ($rel == 'alternate' && $type == 'text/html') { return $link->getAttribute(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'; 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 CONTENT = 'content'; const ID = 'id'; const SOURCE = 'source'; const NAME = 'name'; const URI = 'uri'; const EMAIL = 'email'; 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); 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->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; } } } /** * 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'; } /** * 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 /** * 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...? $updateEl = $this->_child($entry, self::UPDATED, self::ATOM); if (!empty($updateEl)) { $this->time = strtotime($updateEl->textContent); } else { $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); } 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 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); } } }