From: Zach Copley Date: Fri, 18 Feb 2011 23:45:11 +0000 (-0800) Subject: Merge branch '0.9.x' into json-activities X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=0632d4f20c14a55b99d20fd394c340004d12c92b;hp=e9184dd775325e635bda5123486ba5c6d91331a6;p=quix0rs-gnu-social.git Merge branch '0.9.x' into json-activities * 0.9.x: HTML and style cleanup for EmailSummary plugin. --- diff --git a/actions/apitimelinefavorites.php b/actions/apitimelinefavorites.php index c952c46238..36fc3089f5 100644 --- a/actions/apitimelinefavorites.php +++ b/actions/apitimelinefavorites.php @@ -169,6 +169,14 @@ class ApiTimelineFavoritesAction 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->setTitle($title); + $doc->addLink($link,'alternate', 'text/html'); + $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/actions/apitimelinefriends.php b/actions/apitimelinefriends.php index 71049f6eb1..0e356bb18b 100644 --- a/actions/apitimelinefriends.php +++ b/actions/apitimelinefriends.php @@ -263,6 +263,14 @@ 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->setTitle($title); + $doc->addLink($link,'alternate', 'text/html'); + $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/actions/apitimelinegroup.php b/actions/apitimelinegroup.php index e1bc102e45..3fc930fa08 100644 --- a/actions/apitimelinegroup.php +++ b/actions/apitimelinegroup.php @@ -106,6 +106,11 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction $self = $this->getSelfUri(); + $link = common_local_url( + 'ApiTimelineGroup', + array('nickname' => $this->group->nickname) + ); + switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); @@ -123,24 +128,20 @@ class ApiTimelineGroupAction extends ApiPrivateAuthAction break; case 'atom': header('Content-Type: application/atom+xml; charset=utf-8'); - - try { $atom->addEntryFromNotices($this->notices); $this->raw($atom->getString()); - } catch (Atom10FeedException $e) { - $this->serverError( - // TRANS: Server error displayed when generating an Atom feed fails. - // TRANS: %s is the error. - sprintf(_('Could not generate feed for group - %s'),$e->getMessage()), - 400, - $this->format - ); - return; - } break; case 'json': $this->showJsonTimeline($this->notices); break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($atom->title); + $doc->addLink($link, 'alternate', 'text/html'); + $doc->addItemsFromNotices($this->notices); + $this->raw($doc->asString()); + break; default: $this->clientError( // TRANS: Client error displayed when trying to handle an unknown API method. diff --git a/actions/apitimelinehome.php b/actions/apitimelinehome.php index 75a9f72580..023c9698a1 100644 --- a/actions/apitimelinehome.php +++ b/actions/apitimelinehome.php @@ -168,6 +168,14 @@ class ApiTimelineHomeAction 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->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $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/actions/apitimelinementions.php b/actions/apitimelinementions.php index a9b6d0b3df..2857bd41ea 100644 --- a/actions/apitimelinementions.php +++ b/actions/apitimelinementions.php @@ -169,6 +169,14 @@ class ApiTimelineMentionsAction 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->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $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/actions/apitimelinepublic.php b/actions/apitimelinepublic.php index 2745e5d3fe..353973b653 100644 --- a/actions/apitimelinepublic.php +++ b/actions/apitimelinepublic.php @@ -234,6 +234,14 @@ class ApiTimelinePublicAction extends ApiPrivateAuthAction case 'json': $this->showJsonTimeline($this->notices); break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $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/actions/apitimelineretweetedtome.php b/actions/apitimelineretweetedtome.php index 6213a08eac..b9f9be1dda 100644 --- a/actions/apitimelineretweetedtome.php +++ b/actions/apitimelineretweetedtome.php @@ -92,6 +92,20 @@ class ApiTimelineRetweetedToMeAction extends ApiAuthAction $offset = ($this->page-1) * $this->cnt; $limit = $this->cnt; + // TRANS: Title for Atom feed "repeated to me". %s is the user nickname. + $title = sprintf(_("Repeated to %s"), $this->auth_user->nickname); + $subtitle = sprintf( + _('%1$s notices that were to repeated to %2$s / %3$s.'), + $sitename, $this->user->nickname, $profile->getBestName() + ); + $taguribase = TagURI::base(); + $id = "tag:$taguribase:RepeatedToMe:" . $this->auth_user->id; + + $link = common_local_url( + 'all', + array('nickname' => $this->auth_user->nickname) + ); + $strm = $this->auth_user->repeatedToMe($offset, $limit, $this->since_id, $this->max_id); switch ($this->format) { @@ -102,16 +116,31 @@ class ApiTimelineRetweetedToMeAction extends ApiAuthAction $this->showJsonTimeline($strm); break; case 'atom': - $profile = $this->auth_user->getProfile(); + header('Content-Type: application/atom+xml; charset=utf-8'); + + $atom = new AtomNoticeFeed($this->auth_user); + + $atom->setId($id); + $atom->setTitle($title); + $atom->setSubtitle($subtitle); + $atom->setUpdated('now'); + $atom->addLink($link); - // TRANS: Title for Atom feed "repeated to me". %s is the user nickname. - $title = sprintf(_("Repeated to %s"), $this->auth_user->nickname); - $taguribase = TagURI::base(); - $id = "tag:$taguribase:RepeatedToMe:" . $this->auth_user->id; - $link = common_local_url('all', - array('nickname' => $this->auth_user->nickname)); + $id = $this->arg('id'); - $this->showAtomTimeline($strm, $title, $id, $link); + $atom->setSelfLink($self); + $atom->addEntryFromNotices($strm); + + $this->raw($atom->getString()); + + break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $doc->addItemsFromNotices($strm); + $this->raw($doc->asString()); break; default: // TRANS: Client error displayed when trying to handle an unknown API method. diff --git a/actions/apitimelineretweetsofme.php b/actions/apitimelineretweetsofme.php index 9cb277279f..aec6877f15 100644 --- a/actions/apitimelineretweetsofme.php +++ b/actions/apitimelineretweetsofme.php @@ -93,9 +93,27 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction $offset = ($this->page-1) * $this->cnt; $limit = $this->cnt; - $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id); + // TRANS: Title of list of repeated notices of the logged in user. + // TRANS: %s is the nickname of the logged in user. + $title = sprintf(_("Repeats of %s"), $this->auth_user->nickname); + $sitename = common_config('site', 'name'); + + $profile = $this->auth_user->getProfile(); + + $subtitle = sprintf( + _('%1$s notices that %2$s / %3$s has repeated.'), + $sitename, $this->auth_user->nickname, $profile->getBestName() + ); - common_debug(var_export($strm, true)); + $taguribase = TagURI::base(); + $id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id; + + $link = common_local_url( + 'all', + array('nickname' => $this->auth_user->nickname) + ); + + $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id); switch ($this->format) { case 'xml': @@ -105,49 +123,28 @@ class ApiTimelineRetweetsOfMeAction extends ApiAuthAction $this->showJsonTimeline($strm); break; case 'atom': - $profile = $this->auth_user->getProfile(); - - // TRANS: Title of list of repeated notices of the logged in user. - // TRANS: %s is the nickname of the logged in user. - $title = sprintf(_("Repeats of %s"), $this->auth_user->nickname); - $taguribase = TagURI::base(); - $id = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id; - header('Content-Type: application/atom+xml; charset=utf-8'); - $atom = new AtomNoticeFeed($this->auth_user); - $atom->setId($id); $atom->setTitle($title); $atom->setSubtitle($subtitle); $atom->setUpdated('now'); - - $atom->addLink( - common_local_url( - 'showstream', - array('nickname' => $this->auth_user->nickname) - ) - ); - - $id = $this->arg('id'); - $aargs = array('format' => 'atom'); - if (!empty($id)) { - $aargs['id'] = $id; - } - - $atom->addLink( - $this->getSelfUri('ApiTimelineRetweetsOfMe', $aargs), - array('rel' => 'self', 'type' => 'application/atom+xml') - ); - + $atom->addLink($link); + $atom->setSelfLink($this->getSelfUri()); $atom->addEntryFromNotices($strm); - $this->raw($atom->getString()); - + break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $doc->addItemsFromNotices($strm); + $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); + $this->clientError(_('API method not found.'), 404); break; } } diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 4dbe1fc0db..5fa76d0cd0 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -107,7 +107,7 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $sitename ); $taguribase = TagURI::base(); - $id = "tag:$taguribase:TagTimeline:".$tag; + $id = "tag:$taguribase:TagTimeline:".$this->tag; $link = common_local_url( 'tag', @@ -116,8 +116,6 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction $self = $this->getSelfUri(); - common_debug("self link is: $self"); - switch($this->format) { case 'xml': $this->showXmlTimeline($this->notices); @@ -154,6 +152,14 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction case 'json': $this->showJsonTimeline($this->notices); break; + case 'as': + header('Content-Type: application/json; charset=utf-8'); + $doc = new ActivityStreamJSONDocument($this->auth_user); + $doc->setTitle($title); + $doc->addLink($link, 'alternate', 'text/html'); + $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/actions/apitimelineuser.php b/actions/apitimelineuser.php index b0ca7e923f..66984b5abd 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -201,6 +201,17 @@ class ApiTimelineUserAction 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->setTitle($atom->title); + $doc->addLink($link, 'alternate', 'text/html'); + $doc->addItemsFromNotices($this->notices); + + // XXX: Add paging extension? + + $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..95b3d5b480 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1250,23 +1250,23 @@ class Notice extends Memcached_DataObject * @return Activity activity object representing this Notice. */ - function asActivity() + function asActivity($cur) { $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(); - $act->actor = ActivityObject::fromProfile($profile); - $act->verb = ActivityVerb::POST; - $act->objects[] = ActivityObject::fromNotice($this); + $act->actor = ActivityObject::fromProfile($profile); + $act->actor->extra[] = $profile->profileInfo($cur); + $act->verb = ActivityVerb::POST; + $act->objects[] = ActivityObject::fromNotice($this); // XXX: should this be handled by default processing for object entry? @@ -1404,7 +1404,7 @@ class Notice extends Memcached_DataObject $author=true, $cur=null) { - $act = $this->asActivity(); + $act = $this->asActivity($cur); $act->extra[] = $this->noticeInfo($cur); return $act->asString($namespace, $author, $source); } diff --git a/classes/Profile.php b/classes/Profile.php index bdac3ba453..ad01581d5f 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -932,12 +932,12 @@ class Profile extends Memcached_DataObject * * @param User $cur Current user * - * @return array representation of element + * @return array representation of element or null */ function profileInfo($cur) { - $profileInfoAttr = array(); + $profileInfoAttr = array('local_id' => $this->id); if ($cur != null) { // Whether the current user is a subscribed to this profile diff --git a/lib/activity.php b/lib/activity.php index 17684d897b..11af98abb5 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -337,6 +337,150 @@ 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 <-- We should use this when we know a notice is created + // locally + + // icon <-- Should we use this? Maybe a little bubble like we have + // on Facebook posts? + + // object + if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) { + $activity['object'] = $this->objects[0]->asArray(); + + // Context stuff. For now I'm just sticking most of it + // in a property called "context" + + if (!empty($this->context)) { + + if (!empty($this->context->location)) { + $loc = $this->context->location; + + // GeoJSON + + $activity['geopoint'] = array( + 'type' => 'Point', + 'coordinates' => array($loc->lat, $loc->lon) + ); + + } + + $activity['to'] = $this->context->getToArray(); + $activity['context'] = $this->context->asArray(); + } + + // Instead of adding enclosures as an extension to JSON + // Activities, it seems like we should be using the + // attachedObjects property of ActivityObject + + $attachedObjects = array(); + + // XXX: OK, this is kinda cheating. We should probably figure out + // what kind of objects these are based on mime-type and then + // create specific object types. Right now this rely on + // duck-typing. Also, we should include an embed code for + // video attachments. + + foreach ($this->enclosures as $enclosure) { + + if (is_string($enclosure)) { + + $attachedObjects[]['id'] = $enclosure; + + } else { + + $attachedObjects[]['id'] = $enclosure->url; + + $mediaLink = new ActivityStreamsMediaLink( + $enclosure->url, + null, + null, + $enclosure->mimetype + // XXX: Add 'size' as an extension to MediaLink? + ); + + $attachedObjects[]['mediaLink'] = $mediaLink->asArray(); // extension + + if ($enclosure->title) { + $attachedObjects[]['displayName'] = $enclosure->title; + } + } + } + + if (!empty($attachedObjects)) { + $activity['object']['attachedObjects'] = $attachedObjects; + } + + } 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 <-- We should probably use this for showing the the source + // of remote notices, if known + + // target + if (!empty($this->target)) { + $activity['target'] = $this->target->asArray(); + } + + // title + $activity['title'] = $this->title; + + // updatedTime <-- Should we use this to indicate the time we received + // a remote notice? Probably not. + + // verb + // + // We can probably use the whole schema URL here but probably the + // relative simple name is easier to parse + $activity['verb'] = substr($this->verb, strrpos($this->verb, '/') + 1); + + /* Purely extensions hereafter */ + + $tags = array(); + + // Use an Activity Object for term? Which object? Note? + foreach ($this->categories as $cat) { + $tags[] = $cat->term; + } + + $activity['tags'] = $tags; + + // XXX: a bit of a hack... Since JSON isn't namespaced we probably + // shouldn't be using 'statusnet:notice_info', but this will work + // for the moment. + + foreach ($this->extra as $e) { + list($objectName, $props, $txt) = $e; + if (!empty($objectName)) { + $activity[$objectName] = $props; + } + } + + return array_filter($activity); + } + function asString($namespace=false, $author=true, $source=false) { $xs = new XMLStringer(true); diff --git a/lib/activitycontext.php b/lib/activitycontext.php index fd0dfe06c1..acbd0e599f 100644 --- a/lib/activitycontext.php +++ b/lib/activitycontext.php @@ -130,4 +130,69 @@ class ActivityContext common_log(LOG_ERR, "Ignoring bogus georss:point value $point"); return null; } + + /** + * Returns context (StatusNet stuff) as an array suitable for serializing + * in JSON. Right now context stuff is an extension to Activity. + * + * @return array the context + */ + + function asArray() + { + $context = array(); + + $context['replyTo'] = $this->getInReplyToArray(); + $context['conversation'] = $this->conversation; + $context['forwardId'] = $this->forwardID; + $context['forwardUrl'] = $this->forwardUrl; + + return array_filter($context); + } + + /** + * Returns an array of arrays representing Activity Objects (intended to be + * serialized in JSON) that represent WHO the Activity is supposed to + * be received by. This is not really specified but appears in an example + * of the current spec as an extension. We might want to figure out a JSON + * serialization for OStatus and use that to express mentions instead. + * + * XXX: People's ideas on how to do this are all over the place + * + * @return array the array of recipients + */ + + function getToArray() + { + $tos = array(); + + foreach ($this->attention as $attnUrl) { + $to = array( + 'objectType' => 'person', + 'id' => $attnUrl, + 'url' => $attnUrl + ); + $tos[] = $to; + } + + return $tos; + } + + /* + * Show replyTo + */ + + function getInReplyToArray() + { + $replyToObj = array('objectType' => 'note'); + + $replyToObj['id'] = $this->replyToID; + + if (!empty($this->replyToUrl)) { + $replyToObj['url'] = $this->replyToUrl; + } + + } + } + diff --git a/lib/activityobject.php b/lib/activityobject.php index 5898c6d050..a69e1a1b42 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,103 @@ 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(); + + // XXX: attachedObjects are added by Activity + + // displayName + $object['displayName'] = $this->title; + + // TODO: downstreamDuplicates + + // embedCode (used for video) + + // id + // + // XXX: Should we use URL here? or a crazy tag URI? + $object['id'] = $this->id; + + if ($this->type == ActivityObject::PERSON + || $this->type == ActivityObject::GROUP) { + + // XXX: Not sure what the best avatar is to use for the + // author's "image". For now, I'm using the large size. + + $avatarLarge = null; + $avatarMediaLinks = array(); + + foreach ($this->avatarLinks as $a) { + + // Make a MediaLink for every other Avatar + $avatar = new ActivityStreamsMediaLink( + $a->url, + $a->width, + $a->height, + $a->type, + 'avatar' + ); + + // Find the big avatar to use as the "image" + if ($a->height == AVATAR_PROFILE_SIZE) { + $imgLink = $avatar; + } + + $avatarMediaLinks[] = $avatar->asArray(); + } + + $object['avatarLinks'] = $avatarMediaLinks; // extension + + // image + $object['image'] = $imgLink->asArray(); + } + + // objectType + // + // We can probably use the whole schema URL here but probably the + // relative simple name is easier to parse + $object['type'] = substr($this->type, strrpos($this->type, '/') + 1); + + // summary + $object['summary'] = $this->summary; + + // TODO: upstreamDuplicates + + // url (XXX: need to put the right thing here...) + $object['url'] = $this->id; + + /* Extensions */ + + foreach ($this->extra as $e) { + list($objectName, $props, $txt) = $e; + $object[$objectName] = $props; + } + + // GeoJSON + + if (!empty($this->geopoint)) { + + list($lat, $long) = explode(' ', $this->geopoint); + + $object['geopoint'] = array( + 'type' => 'Point', + 'coordinates' => array($lat, $long) + ); + } + + if (!empty($this->poco)) { + $object['contact'] = $this->poco->asArray(); + } + + return array_filter($object); + } } diff --git a/lib/activitystreamjsondocument.php b/lib/activitystreamjsondocument.php new file mode 100644 index 0000000000..2b99d19eb7 --- /dev/null +++ b/lib/activitystreamjsondocument.php @@ -0,0 +1,217 @@ +. + * + * @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; + + /* Title of the JSON document */ + $this->doc['title'] = null; + + /* Array of activity items */ + $this->doc['items'] = array(); + + /* Array of links associated with the document */ + $this->doc['links'] = array(); + + } + + /** + * Set the title of the document + * + * @param String $title the title + */ + + function setTitle($title) + { + $this->doc['title'] = $title; + } + + /** + * Add more than one Item to the document + * + * @param mixed $notices an array of Notice objects or handle + * + */ + + function addItemsFromNotices($notices) + { + 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($cur); + $act->extra[] = $notice->noticeInfo($cur); + array_push($this->doc['items'], $act->asArray()); + } + + /** + * Add a link to the JSON document + * + * @param string $url the URL for the link + * @param string $rel the link relationship + */ + function addLink($url = null, $rel = null, $mediaType = null) + { + $link = new ActivityStreamsLink($url, $rel, $mediaType); + $this->doc['link'][] = $link->asArray(); + } + + /* + * Return the entire document as a big string of JSON + * + * @return string encoded JSON output + */ + function asString() + { + return json_encode(array_filter($this->doc)); + } + +} + +/** + * A class for representing MediaLinks in JSON Activities + * + * @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 ActivityStreamsMediaLink extends ActivityStreamsLink +{ + private $linkDict; + + function __construct( + $url = null, + $width = null, + $height = null, + $mediaType = null, + $rel = null, + $duration = null + ) + { + parent::__construct($url, $rel, $mediaType); + $this->linkDict = array( + 'width' => $width, + 'height' => $height, + 'duration' => $duration + ); + } + + function asArray() + { + return array_merge( + parent::asArray(), + array_filter($this->linkDict) + ); + } +} + +/** + * A class for representing links in JSON Activities + * + * @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 ActivityStreamsLink +{ + private $linkDict; + + function __construct($url = null, $rel = null, $mediaType = null) + { + // links MUST have a URL + if (empty($url)) { + throw new Exception('Links must have a URL.'); + } + + $this->linkDict = array( + 'url' => $url, + 'rel' => $rel, // extension + 'type' => $mediaType // extension + ); + } + + function asArray() + { + return array_filter($this->linkDict); + } +} diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php index 3398cc8b4d..fb0ac5f831 100644 --- a/lib/atomusernoticefeed.php +++ b/lib/atomusernoticefeed.php @@ -64,7 +64,7 @@ class AtomUserNoticeFeed extends AtomNoticeFeed $ao = ActivityObject::fromProfile($profile); - $ao->extra[] = $profile->profileInfo($cur); + array_push($ao->extra, $profile->profileInfo($cur)); // XXX: For users, we generate an author _AND_ an // This is for backward compatibility with clients (especially diff --git a/lib/poco.php b/lib/poco.php index d7b082163e..baea5b33b0 100644 --- a/lib/poco.php +++ b/lib/poco.php @@ -241,4 +241,42 @@ class PoCo $url->outputTo($xo); } } + + /** + * Output a Portable Contact as an array suitable for serializing + * as JSON + * + * @return $array the PoCo array + */ + + function asArray() + { + $poco = array(); + + $poco['preferredUsername'] = $this->preferredUsername; + $poco['displayName'] = $this->displayName; + + if (!empty($this->note)) { + $poco['note'] = $this->note; + } + + if (!empty($this->address)) { + $poco['addresses'] = $this->address->asArray(); + } + + if (!empty($this->urls)) { + + $urls = array(); + + foreach ($this->urls as $url) { + $urls[] = $url->asArray(); + } + + $poco['urls'] = $urls; + } + + return $poco; + } + } + diff --git a/lib/pocoaddress.php b/lib/pocoaddress.php index d9f6ff2bde..22d4d02b13 100644 --- a/lib/pocoaddress.php +++ b/lib/pocoaddress.php @@ -56,4 +56,17 @@ class PoCoAddress $xo->elementEnd('poco:address'); } } + + /** + * Return this PoCo address as an array suitable for serializing in JSON + * + * @return array the address + */ + + function asArray() + { + if (!empty($this->formatted)) { + return array('formatted' => $this->formatted); + } + } } diff --git a/lib/pocourl.php b/lib/pocourl.php index 786793b280..e375f125a0 100644 --- a/lib/pocourl.php +++ b/lib/pocourl.php @@ -67,4 +67,24 @@ class PoCoURL } $xo->elementEnd('poco:urls'); } + + /** + * Return this PoCo URL as an array suitable for serializing in JSON + * + * @array $url the url + */ + + function asArray() + { + $url = array(); + + $url['type'] = $this->type; + $url['value'] = $this->value; + + if (!empty($this->primary)) { + $url['primary'] = 'true'; + } + + return $url; + } } diff --git a/lib/router.php b/lib/router.php index c8e1c365a5..a4547b3258 100644 --- a/lib/router.php +++ b/lib/router.php @@ -407,64 +407,64 @@ class Router $m->connect('api/statuses/public_timeline.:format', array('action' => 'ApiTimelinePublic', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $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', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/home_timeline.:format', array('action' => 'ApiTimelineHome', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/home_timeline/:id.:format', array('action' => 'ApiTimelineHome', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/user_timeline.:format', array('action' => 'ApiTimelineUser', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/user_timeline/:id.:format', array('action' => 'ApiTimelineUser', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/mentions.:format', array('action' => 'ApiTimelineMentions', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/mentions/:id.:format', array('action' => 'ApiTimelineMentions', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/replies.:format', array('action' => 'ApiTimelineMentions', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/replies/:id.:format', array('action' => 'ApiTimelineMentions', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statuses/retweeted_by_me.:format', array('action' => 'ApiTimelineRetweetedByMe', - 'format' => '(xml|json|atom)')); + 'format' => '(xml|json|atom|as)')); $m->connect('api/statuses/retweeted_to_me.:format', array('action' => 'ApiTimelineRetweetedToMe', - 'format' => '(xml|json|atom)')); + 'format' => '(xml|json|atom|as)')); $m->connect('api/statuses/retweets_of_me.:format', array('action' => 'ApiTimelineRetweetsOfMe', - 'format' => '(xml|json|atom)')); + 'format' => '(xml|json|atom|as)')); $m->connect('api/statuses/friends.:format', array('action' => 'ApiUserFriends', @@ -625,12 +625,12 @@ class Router $m->connect('api/favorites.:format', array('action' => 'ApiTimelineFavorites', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/favorites/:id.:format', array('action' => 'ApiTimelineFavorites', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/favorites/create/:id.:format', array('action' => 'ApiFavoriteCreate', @@ -695,7 +695,7 @@ class Router $m->connect('api/statusnet/groups/timeline/:id.:format', array('action' => 'ApiTimelineGroup', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); $m->connect('api/statusnet/groups/show.:format', array('action' => 'ApiGroupShow', @@ -756,7 +756,7 @@ class Router // Tags $m->connect('api/statusnet/tags/timeline/:tag.:format', array('action' => 'ApiTimelineTag', - 'format' => '(xml|json|rss|atom)')); + 'format' => '(xml|json|rss|atom|as)')); // media related $m->connect(