From 297d603febbb1900b395e1ddaf970f61f259cdad Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 25 Aug 2011 10:00:20 -0700 Subject: [PATCH] Update activity streams JSON to match spec Squashed commit of the following: commit 0722450267a1d0f4bdc2853f52a85b850329db73 Author: Zach Copley Date: Thu Aug 25 09:58:29 2011 -0700 Updgrade activity object json commit 882ba1dceaba8a0b3ec3513760aa09f68e41f270 Author: Zach Copley Date: Wed Aug 24 16:30:07 2011 -0700 Update to the JSON activity serialization document commit 121e441b314b93e184711c3dcc79ada69d429eba Author: Zach Copley Date: Wed Aug 24 15:08:06 2011 -0700 Output application/json instead of application/stream+json (at least for now) commit e045e214bffe5e0ddeb0a42555d440b75ae4edde Author: Zach Copley Date: Wed Aug 24 15:06:40 2011 -0700 Update to use latest property names from the JSON activity spec --- actions/apitimelinefriends.php | 7 +- lib/activity.php | 35 +++++----- lib/activityobject.php | 19 ++++-- lib/activitystreamjsondocument.php | 105 +++++++++++++++++++++++++---- 4 files changed, 126 insertions(+), 40 deletions(-) diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index e27d052652..591baccfcc 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -267,15 +267,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction break; case 'as': header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE); - $doc = new ActivityStreamJSONDocument($this->auth_user); - $doc->setTitle($title); - $doc->addLink($link,'alternate', 'text/html'); + $doc = new ActivityStreamJSONDocument($this->auth_user, $title); + $doc->addLink($link, 'alternate', 'text/html'); $doc->addItemsFromNotices($this->notices); $this->raw($doc->asString()); break; default: // TRANS: Client error displayed when coming across a non-supported API method. - $this->clientError(_('API method not found.'), $code = 404); + $this->clientError(_('API method not found.'), 404); break; } } diff --git a/lib/activity.php b/lib/activity.php index 1582a2019e..9dbb0e2ee0 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -362,15 +362,16 @@ class Activity // actor $activity['actor'] = $this->actor->asArray(); - // body - $activity['body'] = $this->content; + // content + $activity['content'] = $this->content; // generator <-- We could use this when we know a notice is created // locally. Or if we know the upstream Generator. - // icon <-- I've decided to use the posting user's stream avatar here - // for now (also included in the avatarLinks extension) + // icon <-- possibly a mini object representing verb? + // id + $activity['id'] = $this->id; // object if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) { @@ -399,9 +400,9 @@ class Activity // Instead of adding enclosures as an extension to JSON // Activities, it seems like we should be using the - // attachedObjects property of ActivityObject + // attachements property of ActivityObject - $attachedObjects = array(); + $attachments = array(); // XXX: OK, this is kinda cheating. We should probably figure out // what kind of objects these are based on mime-type and then @@ -413,11 +414,11 @@ class Activity if (is_string($enclosure)) { - $attachedObjects[]['id'] = $enclosure; + $attachments[]['id'] = $enclosure; } else { - $attachedObjects[]['id'] = $enclosure->url; + $attachments[]['id'] = $enclosure->url; $mediaLink = new ActivityStreamsMediaLink( $enclosure->url, @@ -427,16 +428,16 @@ class Activity // XXX: Add 'size' as an extension to MediaLink? ); - $attachedObjects[]['mediaLink'] = $mediaLink->asArray(); // extension + $attachments[]['mediaLink'] = $mediaLink->asArray(); // extension if ($enclosure->title) { - $attachedObjects[]['displayName'] = $enclosure->title; + $attachments[]['displayName'] = $enclosure->title; } } } - if (!empty($attachedObjects)) { - $activity['object']['attachedObjects'] = $attachedObjects; + if (!empty($attachments)) { + $activity['object']['attachments'] = $attachments; } } else { @@ -452,7 +453,8 @@ class Activity } } - $activity['postedTime'] = self::iso8601Date($this->time); // Change to exactly be RFC3339? + // published + $activity['published'] = self::iso8601Date($this->time); // provider $provider = array( @@ -471,8 +473,8 @@ class Activity // title $activity['title'] = $this->title; - // updatedTime <-- Should we use this to indicate the time we received - // a remote notice? Probably not. + // updated <-- Optional. Should we use this to indicate the time we r + // eceived a remote notice? Probably not. // verb // @@ -480,6 +482,9 @@ class Activity // relative simple name is easier to parse $activity['verb'] = substr($this->verb, strrpos($this->verb, '/') + 1); + // url + $activity['url'] = $this->id; + /* Purely extensions hereafter */ $tags = array(); diff --git a/lib/activityobject.php b/lib/activityobject.php index 0343fa664e..7204980d40 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -679,16 +679,19 @@ class ActivityObject $object = array(); if (Event::handle('StartActivityObjectOutputJson', array($this, &$object))) { - // XXX: attachedObjects are added by Activity + // XXX: attachments are added by Activity + + // author (Add object for author? Could be useful for repeats.) + + // content (Add rendered version of the notice?) // displayName $object['displayName'] = $this->title; - // TODO: downstreamDuplicates - - // embedCode (used for video) + // downstreamDuplicates // id + $object['id'] = $this->id; // // XXX: Should we use URL here? or a crazy tag URI? $object['id'] = $this->id; @@ -736,9 +739,13 @@ class ActivityObject // @fixme this breaks extension URIs $object['type'] = substr($this->type, strrpos($this->type, '/') + 1); + // published (probably don't need. Might be useful for repeats.) + // summary $object['summary'] = $this->summary; + // udpated (probably don't need this) + // TODO: upstreamDuplicates // url (XXX: need to put the right thing here...) @@ -753,8 +760,6 @@ class ActivityObject $object[$objectName] = $props; } - // GeoJSON - if (!empty($this->geopoint)) { list($lat, $long) = explode(' ', $this->geopoint); @@ -766,7 +771,7 @@ class ActivityObject } if (!empty($this->poco)) { - $object['contact'] = $this->poco->asArray(); + $object['contact'] = array_filter($this->poco->asArray()); } Event::handle('EndActivityObjectOutputJson', array($this, &$object)); } diff --git a/lib/activitystreamjsondocument.php b/lib/activitystreamjsondocument.php index 097267f504..6d17075931 100644 --- a/lib/activitystreamjsondocument.php +++ b/lib/activitystreamjsondocument.php @@ -41,15 +41,29 @@ if (!defined('STATUSNET')) * @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 +class ActivityStreamJSONDocument extends JSONActivityCollection { - const CONTENT_TYPE = 'application/stream+json; charset=utf-8'; + // Note: Lot of AS folks think the content type should be: + // 'application/stream+json; charset=utf-8', but this is more + // useful at the moment, because some programs actually understand + // it. + const CONTENT_TYPE = 'application/json; charset=utf-8'; /* Top level array representing the document */ protected $doc = array(); /* The current authenticated user */ - protected $cur = null; + protected $cur; + + /* Title of the document */ + protected $title; + + /* Links associated with this document */ + protected $links; + + /* Count of items in this document */ + // XXX This is cryptically referred to in the spec: "The Stream serialization MAY contain a count property." + protected $count; /** * Constructor @@ -57,20 +71,26 @@ class ActivityStreamJSONDocument * @param User $cur the current authenticated user */ - function __construct($cur = null) + function __construct($cur = null, $title = null, $items = null, $links = null, $url = null) { + parent::__construct($items, $url); $this->cur = $cur; /* Title of the JSON document */ - $this->doc['title'] = null; + $this->title = $title; - /* Array of activity items */ - $this->doc['items'] = array(); + if (!empty($items)) { + $this->count = count($this->items); + } /* Array of links associated with the document */ - $this->doc['links'] = array(); + $this->links = empty($links) ? array() : $items; + /* URL of a document, this document? containing a list of all the items in the stream */ + if (!empty($this->url)) { + $this->url = $this->url; + } } /** @@ -81,9 +101,15 @@ class ActivityStreamJSONDocument function setTitle($title) { - $this->doc['title'] = $title; + $this->title = $title; } + function setUrl($url) + { + $this->url = $url; + } + + /** * Add more than one Item to the document * @@ -116,7 +142,8 @@ class ActivityStreamJSONDocument $act = $notice->asActivity($cur); $act->extra[] = $notice->noticeInfo($cur); - array_push($this->doc['items'], $act->asArray()); + array_push($this->items, $act->asArray()); + $this->count++; } /** @@ -128,7 +155,7 @@ class ActivityStreamJSONDocument function addLink($url = null, $rel = null, $mediaType = null) { $link = new ActivityStreamsLink($url, $rel, $mediaType); - $this->doc['links'][] = $link->asArray(); + array_push($this->links, $link->asArray()); } /* @@ -138,7 +165,13 @@ class ActivityStreamJSONDocument */ function asString() { - return json_encode(array_filter($this->doc)); + $this->doc['generator'] = 'StatusNet ' . STATUSNET_VERSION; // extension + $this->doc['title'] = $this->title; + $this->doc['url'] = $this->url; + $this->doc['count'] = $this->count; + $this->doc['items'] = $this->items; + $this->doc['links'] = $this->links; // extension + return json_encode(array_filter($this->doc)); // filter out empty elements } } @@ -161,8 +194,8 @@ class ActivityStreamsMediaLink extends ActivityStreamsLink $url = null, $width = null, $height = null, - $mediaType = null, - $rel = null, + $mediaType = null, // extension + $rel = null, // extension $duration = null ) { @@ -183,6 +216,49 @@ class ActivityStreamsMediaLink extends ActivityStreamsLink } } +/* + * Collection primarily as the root of an Activity Streams doc but can be used as the value + * of extension properties in a variety of situations. + * + * A valid Collection object serialization MUST contain at least the url or items properties. + */ +class JSONActivityCollection { + + /* Non-negative integer specifying the total number of activities within the stream */ + protected $totalItems; + + /* An array containing a listing of Objects of any object type */ + protected $items; + + /* IRI referencing a JSON document containing the full listing of objects in the collection */ + protected $url; + + /** + * Constructor + * + * @param array $items array of activity items + * @param string $url url of a doc list all the objs in the collection + * @param int $totalItems total number of items in the collection + */ + function __construct($items = null, $url = null) + { + $this->items = empty($items) ? array() : $items; + $this->totalItems = count($items); + $this->url = $url; + } + + /** + * Get the total number of items in the collection + * + * @return int total the total + */ + public function getTotalItems() + { + $this->totalItems = count($items); + return $this->totalItems; + } +} + /** * A class for representing links in JSON Activities * @@ -216,3 +292,4 @@ class ActivityStreamsLink return array_filter($this->linkDict); } } +