]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Move activity classes to their own files
authorEvan Prodromou <evan@status.net>
Sat, 20 Mar 2010 21:06:22 +0000 (16:06 -0500)
committerEvan Prodromou <evan@status.net>
Sat, 20 Mar 2010 21:06:22 +0000 (16:06 -0500)
Moved the various classes used by the Activity class to their own
files. There were >10 classes in the same file, with around 1500 lines
in the file. Just too big.

This change makes autoloading work for these classes, so also removed
the hard require in lib/common.php.

lib/activity.php
lib/activitycontext.php [new file with mode: 0644]
lib/activityobject.php [new file with mode: 0644]
lib/activityutils.php [new file with mode: 0644]
lib/activityverb.php [new file with mode: 0644]
lib/avatarlink.php [new file with mode: 0644]
lib/common.php
lib/poco.php [new file with mode: 0644]
lib/pocoaddress.php [new file with mode: 0644]
lib/pocourl.php [new file with mode: 0644]

index de4e38c3cb0193fcec2396df2a5ba0c6e3d72434..b1744e68f585c681d76cab8097ee7c0b58d558b9 100644 (file)
@@ -32,1129 +32,6 @@ 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, common_xml_safe_str($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, common_xml_safe_str($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)
-    {
-        $els = $element->childNodes;
-
-        foreach ($els as $link) {
-
-            if (!($link instanceof DOMElement)) {
-                continue;
-            }
-
-            if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
-
-                $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)
-    {
-        $els = $element->childNodes;
-        $out = array();
-
-        foreach ($els as $link) {
-            if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
-
-                $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;
-        }
-    }
-
-    static function childHtmlContent(DOMNode $element, $tag, $namespace=self::ATOM)
-    {
-        $el = self::child($element, $tag, $namespace);
-
-        if (empty($el)) {
-            return null;
-        } else {
-            return self::textConstruct($el);
-        }
-    }
-
-    /**
-     * 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)
-    {
-        return self::childHtmlContent($element, self::CONTENT, self::ATOM);
-    }
-
-    static function textConstruct($el)
-    {
-        $src  = $el->getAttribute(self::SRC);
-
-        if (!empty($src)) {
-            throw new ClientException(_("Can't handle remote content yet."));
-        }
-
-        $type = $el->getAttribute(self::TYPE);
-
-        // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3
-
-        if (empty($type) || $type == 'text') {
-            return $el->textContent;
-        } else if ($type == 'html') {
-            $text = $el->textContent;
-            return htmlspecialchars_decode($text, ENT_QUOTES);
-        } else if ($type == 'xhtml') {
-            $divEl = ActivityUtils::child($el, 'div', 'http://www.w3.org/1999/xhtml');
-            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($type, array('text/xml', 'application/xml')) ||
-                   preg_match('#(+|/)xml$#', $type)) {
-            throw new ClientException(_("Can't handle embedded XML content yet."));
-        } else if (strncasecmp($type, 'text/', 5)) {
-            return $el->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->_fromAuthor($element);
-        } else if ($element->tagName == 'item') {
-            $this->_fromRssItem($element);
-        } else {
-            $this->_fromAtomEntry($element);
-        }
-
-        // Some per-type attributes...
-        if ($this->type == self::PERSON || $this->type == self::GROUP) {
-            $this->displayName = $this->title;
-
-            $photos = ActivityUtils::getLinks($element, 'photo');
-            if (count($photos)) {
-                foreach ($photos as $link) {
-                    $this->avatarLinks[] = new AvatarLink($link);
-                }
-            } else {
-                $avatars = ActivityUtils::getLinks($element, 'avatar');
-                foreach ($avatars as $link) {
-                    $this->avatarLinks[] = new AvatarLink($link);
-                }
-            }
-
-            $this->poco = new PoCo($element);
-        }
-    }
-
-    private function _fromAuthor($element)
-    {
-        $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;
-            }
-        }
-    }
-
-    private function _fromAtomEntry($element)
-    {
-        $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->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY);
-        $this->content = ActivityUtils::getContent($element);
-
-        // We don't like HTML in our titles, although it's technically allowed
-
-        $title = ActivityUtils::childHtmlContent($element, self::TITLE);
-
-        $this->title = html_entity_decode(strip_tags($title));
-
-        $this->source  = $this->_getSource($element);
-
-        $this->link = ActivityUtils::getPermalink($element);
-    }
-
-    // @fixme rationalize with Activity::_fromRssItem()
-
-    private function _fromRssItem($item)
-    {
-        $this->title = ActivityUtils::childContent($item, ActivityObject::TITLE, Activity::RSS);
-
-        $contentEl = ActivityUtils::child($item, ActivityUtils::CONTENT, Activity::CONTENTNS);
-
-        if (!empty($contentEl)) {
-            $this->content = htmlspecialchars_decode($contentEl->textContent, ENT_QUOTES);
-        } else {
-            $descriptionEl = ActivityUtils::child($item, Activity::DESCRIPTION, Activity::RSS);
-            if (!empty($descriptionEl)) {
-                $this->content = htmlspecialchars_decode($descriptionEl->textContent, ENT_QUOTES);
-            }
-        }
-
-        $this->link = ActivityUtils::childContent($item, ActivityUtils::LINK, Activity::RSS);
-
-        $guidEl = ActivityUtils::child($item, Activity::GUID, Activity::RSS);
-
-        if (!empty($guidEl)) {
-            $this->id = $guidEl->textContent;
-
-            if ($guidEl->hasAttribute('isPermaLink')) {
-                // overwrites <link>
-                $this->link = $this->id;
-            }
-        }
-    }
-
-    public static function fromRssAuthor($el)
-    {
-        $text = $el->textContent;
-
-        if (preg_match('/^(.*?) \((.*)\)$/', $text, $match)) {
-            $email = $match[1];
-            $name = $match[2];
-        } else if (preg_match('/^(.*?) <(.*)>$/', $text, $match)) {
-            $name = $match[1];
-            $email = $match[2];
-        } else if (preg_match('/.*@.*/', $text)) {
-            $email = $text;
-            $name = null;
-        } else {
-            $name = $text;
-            $email = null;
-        }
-
-        // Not really enough info
-
-        $obj = new ActivityObject();
-
-        $obj->element = $el;
-
-        $obj->type  = ActivityObject::PERSON;
-        $obj->title = $name;
-
-        if (!empty($email)) {
-            $obj->id = 'mailto:'.$email;
-        }
-
-        return $obj;
-    }
-
-    public static function fromDcCreator($el)
-    {
-        // Not really enough info
-
-        $text = $el->textContent;
-
-        $obj = new ActivityObject();
-
-        $obj->element = $el;
-
-        $obj->title = $text;
-        $obj->type  = ActivityObject::PERSON;
-
-        return $obj;
-    }
-
-    public static function fromRssChannel($el)
-    {
-        $obj = new ActivityObject();
-
-        $obj->element = $el;
-
-        $obj->type = ActivityObject::PERSON; // @fixme guess better
-
-        $obj->title = ActivityUtils::childContent($el, ActivityObject::TITLE, Activity::RSS);
-        $obj->link  = ActivityUtils::childContent($el, ActivityUtils::LINK, Activity::RSS);
-        $obj->id    = ActivityUtils::getLink($el, Activity::SELF);
-
-        if (empty($obj->id)) {
-            $obj->id = $obj->link;
-        }
-
-        $desc = ActivityUtils::childContent($el, Activity::DESCRIPTION, Activity::RSS);
-
-        if (!empty($desc)) {
-            $obj->content = htmlspecialchars_decode($desc, ENT_QUOTES);
-        }
-
-        $imageEl = ActivityUtils::child($el, Activity::IMAGE, Activity::RSS);
-
-        if (!empty($imageEl)) {
-            $obj->avatarLinks[] = ActivityUtils::childContent($imageEl, Activity::URL, Activity::RSS);
-        }
-
-        return $obj;
-    }
-
-    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 $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 $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,
-                common_xml_safe_str($this->title)
-            );
-        }
-
-        if (!empty($this->summary)) {
-            $xs->element(
-                self::SUMMARY,
-                null,
-                common_xml_safe_str($this->summary)
-            );
-        }
-
-        if (!empty($this->content)) {
-            // XXX: assuming HTML content here
-            $xs->element(
-                ActivityUtils::CONTENT,
-                array('type' => 'html'),
-                common_xml_safe_str($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
  *
diff --git a/lib/activitycontext.php b/lib/activitycontext.php
new file mode 100644 (file)
index 0000000..2df7613
--- /dev/null
@@ -0,0 +1,121 @@
+<?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 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;
+    }
+}
diff --git a/lib/activityobject.php b/lib/activityobject.php
new file mode 100644 (file)
index 0000000..b1e9071
--- /dev/null
@@ -0,0 +1,494 @@
+<?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);
+}
+
+/**
+ * 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->_fromAuthor($element);
+        } else if ($element->tagName == 'item') {
+            $this->_fromRssItem($element);
+        } else {
+            $this->_fromAtomEntry($element);
+        }
+
+        // Some per-type attributes...
+        if ($this->type == self::PERSON || $this->type == self::GROUP) {
+            $this->displayName = $this->title;
+
+            $photos = ActivityUtils::getLinks($element, 'photo');
+            if (count($photos)) {
+                foreach ($photos as $link) {
+                    $this->avatarLinks[] = new AvatarLink($link);
+                }
+            } else {
+                $avatars = ActivityUtils::getLinks($element, 'avatar');
+                foreach ($avatars as $link) {
+                    $this->avatarLinks[] = new AvatarLink($link);
+                }
+            }
+
+            $this->poco = new PoCo($element);
+        }
+    }
+
+    private function _fromAuthor($element)
+    {
+        $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;
+            }
+        }
+    }
+
+    private function _fromAtomEntry($element)
+    {
+        $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->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY);
+        $this->content = ActivityUtils::getContent($element);
+
+        // We don't like HTML in our titles, although it's technically allowed
+
+        $title = ActivityUtils::childHtmlContent($element, self::TITLE);
+
+        $this->title = html_entity_decode(strip_tags($title));
+
+        $this->source  = $this->_getSource($element);
+
+        $this->link = ActivityUtils::getPermalink($element);
+    }
+
+    // @fixme rationalize with Activity::_fromRssItem()
+
+    private function _fromRssItem($item)
+    {
+        $this->title = ActivityUtils::childContent($item, ActivityObject::TITLE, Activity::RSS);
+
+        $contentEl = ActivityUtils::child($item, ActivityUtils::CONTENT, Activity::CONTENTNS);
+
+        if (!empty($contentEl)) {
+            $this->content = htmlspecialchars_decode($contentEl->textContent, ENT_QUOTES);
+        } else {
+            $descriptionEl = ActivityUtils::child($item, Activity::DESCRIPTION, Activity::RSS);
+            if (!empty($descriptionEl)) {
+                $this->content = htmlspecialchars_decode($descriptionEl->textContent, ENT_QUOTES);
+            }
+        }
+
+        $this->link = ActivityUtils::childContent($item, ActivityUtils::LINK, Activity::RSS);
+
+        $guidEl = ActivityUtils::child($item, Activity::GUID, Activity::RSS);
+
+        if (!empty($guidEl)) {
+            $this->id = $guidEl->textContent;
+
+            if ($guidEl->hasAttribute('isPermaLink')) {
+                // overwrites <link>
+                $this->link = $this->id;
+            }
+        }
+    }
+
+    public static function fromRssAuthor($el)
+    {
+        $text = $el->textContent;
+
+        if (preg_match('/^(.*?) \((.*)\)$/', $text, $match)) {
+            $email = $match[1];
+            $name = $match[2];
+        } else if (preg_match('/^(.*?) <(.*)>$/', $text, $match)) {
+            $name = $match[1];
+            $email = $match[2];
+        } else if (preg_match('/.*@.*/', $text)) {
+            $email = $text;
+            $name = null;
+        } else {
+            $name = $text;
+            $email = null;
+        }
+
+        // Not really enough info
+
+        $obj = new ActivityObject();
+
+        $obj->element = $el;
+
+        $obj->type  = ActivityObject::PERSON;
+        $obj->title = $name;
+
+        if (!empty($email)) {
+            $obj->id = 'mailto:'.$email;
+        }
+
+        return $obj;
+    }
+
+    public static function fromDcCreator($el)
+    {
+        // Not really enough info
+
+        $text = $el->textContent;
+
+        $obj = new ActivityObject();
+
+        $obj->element = $el;
+
+        $obj->title = $text;
+        $obj->type  = ActivityObject::PERSON;
+
+        return $obj;
+    }
+
+    public static function fromRssChannel($el)
+    {
+        $obj = new ActivityObject();
+
+        $obj->element = $el;
+
+        $obj->type = ActivityObject::PERSON; // @fixme guess better
+
+        $obj->title = ActivityUtils::childContent($el, ActivityObject::TITLE, Activity::RSS);
+        $obj->link  = ActivityUtils::childContent($el, ActivityUtils::LINK, Activity::RSS);
+        $obj->id    = ActivityUtils::getLink($el, Activity::SELF);
+
+        if (empty($obj->id)) {
+            $obj->id = $obj->link;
+        }
+
+        $desc = ActivityUtils::childContent($el, Activity::DESCRIPTION, Activity::RSS);
+
+        if (!empty($desc)) {
+            $obj->content = htmlspecialchars_decode($desc, ENT_QUOTES);
+        }
+
+        $imageEl = ActivityUtils::child($el, Activity::IMAGE, Activity::RSS);
+
+        if (!empty($imageEl)) {
+            $obj->avatarLinks[] = ActivityUtils::childContent($imageEl, Activity::URL, Activity::RSS);
+        }
+
+        return $obj;
+    }
+
+    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 $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 $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,
+                common_xml_safe_str($this->title)
+            );
+        }
+
+        if (!empty($this->summary)) {
+            $xs->element(
+                self::SUMMARY,
+                null,
+                common_xml_safe_str($this->summary)
+            );
+        }
+
+        if (!empty($this->content)) {
+            // XXX: assuming HTML content here
+            $xs->element(
+                ActivityUtils::CONTENT,
+                array('type' => 'html'),
+                common_xml_safe_str($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();
+    }
+}
diff --git a/lib/activityutils.php b/lib/activityutils.php
new file mode 100644 (file)
index 0000000..c85a3db
--- /dev/null
@@ -0,0 +1,243 @@
+<?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);
+}
+
+/**
+ * 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)
+    {
+        $els = $element->childNodes;
+
+        foreach ($els as $link) {
+
+            if (!($link instanceof DOMElement)) {
+                continue;
+            }
+
+            if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
+
+                $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)
+    {
+        $els = $element->childNodes;
+        $out = array();
+
+        foreach ($els as $link) {
+            if ($link->localName == self::LINK && $link->namespaceURI == self::ATOM) {
+
+                $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;
+        }
+    }
+
+    static function childHtmlContent(DOMNode $element, $tag, $namespace=self::ATOM)
+    {
+        $el = self::child($element, $tag, $namespace);
+
+        if (empty($el)) {
+            return null;
+        } else {
+            return self::textConstruct($el);
+        }
+    }
+
+    /**
+     * 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)
+    {
+        return self::childHtmlContent($element, self::CONTENT, self::ATOM);
+    }
+
+    static function textConstruct($el)
+    {
+        $src  = $el->getAttribute(self::SRC);
+
+        if (!empty($src)) {
+            throw new ClientException(_("Can't handle remote content yet."));
+        }
+
+        $type = $el->getAttribute(self::TYPE);
+
+        // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3
+
+        if (empty($type) || $type == 'text') {
+            return $el->textContent;
+        } else if ($type == 'html') {
+            $text = $el->textContent;
+            return htmlspecialchars_decode($text, ENT_QUOTES);
+        } else if ($type == 'xhtml') {
+            $divEl = ActivityUtils::child($el, 'div', 'http://www.w3.org/1999/xhtml');
+            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($type, array('text/xml', 'application/xml')) ||
+                   preg_match('#(+|/)xml$#', $type)) {
+            throw new ClientException(_("Can't handle embedded XML content yet."));
+        } else if (strncasecmp($type, 'text/', 5)) {
+            return $el->textContent;
+        } else {
+            throw new ClientException(_("Can't handle embedded Base64 content yet."));
+        }
+    }
+}
diff --git a/lib/activityverb.php b/lib/activityverb.php
new file mode 100644 (file)
index 0000000..76f2b84
--- /dev/null
@@ -0,0 +1,66 @@
+<?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);
+}
+
+/**
+ * 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';
+}
diff --git a/lib/avatarlink.php b/lib/avatarlink.php
new file mode 100644 (file)
index 0000000..e67799e
--- /dev/null
@@ -0,0 +1,102 @@
+<?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);
+}
+
+// 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;
+    }
+}
index 5d53270e30b85ac97682d1e0a5438ca43891ecf9..334a88ffd560f87ca6d2978e07ef06209068f8d9 100644 (file)
@@ -123,7 +123,6 @@ 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/poco.php b/lib/poco.php
new file mode 100644 (file)
index 0000000..2157062
--- /dev/null
@@ -0,0 +1,240 @@
+<?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 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, common_xml_safe_str($this->note));
+        }
+
+        if (!empty($this->address)) {
+            $xs->raw($this->address->asString());
+        }
+
+        foreach ($this->urls as $url) {
+            $xs->raw($url->asString());
+        }
+
+        return $xs->getString();
+    }
+}
diff --git a/lib/pocoaddress.php b/lib/pocoaddress.php
new file mode 100644 (file)
index 0000000..60873bd
--- /dev/null
@@ -0,0 +1,56 @@
+<?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 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, common_xml_safe_str($this->formatted));
+            $xs->elementEnd('poco:address');
+            return $xs->getString();
+        }
+
+        return null;
+    }
+}
diff --git a/lib/pocourl.php b/lib/pocourl.php
new file mode 100644 (file)
index 0000000..803484d
--- /dev/null
@@ -0,0 +1,65 @@
+<?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();
+    }
+}