Update activity streams JSON to match spec

Squashed commit of the following:

commit 0722450267a1d0f4bdc2853f52a85b850329db73
Author: Zach Copley <zach@status.net>
Date:   Thu Aug 25 09:58:29 2011 -0700

    Updgrade activity object json

commit 882ba1dceaba8a0b3ec3513760aa09f68e41f270
Author: Zach Copley <zach@status.net>
Date:   Wed Aug 24 16:30:07 2011 -0700

    Update to the JSON activity serialization document

commit 121e441b314b93e184711c3dcc79ada69d429eba
Author: Zach Copley <zach@status.net>
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 <zach@status.net>
Date:   Wed Aug 24 15:06:40 2011 -0700

    Update to use latest property names from the JSON activity spec
This commit is contained in:
Zach Copley 2011-08-25 10:00:20 -07:00
parent 94503a50fd
commit 297d603feb
4 changed files with 126 additions and 40 deletions

View File

@ -267,15 +267,14 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
break; break;
case 'as': case 'as':
header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE); header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
$doc = new ActivityStreamJSONDocument($this->auth_user); $doc = new ActivityStreamJSONDocument($this->auth_user, $title);
$doc->setTitle($title);
$doc->addLink($link, 'alternate', 'text/html'); $doc->addLink($link, 'alternate', 'text/html');
$doc->addItemsFromNotices($this->notices); $doc->addItemsFromNotices($this->notices);
$this->raw($doc->asString()); $this->raw($doc->asString());
break; break;
default: default:
// TRANS: Client error displayed when coming across a non-supported API method. // 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; break;
} }
} }

View File

