From a1b436a8c6a733dc0c11b3b9421b4db613a62fd1 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Feb 2011 20:25:39 -0800 Subject: [PATCH] First cut at some JSON Activity Streams output --- actions/apitimelinefriends.php | 6 ++ classes/Notice.php | 5 +- lib/activity.php | 53 ++++++++++++++ lib/activityobject.php | 48 ++++++++++++- lib/activitystreamjsondocument.php | 112 +++++++++++++++++++++++++++++ lib/router.php | 2 +- 6 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 lib/activitystreamjsondocument.php diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index 71049f6eb1..3833418baa 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -263,6 +263,12 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction case 'json': $this->showJsonTimeline($this->notices); break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->addItemsFromNotices($this->notices); + $this->raw($doc->asString()); + break; default: // TRANS: Client error displayed when trying to handle an unknown API method. $this->clientError(_('API method not found.'), $code = 404); diff --git a/classes/Notice.php b/classes/Notice.php index 4522d1fc38..ed1aab0014 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1252,18 +1252,19 @@ class Notice extends Memcached_DataObject function asActivity() { + common_debug("a"); + $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id)); if (!empty($act)) { return $act; } - $act = new Activity(); if (Event::handle('StartNoticeAsActivity', array($this, &$act))) { $profile = $this->getProfile(); - + common_debug('b'); $act->actor = ActivityObject::fromProfile($profile); $act->verb = ActivityVerb::POST; $act->objects[] = ActivityObject::fromNotice($this); diff --git a/lib/activity.php b/lib/activity.php index 17684d897b..0046469899 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -337,6 +337,59 @@ class Activity return null; } + /** + * Returns an array based on this activity suitable + * for encoding as a JSON object + * + * @return array $activity + */ + + function asArray() + { + $activity = array(); + + // actor + $activity['actor'] = $this->actor->asArray(); + + // body + $activity['body'] = $this->content; + + // generator <--- might be useful; might be too much junk + + // icon <-- should we use this? + + // object + if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) { + $activity['object'] = $this->objects[0]->asArray(); + } else { + $activity['object'] = array(); + foreach($this->objects as $object) { + $activity['object'][] = $object->asArray(); + } + } + + $activity['postedTime'] = self::iso8601Date($this->time); // Change to exactly be RFC3339? + + // provider <--- again not sure we should use this + + // target + if (!empty($this->target)) { + $activity['target'] = $this->target->asArray(); + } + + // title + $activity['title'] = $this->title; + + // updatedTime <-- should we use? spec says activity MAY have this + + // verb + $activity['verb'] = $this->verb; + + // TODO: extensions (ActivityContext, OStatus stuff, etc.) + + return $activity; + } + function asString($namespace=false, $author=true, $source=false) { $xs = new XMLStringer(true); diff --git a/lib/activityobject.php b/lib/activityobject.php index 5898c6d050..53ffe1a172 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -179,7 +179,7 @@ class ActivityObject if (empty($this->type)) { $this->type = self::PERSON; // XXX: is this fair? } - + // start with $title = ActivityUtils::childHtmlContent($element, self::TITLE); @@ -419,7 +419,7 @@ class ActivityObject static function fromNotice(Notice $notice) { $object = new ActivityObject(); - + if (Event::handle('StartActivityObjectFromNotice', array($notice, &$object))) { $object->type = ActivityObject::NOTE; @@ -526,7 +526,7 @@ class ActivityObject return $object; } - + function outputTo($xo, $tag='activity:object') { if (!empty($tag)) { @@ -633,4 +633,46 @@ class ActivityObject return $xs->getString(); } + + /* + * Returns an array based on this Activity Object suitable for + * encoding as JSON. + * + * @return array $object the activity object array + */ + + function asArray() + { + $object = array(); + + // TODO: attachedObjects + + // displayName + $object['displayName'] = $this->title; + + + // TODO: downstreamDuplicates + // TODO: embedCode + + // id + $object['id'] = $this->id; + + // TODO: image + // Need to make MediaLink serialization + + // objectType + $object['type'] = $this->type; + + // summary + $object['summary'] = $this->summary; + + // TODO: upstreamDuplicates + + // url (XXX: need to put the right thing here...) + $object['url'] = $this->id; + + // TODO: extensions (OStatus stuff, etc.) + + return $object; + } } diff --git a/lib/activitystreamjsondocument.php b/lib/activitystreamjsondocument.php new file mode 100644 index 0000000000..57ece9f6bf --- /dev/null +++ b/lib/activitystreamjsondocument.php @@ -0,0 +1,112 @@ +. + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) +{ + exit(1); +} + +/** + * A class for generating JSON documents that represent an Activity Streams + * + * @category Feed + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ActivityStreamJSONDocument +{ + /* Top level array representing the document */ + protected $doc = array(); + + /* The current authenticated user */ + protected $cur = null; + + /** + * Constructor + * + * @param User $cur the current authenticated user + */ + + function __construct($cur = null) + { + $this->cur = $cur; + + $this->doc['items'] = array(); + } + + /** + * Add more than one Item to the document + * + * @param mixed $notices an array of Notice objects or handle + * + */ + + function addItemsFromNotices($notices) + { + common_debug("addItemsFromNotices"); + if (is_array($notices)) { + foreach ($notices as $notice) { + $this->addItemFromNotice($notice); + } + } else { + while ($notices->fetch()) { + $this->addItemFromNotice($notices); + } + } + } + + /** + * Add a single Notice to the document + * + * @param Notice $notice a Notice to add + */ + + function addItemFromNotice($notice) + { + $cur = empty($this->cur) ? common_current_user() : $this->cur; + + $act = $notice->asActivity(); + $act->extra[] = $notice->noticeInfo($cur); + + array_push($this->doc['items'], $act->asArray()); + } + + /* + * Return the entire document as a big string of JSON + * + * @return string encoded JSON output + */ + function asString() + { + return json_encode($this->doc); + } + +} diff --git a/lib/router.php b/lib/router.php index c8e1c365a5..9f2b1df868 100644 --- a/lib/router.php +++ b/lib/router.php @@ -411,7 +411,7 @@ class Router $m->connect('api/statuses/friends_timeline.:format', array('action' => 'ApiTimelineFriends', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/friends_timeline/:id.:format', array('action' => 'ApiTimelineFriends',