diff options
Diffstat (limited to 'lib/activity.php')
-rw-r--r-- | lib/activity.php | 317 |
1 files changed, 271 insertions, 46 deletions
diff --git a/lib/activity.php b/lib/activity.php index 2cb80f9e1..dcd079c7a 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -78,7 +78,7 @@ class PoCoAddress if (!empty($this->formatted)) { $xs = new XMLStringer(true); $xs->elementStart('poco:address'); - $xs->element('poco:formatted', null, $this->formatted); + $xs->element('poco:formatted', null, common_xml_safe_str($this->formatted)); $xs->elementEnd('poco:address'); return $xs->getString(); } @@ -279,7 +279,7 @@ class PoCo ); if (!empty($this->note)) { - $xs->element('poco:note', null, $this->note); + $xs->element('poco:note', null, common_xml_safe_str($this->note)); } if (!empty($this->address)) { @@ -347,6 +347,11 @@ class ActivityUtils $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); @@ -457,13 +462,13 @@ class ActivityUtils // slavishly following http://atompub.org/rfc4287.html#rfc.section.4.1.3.3 - if ($type == 'text') { + if (empty($type) || $type == 'text') { return $contentEl->textContent; } else if ($type == 'html') { $text = $contentEl->textContent; return htmlspecialchars_decode($text, ENT_QUOTES); } else if ($type == 'xhtml') { - $divEl = ActivityUtils::child($contentEl, 'div'); + $divEl = ActivityUtils::child($contentEl, 'div', 'http://www.w3.org/1999/xhtml'); if (empty($divEl)) { return null; } @@ -476,7 +481,7 @@ class ActivityUtils $text .= $doc->saveXML($child); } return trim($text); - } else if (in_array(array('text/xml', 'application/xml'), $type) || + } 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)) { @@ -643,51 +648,174 @@ class ActivityObject ); if ($element->tagName == 'author') { + $this->_fromAuthor($element); + } else if ($element->tagName == 'item') { + $this->_fromRssItem($element); + } else { + $this->_fromAtomEntry($element); + } - $this->type = self::PERSON; // XXX: is this fair? - $this->title = $this->_childContent($element, self::NAME); - $this->id = $this->_childContent($element, self::URI); + // Some per-type attributes... + if ($this->type == self::PERSON || $this->type == self::GROUP) { + $this->displayName = $this->title; - if (empty($this->id)) { - $email = $this->_childContent($element, self::EMAIL); - if (!empty($email)) { - // XXX: acct: ? - $this->id = 'mailto:'.$email; + $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->title = $this->_childContent($element, self::TITLE); + $this->summary = $this->_childContent($element, self::SUMMARY); + + $this->source = $this->_getSource($element); + + $this->content = ActivityUtils::getContent($element); + + $this->link = ActivityUtils::getPermalink($element); + } + + // @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->type = $this->_childContent($element, Activity::OBJECTTYPE, - Activity::SPEC); + $this->link = ActivityUtils::childContent($item, ActivityUtils::LINK, Activity::RSS); - if (empty($this->type)) { - $this->type = ActivityObject::NOTE; + $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; } + } + } - $this->id = $this->_childContent($element, self::ID); - $this->title = $this->_childContent($element, self::TITLE); - $this->summary = $this->_childContent($element, self::SUMMARY); + 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 - $this->source = $this->_getSource($element); + $obj = new ActivityObject(); - $this->content = ActivityUtils::getContent($element); + $obj->element = $el; - $this->link = ActivityUtils::getPermalink($element); + $obj->type = ActivityObject::PERSON; + $obj->title = $name; + if (!empty($email)) { + $obj->id = 'mailto:'.$email; } - // Some per-type attributes... - if ($this->type == self::PERSON || $this->type == self::GROUP) { - $this->displayName = $this->title; + return $obj; + } - $avatars = ActivityUtils::getLinks($element, 'avatar'); - foreach ($avatars as $link) { - $this->avatarLinks[] = new AvatarLink($link); - } + public static function fromDcCreator($el) + { + // Not really enough info - $this->poco = new PoCo($element); + $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, self::RSS); + $obj->link = ActivityUtils::childContent($el, ActivityUtils::LINK, self::RSS); + $obj->id = ActivityUtils::getLink($el, self::SELF); + + $desc = ActivityUtils::childContent($el, self::DESCRIPTION, self::RSS); + + if (!empty($desc)) { + $obj->content = htmlspecialchars_decode($desc, ENT_QUOTES); + } + + $imageEl = ActivityUtils::child($el, self::IMAGE, self::RSS); + + if (!empty($imageEl)) { + $obj->avatarLinks[] = ActivityUtils::childContent($imageEl, self::URL, self::RSS); } + + return $obj; } private function _childContent($element, $tag, $namespace=ActivityUtils::ATOM) @@ -713,7 +841,7 @@ class ActivityObject } } - static function fromNotice($notice) + static function fromNotice(Notice $notice) { $object = new ActivityObject(); @@ -727,7 +855,7 @@ class ActivityObject return $object; } - static function fromProfile($profile) + static function fromProfile(Profile $profile) { $object = new ActivityObject(); @@ -805,7 +933,6 @@ class ActivityObject return $object; } - function asString($tag='activity:object') { $xs = new XMLStringer(true); @@ -817,16 +944,28 @@ class ActivityObject $xs->element(self::ID, null, $this->id); if (!empty($this->title)) { - $xs->element(self::TITLE, null, $this->title); + $xs->element( + self::TITLE, + null, + common_xml_safe_str($this->title) + ); } if (!empty($this->summary)) { - $xs->element(self::SUMMARY, null, $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'), $this->content); + $xs->element( + ActivityUtils::CONTENT, + array('type' => 'html'), + common_xml_safe_str($this->content) + ); } if (!empty($this->link)) { @@ -1033,6 +1172,21 @@ class Activity const PUBLISHED = 'published'; const UPDATED = 'updated'; + const RSS = null; // no namespace! + + const PUBDATE = 'pubDate'; + const DESCRIPTION = 'description'; + const GUID = 'guid'; + const SELF = 'self'; + const IMAGE = 'image'; + const URL = 'url'; + + const DC = 'http://purl.org/dc/elements/1.1/'; + + const CREATOR = 'creator'; + + const CONTENTNS = 'http://purl.org/rss/1.0/modules/content/'; + public $actor; // an ActivityObject public $verb; // a string (the URL) public $object; // an ActivityObject @@ -1063,21 +1217,29 @@ class Activity return; } - $this->entry = $entry; - - // @fixme Don't send in a DOMDocument + // Insist on a feed's root DOMElement; don't allow a DOMDocument if ($feed instanceof DOMDocument) { - common_log( - LOG_WARNING, - 'Activity::__construct() - ' - . 'DOMDocument passed in for feed by mistake. ' - . "Expecting a 'feed' DOMElement." + throw new ClientException( + _("Expecting a root feed element but got a whole XML document.") ); - $feed = $feed->getElementsByTagName('feed')->item(0); } + $this->entry = $entry; $this->feed = $feed; + if ($entry->namespaceURI == Activity::ATOM && + $entry->localName == 'entry') { + $this->_fromAtomEntry($entry, $feed); + } else if ($entry->namespaceURI == Activity::RSS && + $entry->localName == 'item') { + $this->_fromRssItem($entry, $feed); + } else { + throw new Exception("Unknown DOM element: {$entry->namespaceURI} {$entry->localName}"); + } + } + + function _fromAtomEntry($entry, $feed) + { $pubEl = $this->_child($entry, self::PUBLISHED, self::ATOM); if (!empty($pubEl)) { @@ -1163,6 +1325,69 @@ class Activity } } + function _fromRssItem($item, $rss) + { + $verbEl = $this->_child($item, self::VERB); + + if (!empty($verbEl)) { + $this->verb = trim($verbEl->textContent); + } else { + $this->verb = ActivityVerb::POST; + // XXX: do other implied stuff here + } + + $pubDateEl = $this->_child($item, self::PUBDATE, self::RSS); + + if (!empty($pubDateEl)) { + $this->time = strtotime($pubDateEl->textContent); + } + + $authorEl = $this->_child($item, self::AUTHOR, self::RSS); + + if (!empty($authorEl)) { + $this->actor = ActivityObject::fromRssAuthor($authorEl); + } else { + $dcCreatorEl = $this->_child($item, self::CREATOR, self::DC); + if (!empty($dcCreatorEl)) { + $this->actor = ActivityObject::fromDcCreator($dcCreatorEl); + } else if (!empty($rss)) { + $this->actor = ActivityObject::fromRssChannel($rss); + } + } + + $this->title = ActivityUtils::childContent($item, ActivityObject::TITLE, self::RSS); + + $contentEl = ActivityUtils::child($item, ActivityUtils::CONTENT, self::CONTENTNS); + + if (!empty($contentEl)) { + $this->content = htmlspecialchars_decode($contentEl->textContent, ENT_QUOTES); + } else { + $descriptionEl = ActivityUtils::child($item, self::DESCRIPTION, self::RSS); + if (!empty($descriptionEl)) { + $this->content = htmlspecialchars_decode($descriptionEl->textContent, ENT_QUOTES); + } + } + + $this->link = ActivityUtils::childContent($item, ActivityUtils::LINK, self::RSS); + + // @fixme enclosures + // @fixme thumbnails... maybe + + $guidEl = ActivityUtils::child($item, self::GUID, self::RSS); + + if (!empty($guidEl)) { + $this->id = $guidEl->textContent; + + if ($guidEl->hasAttribute('isPermaLink') && $guidEl->getAttribute('isPermaLink') != 'false') { + // overwrites <link> + $this->link = $this->id; + } + } + + $this->object = new ActivityObject($item); + $this->context = new ActivityContext($item); + } + /** * Returns an Atom <entry> based on this activity * |