@ -362,15 +362,16 @@ class Activity
// actor // actor
$activity['actor'] = $this->actor->asArray(); $activity['actor'] = $this->actor->asArray();
// body // content
$activity['body'] = $this->content; $activity['content'] = $this->content;
// generator <-- We could use this when we know a notice is created // generator <-- We could use this when we know a notice is created
// locally. Or if we know the upstream Generator. // locally. Or if we know the upstream Generator.
// icon <-- I've decided to use the posting user's stream avatar here // icon <-- possibly a mini object representing verb?
// for now (also included in the avatarLinks extension)
// id
$activity['id'] = $this->id;
// object // object
if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) { if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) {
@ -399,9 +400,9 @@ class Activity
// Instead of adding enclosures as an extension to JSON // Instead of adding enclosures as an extension to JSON
// Activities, it seems like we should be using the // 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 // XXX: OK, this is kinda cheating. We should probably figure out
// what kind of objects these are based on mime-type and then // what kind of objects these are based on mime-type and then
@ -413,11 +414,11 @@ class Activity
if (is_string($enclosure)) { if (is_string($enclosure)) {
$attachedObjects[]['id'] = $enclosure; $attachments[]['id'] = $enclosure;
} else { } else {
$attachedObjects[]['id'] = $enclosure->url; $attachments[]['id'] = $enclosure->url;
$mediaLink = new ActivityStreamsMediaLink( $mediaLink = new ActivityStreamsMediaLink(
$enclosure->url, $enclosure->url,
@ -427,16 +428,16 @@ class Activity
// XXX: Add 'size' as an extension to MediaLink? // XXX: Add 'size' as an extension to MediaLink?
); );
$attachedObjects[]['mediaLink'] = $mediaLink->asArray(); // extension $attachments[]['mediaLink'] = $mediaLink->asArray(); // extension
if ($enclosure->title) { if ($enclosure->title) {
$attachedObjects[]['displayName'] = $enclosure->title; $attachments[]['displayName'] = $enclosure->title;
} }
} }
} }
if (!empty($attachedObjects)) { if (!empty($attachments)) {
$activity['object']['attachedObjects'] = $attachedObjects; $activity['object']['attachments'] = $attachments;
} }
} else { } 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
$provider = array( $provider = array(
@ -471,8 +473,8 @@ class Activity
// title // title
$activity['title'] = $this->title; $activity['title'] = $this->title;
// updatedTime <-- Should we use this to indicate the time we received // updated <-- Optional. Should we use this to indicate the time we r
// a remote notice? Probably not. // eceived a remote notice? Probably not.
// verb // verb
// //
@ -480,6 +482,9 @@ class Activity
// relative simple name is easier to parse // relative simple name is easier to parse
$activity['verb'] = substr($this->verb, strrpos($this->verb, '/') + 1); $activity['verb'] = substr($this->verb, strrpos($this->verb, '/') + 1);
// url
$activity['url'] = $this->id;
/* Purely extensions hereafter */ /* Purely extensions hereafter */
$tags = array(); $tags = array();

View File

@ -679,16 +679,19 @@ class ActivityObject
$object = array(); $object = array();
if (Event::handle('StartActivityObjectOutputJson', array($this, &$object))) { 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 // displayName
$object['displayName'] = $this->title; $object['displayName'] = $this->title;
// TODO: downstreamDuplicates // downstreamDuplicates
// embedCode (used for video)
// id // id
$object['id'] = $this->id;
// //
// XXX: Should we use URL here? or a crazy tag URI? // XXX: Should we use URL here? or a crazy tag URI?
$object['id'] = $this->id; $object['id'] = $this->id;
@ -736,9 +739,13 @@ class ActivityObject
// @fixme this breaks extension URIs // @fixme this breaks extension URIs
$object['type'] = substr($this->type, strrpos($this->type, '/') + 1); $object['type'] = substr($this->type, strrpos($this->type, '/') + 1);
// published (probably don't need. Might be useful for repeats.)
// summary // summary
$object['summary'] = $this->summary; $object['summary'] = $this->summary;
// udpated (probably don't need this)
// TODO: upstreamDuplicates // TODO: upstreamDuplicates
// url (XXX: need to put the right thing here...) // url (XXX: need to put the right thing here...)
@ -753,8 +760,6 @@ class ActivityObject
$object[$objectName] = $props; $object[$objectName] = $props;
} }
// GeoJSON
if (!empty($this->geopoint)) { if (!empty($this->geopoint)) {
list($lat, $long) = explode(' ', $this->geopoint); list($lat, $long) = explode(' ', $this->geopoint);
@ -766,7 +771,7 @@ class ActivityObject
} }
if (!empty($this->poco)) { if (!empty($this->poco)) {
$object['contact'] = $this->poco->asArray(); $object['contact'] = array_filter($this->poco->asArray());
} }
Event::handle('EndActivityObjectOutputJson', array($this, &$object)); Event::handle('EndActivityObjectOutputJson', array($this, &$object));
} }

View File

@ -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 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @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 */ /* Top level array representing the document */
protected $doc = array(); protected $doc = array();
/* The current authenticated user */ /* 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 * Constructor
@ -57,20 +71,26 @@ class ActivityStreamJSONDocument
* @param User $cur the current authenticated user * @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; $this->cur = $cur;
/* Title of the JSON document */ /* Title of the JSON document */
$this->doc['title'] = null; $this->title = $title;
/* Array of activity items */ if (!empty($items)) {
$this->doc['items'] = array(); $this->count = count($this->items);
}
/* Array of links associated with the document */ /* 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) function setTitle($title)
{ {
$this->doc['title'] = $title; $this->title = $title;
} }
function setUrl($url)
{
$this->url = $url;
}
/** /**
* Add more than one Item to the document * Add more than one Item to the document
* *
@ -116,7 +142,8 @@ class ActivityStreamJSONDocument
$act = $notice->asActivity($cur); $act = $notice->asActivity($cur);
$act->extra[] = $notice->noticeInfo($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) function addLink($url = null, $rel = null, $mediaType = null)
{ {
$link = new ActivityStreamsLink($url, $rel, $mediaType); $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() 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, $url = null,
$width = null, $width = null,
$height = null, $height = null,
$mediaType = null, $mediaType = null, // extension
$rel = null, $rel = null, // extension
$duration = null $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 * A class for representing links in JSON Activities
* *
@ -216,3 +292,4 @@ class ActivityStreamsLink
return array_filter($this->linkDict); return array_filter($this->linkDict);
} }
} }