diff --git a/EVENTS.txt b/EVENTS.txt
index 6cc1a7fe1c..1443a94fbe 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -1115,3 +1115,19 @@ StartGroupProfileElements: Start showing stuff about the group on its profile pa
EndGroupProfileElements: Start showing stuff about the group on its profile page
- $action: action being executed (for output and params)
- $group: group for the page
+
+StartActivityObjectOutputAtom: Called at start of Atom XML output generation for ActivityObject chunks, just inside the . Cancel the event to take over its output completely (you're responsible for calling the matching End event if so)
+- $obj: ActivityObject
+- $out: XMLOutputter to append custom output
+
+EndActivityObjectOutputAtom: Called at end of Atom XML output generation for ActivityObject chunks, just inside the
+- $obj: ActivityObject
+- $out: XMLOutputter to append custom output
+
+StartActivityObjectOutputJson: Called at start of JSON output generation for ActivityObject chunks: the array has not yet been filled out. Cancel the event to take over its output completely (you're responsible for calling the matching End event if so)
+- $obj ActivityObject
+- &$out: array to be serialized; you're free to modify it
+
+EndActivityObjectOutputJson: Called at end of JSON output generation for ActivityObject chunks: the array has not yet been filled out.
+- $obj ActivityObject
+- &$out: array to be serialized; you're free to modify it
diff --git a/lib/activityobject.php b/lib/activityobject.php
index d620bf27bb..241f99564f 100644
--- a/lib/activityobject.php
+++ b/lib/activityobject.php
@@ -533,91 +533,95 @@ class ActivityObject
$xo->elementStart($tag);
}
- $xo->element('activity:object-type', null, $this->type);
+ if (Event::handle('StartActivityObjectOutputAtom', array($this, $xo))) {
+ $xo->element('activity:object-type', null, $this->type);
- // uses URI
+ // uses URI
- if ($tag == 'author') {
- $xo->element(self::URI, null, $this->id);
- } else {
- $xo->element(self::ID, null, $this->id);
- }
-
- if (!empty($this->title)) {
- $name = common_xml_safe_str($this->title);
if ($tag == 'author') {
- // XXX: Backward compatibility hack -- atom:name should contain
- // full name here, instead of nickname, i.e.: $name. Change
- // this in the next version.
- $xo->element(self::NAME, null, $this->poco->preferredUsername);
+ $xo->element(self::URI, null, $this->id);
} else {
- $xo->element(self::TITLE, null, $name);
+ $xo->element(self::ID, null, $this->id);
}
- }
- if (!empty($this->summary)) {
- $xo->element(
- self::SUMMARY,
- null,
- common_xml_safe_str($this->summary)
- );
- }
+ if (!empty($this->title)) {
+ $name = common_xml_safe_str($this->title);
+ if ($tag == 'author') {
+ // XXX: Backward compatibility hack -- atom:name should contain
+ // full name here, instead of nickname, i.e.: $name. Change
+ // this in the next version.
+ $xo->element(self::NAME, null, $this->poco->preferredUsername);
+ } else {
+ $xo->element(self::TITLE, null, $name);
+ }
+ }
- if (!empty($this->content)) {
- // XXX: assuming HTML content here
- $xo->element(
- ActivityUtils::CONTENT,
- array('type' => 'html'),
- common_xml_safe_str($this->content)
- );
- }
-
- if (!empty($this->link)) {
- $xo->element(
- 'link',
- array(
- 'rel' => 'alternate',
- 'type' => 'text/html',
- 'href' => $this->link
- ),
- null
- );
- }
-
- if ($this->type == ActivityObject::PERSON
- || $this->type == ActivityObject::GROUP) {
-
- foreach ($this->avatarLinks as $avatar) {
+ if (!empty($this->summary)) {
$xo->element(
- 'link', array(
- 'rel' => 'avatar',
- 'type' => $avatar->type,
- 'media:width' => $avatar->width,
- 'media:height' => $avatar->height,
- 'href' => $avatar->url
+ self::SUMMARY,
+ null,
+ common_xml_safe_str($this->summary)
+ );
+ }
+
+ if (!empty($this->content)) {
+ // XXX: assuming HTML content here
+ $xo->element(
+ ActivityUtils::CONTENT,
+ array('type' => 'html'),
+ common_xml_safe_str($this->content)
+ );
+ }
+
+ if (!empty($this->link)) {
+ $xo->element(
+ 'link',
+ array(
+ 'rel' => 'alternate',
+ 'type' => 'text/html',
+ 'href' => $this->link
),
null
);
}
- }
- if (!empty($this->geopoint)) {
- $xo->element(
- 'georss:point',
- null,
- $this->geopoint
- );
- }
+ if ($this->type == ActivityObject::PERSON
+ || $this->type == ActivityObject::GROUP) {
- if (!empty($this->poco)) {
- $this->poco->outputTo($xo);
- }
+ foreach ($this->avatarLinks as $avatar) {
+ $xo->element(
+ 'link', array(
+ 'rel' => 'avatar',
+ 'type' => $avatar->type,
+ 'media:width' => $avatar->width,
+ 'media:height' => $avatar->height,
+ 'href' => $avatar->url
+ ),
+ null
+ );
+ }
+ }
- // @fixme there's no way here to make a tree; elements can only contain plaintext
- // @fixme these may collide with JSON extensions
- foreach ($this->extra as $el) {
- list($extraTag, $attrs, $content) = $el;
- $xo->element($extraTag, $attrs, $content);
+ if (!empty($this->geopoint)) {
+ $xo->element(
+ 'georss:point',
+ null,
+ $this->geopoint
+ );
+ }
+
+ if (!empty($this->poco)) {
+ $this->poco->outputTo($xo);
+ }
+
+ // @fixme there's no way here to make a tree; elements can only contain plaintext
+ // @fixme these may collide with JSON extensions
+ foreach ($this->extra as $el) {
+ list($extraTag, $attrs, $content) = $el;
+ $xo->element($extraTag, $attrs, $content);
+ }
+
+ Event::handle('EndActivityObjectOutputAtom', array($this, $xo));
}
if (!empty($tag)) {
@@ -647,94 +651,96 @@ class ActivityObject
{
$object = array();
- // XXX: attachedObjects are added by Activity
+ if (Event::handle('StartActivityObjectOutputJson', array($this, &$object))) {
+ // XXX: attachedObjects are added by Activity
- // displayName
- $object['displayName'] = $this->title;
+ // displayName
+ $object['displayName'] = $this->title;
- // TODO: downstreamDuplicates
+ // TODO: downstreamDuplicates
- // embedCode (used for video)
+ // embedCode (used for video)
- // id
- //
- // XXX: Should we use URL here? or a crazy tag URI?
- $object['id'] = $this->id;
+ // 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) {
+ 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.
+ // 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();
+ $avatarLarge = null;
+ $avatarMediaLinks = array();
- foreach ($this->avatarLinks as $a) {
+ foreach ($this->avatarLinks as $a) {
- // Make a MediaLink for every other Avatar
- $avatar = new ActivityStreamsMediaLink(
- $a->url,
- $a->width,
- $a->height,
- $a->type,
- 'avatar'
- );
+ // 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;
+ // Find the big avatar to use as the "image"
+ if ($a->height == AVATAR_PROFILE_SIZE) {
+ $imgLink = $avatar;
+ }
+
+ $avatarMediaLinks[] = $avatar->asArray();
}
- $avatarMediaLinks[] = $avatar->asArray();
+ $object['avatarLinks'] = $avatarMediaLinks; // extension
+
+ // image
+ $object['image'] = $imgLink->asArray();
}
- $object['avatarLinks'] = $avatarMediaLinks; // extension
+ // objectType
+ //
+ // We can probably use the whole schema URL here but probably the
+ // relative simple name is easier to parse
+ // @fixme this breaks extension URIs
+ $object['type'] = substr($this->type, strrpos($this->type, '/') + 1);
- // image
- $object['image'] = $imgLink->asArray();
+ // summary
+ $object['summary'] = $this->summary;
+
+ // TODO: upstreamDuplicates
+
+ // url (XXX: need to put the right thing here...)
+ $object['url'] = $this->id;
+
+ /* Extensions */
+ // @fixme these may collide with XML extensions
+ // @fixme multiple tags of same name will overwrite each other
+ // @fixme text content from XML extensions will be lost
+ 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();
+ }
+ Event::handle('EndActivityObjectOutputJson', array($this, &$object));
}
-
- // objectType
- //
- // We can probably use the whole schema URL here but probably the
- // relative simple name is easier to parse
- // @fixme this breaks extension URIs
- $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 */
- // @fixme these may collide with XML extensions
- // @fixme multiple tags of same name will overwrite each other
- // @fixme text content from XML extensions will be lost
- 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/microappplugin.php b/lib/microappplugin.php
index fbead58cc5..86803b8ae3 100644
--- a/lib/microappplugin.php
+++ b/lib/microappplugin.php
@@ -212,6 +212,44 @@ abstract class MicroAppPlugin extends Plugin
in_array($activity->objects[0]->type, $types));
}
+ /**
+ * Called when generating Atom XML ActivityStreams output from an
+ * ActivityObject belonging to this plugin. Gives the plugin
+ * a chance to add custom output.
+ *
+ * Note that you can only add output of additional XML elements,
+ * not change existing stuff here.
+ *
+ * If output is already handled by the base Activity classes,
+ * you can leave this base implementation as a no-op.
+ *
+ * @param ActivityObject $obj
+ * @param XMLOutputter $out to add elements at end of object
+ */
+ function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
+ {
+ // default is a no-op
+ }
+
+ /**
+ * Called when generating JSON ActivityStreams output from an
+ * ActivityObject belonging to this plugin. Gives the plugin
+ * a chance to add custom output.
+ *
+ * Modify the array contents to your heart's content, and it'll
+ * all get serialized out as JSON.
+ *
+ * If output is already handled by the base Activity classes,
+ * you can leave this base implementation as a no-op.
+ *
+ * @param ActivityObject $obj
+ * @param array &$out JSON-targeted array which can be modified
+ */
+ public function activityObjectOutputJson(ActivityObject $obj, array &$out)
+ {
+ // default is a no-op
+ }
+
/**
* When a notice is deleted, delete the related objects
* by calling the overridable $this->deleteRelated().
@@ -439,6 +477,46 @@ abstract class MicroAppPlugin extends Plugin
return true;
}
+ /**
+ * Event handler gives the plugin a chance to add custom
+ * Atom XML ActivityStreams output from a previously filled-out
+ * ActivityObject.
+ *
+ * The atomOutput method is called if it's one of
+ * our matching types.
+ *
+ * @param ActivityObject $obj
+ * @param XMLOutputter $out to add elements at end of object
+ * @return boolean hook return value
+ */
+ function onEndActivityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
+ {
+ if (in_array($obj->type, $this->types())) {
+ $this->activityObjectOutputAtom($obj, $out);
+ }
+ return true;
+ }
+
+ /**
+ * Event handler gives the plugin a chance to add custom
+ * JSON ActivityStreams output from a previously filled-out
+ * ActivityObject.
+ *
+ * The activityObjectOutputJson method is called if it's one of
+ * our matching types.
+ *
+ * @param ActivityObject $obj
+ * @param array &$out JSON-targeted array which can be modified
+ * @return boolean hook return value
+ */
+ function onEndActivityObjectOutputJson(ActivityObject $obj, array &$out)
+ {
+ if (in_array($obj->type, $this->types())) {
+ $this->activityObjectOutputJson($obj, &$out);
+ }
+ return true;
+ }
+
function onStartShowEntryForms(&$tabs)
{
$tabs[$this->tag()] = $this->appTitle();
diff --git a/plugins/Poll/PollPlugin.php b/plugins/Poll/PollPlugin.php
index 34c37eb526..ea6ab9ecd9 100644
--- a/plugins/Poll/PollPlugin.php
+++ b/plugins/Poll/PollPlugin.php
@@ -48,8 +48,8 @@ class PollPlugin extends MicroAppPlugin
const VERSION = '0.1';
// @fixme which domain should we use for these namespaces?
- const POLL_OBJECT = 'http://apinamespace.org/activitystreams/object/poll';
- const POLL_RESPONSE_OBJECT = 'http://apinamespace.org/activitystreams/object/poll-response';
+ const POLL_OBJECT = 'http://activityschema.org/object/poll';
+ const POLL_RESPONSE_OBJECT = 'http://activityschema.org/object/poll-response';
/**
* Database schema setup
@@ -203,26 +203,22 @@ class PollPlugin extends MicroAppPlugin
$pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
$responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
if ($pollElements->length) {
- $data = $pollElements->item(0);
- $question = $data->getAttribute('question');
+ $question = '';
$opts = array();
- foreach ($data->attributes as $node) {
- $name = $node->nodeName;
- if (substr($name, 0, 6) == 'option') {
- $n = intval(substr($name, 6));
- if ($n > 0) {
- $opts[$n - 1] = $node->nodeValue;
- }
- }
+
+ $data = $pollElements->item(0);
+ foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'question') as $node) {
+ $question = $node->textContent;
+ }
+ foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'option') as $node) {
+ $opts[] = $node->textContent;
}
- common_log(LOG_DEBUG, "YYY question: $question");
- common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
try {
$notice = Poll::saveNew($profile, $question, $opts, $options);
- common_log(LOG_DEBUG, "YYY ok: " . $notice->id);
+ common_log(LOG_DEBUG, "Saved Poll from ActivityStream data ok: notice id " . $notice->id);
return $notice;
} catch (Exception $e) {
- common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage());
+ common_log(LOG_DEBUG, "Poll save from ActivityStream data failed: " . $e->getMessage());
}
} else if ($responseElements->length) {
$data = $responseElements->item(0);
@@ -240,10 +236,10 @@ class PollPlugin extends MicroAppPlugin
}
try {
$notice = Poll_response::saveNew($profile, $poll, $selection, $options);
- common_log(LOG_DEBUG, "YYY response ok: " . $notice->id);
+ common_log(LOG_DEBUG, "Saved Poll_response ok, notice id: " . $notice->id);
return $notice;
} catch (Exception $e) {
- common_log(LOG_DEBUG, "YYY response fail: " . $e->getMessage());
+ common_log(LOG_DEBUG, "Poll response save fail: " . $e->getMessage());
}
} else {
common_log(LOG_DEBUG, "YYY no poll data");
@@ -277,33 +273,15 @@ class PollPlugin extends MicroAppPlugin
$object->link = $notice->bestUrl();
$response = Poll_response::getByNotice($notice);
- if (!$response) {
- common_log(LOG_DEBUG, "QQQ notice uri: $notice->uri");
- } else {
+ if ($response) {
$poll = $response->getPoll();
- /**
- * For the moment, using a kind of icky-looking schema that happens to
- * work with out code for generating both Atom and JSON forms, though
- * I don't like it:
- *
- *
- *
- * "poll:response": {
- * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
- * "uri": "http://..../poll/...."
- * "selection": 3
- * }
- *
- */
- // @fixme there's no way to specify an XML node tree here, like
- // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
- // @fixme XML node contents don't get shown in JSON
- $data = array('xmlns:poll' => self::POLL_OBJECT,
- 'poll' => $poll->uri,
- 'selection' => intval($response->selection));
- $object->extra[] = array('poll:response', $data, '');
+ if ($poll) {
+ // Stash data to be formatted later by
+ // $this->activityObjectOutputAtom() or
+ // $this->activityObjectOutputJson()...
+ $object->pollSelection = intval($response->selection);
+ $object->pollUri = $poll->uri;
+ }
}
return $object;
}
@@ -318,41 +296,112 @@ class PollPlugin extends MicroAppPlugin
$object->link = $notice->bestUrl();
$poll = Poll::getByNotice($notice);
- /**
- * Adding the poll-specific data. There's no standard in AS for polls,
- * so we're making stuff up.
- *
- * For the moment, using a kind of icky-looking schema that happens to
- * work with out code for generating both Atom and JSON forms, though
- * I don't like it:
- *
- *
- *
- * "poll:response": {
- * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
- * "question": "Who wants a poll question?"
- * "option1": "Option one"
- * "option2": "Option two"
- * "option3": "Option three"
- * }
- *
- */
- // @fixme there's no way to specify an XML node tree here, like
- // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
- // @fixme XML node contents don't get shown in JSON
- $data = array('xmlns:poll' => self::POLL_OBJECT,
- 'question' => $poll->question);
- foreach ($poll->getOptions() as $i => $opt) {
- $data['option' . ($i + 1)] = $opt;
+ if ($poll) {
+ // Stash data to be formatted later by
+ // $this->activityObjectOutputAtom() or
+ // $this->activityObjectOutputJson()...
+ $object->pollQuestion = $poll->question;
+ $object->pollOptions = $poll->getOptions();
}
- $object->extra[] = array('poll:poll', $data, '');
+
return $object;
}
+ /**
+ * Called when generating Atom XML ActivityStreams output from an
+ * ActivityObject belonging to this plugin. Gives the plugin
+ * a chance to add custom output.
+ *
+ * Note that you can only add output of additional XML elements,
+ * not change existing stuff here.
+ *
+ * If output is already handled by the base Activity classes,
+ * you can leave this base implementation as a no-op.
+ *
+ * @param ActivityObject $obj
+ * @param XMLOutputter $out to add elements at end of object
+ */
+ function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
+ {
+ if (isset($obj->pollQuestion)) {
+ /**
+ *
+ * Who wants a poll question?
+ * Option one
+ * Option two
+ * Option three
+ *
+ */
+ $data = array('xmlns:poll' => self::POLL_OBJECT);
+ $out->elementStart('poll:poll', $data);
+ $out->element('poll:question', array(), $obj->pollQuestion);
+ foreach ($obj->pollOptions as $opt) {
+ $out->element('poll:option', array(), $opt);
+ }
+ $out->elementEnd('poll:poll');
+ }
+ if (isset($obj->pollSelection)) {
+ /**
+ *
+ * poll="http://..../poll/...."
+ * selection="3" />
+ */
+ $data = array('xmlns:poll' => self::POLL_OBJECT,
+ 'poll' => $obj->pollUri,
+ 'selection' => $obj->pollSelection);
+ $out->element('poll:response', $data, '');
+ }
+ }
+
+ /**
+ * Called when generating JSON ActivityStreams output from an
+ * ActivityObject belonging to this plugin. Gives the plugin
+ * a chance to add custom output.
+ *
+ * Modify the array contents to your heart's content, and it'll
+ * all get serialized out as JSON.
+ *
+ * If output is already handled by the base Activity classes,
+ * you can leave this base implementation as a no-op.
+ *
+ * @param ActivityObject $obj
+ * @param array &$out JSON-targeted array which can be modified
+ */
+ public function activityObjectOutputJson(ActivityObject $obj, array &$out)
+ {
+ common_log(LOG_DEBUG, 'QQQ: ' . var_export($obj, true));
+ if (isset($obj->pollQuestion)) {
+ /**
+ * "poll": {
+ * "question": "Who wants a poll question?",
+ * "options": [
+ * "Option 1",
+ * "Option 2",
+ * "Option 3"
+ * ]
+ * }
+ */
+ $data = array('question' => $obj->pollQuestion,
+ 'options' => array());
+ foreach ($obj->pollOptions as $opt) {
+ $data['options'][] = $opt;
+ }
+ $out['poll'] = $data;
+ }
+ if (isset($obj->pollSelection)) {
+ /**
+ * "pollResponse": {
+ * "poll": "http://..../poll/....",
+ * "selection": 3
+ * }
+ */
+ $data = array('poll' => $obj->pollUri,
+ 'selection' => $obj->pollSelection);
+ $out['pollResponse'] = $data;
+ }
+ }
+
+
/**
* @fixme WARNING WARNING WARNING parent class closes the final div that we
* open here, but we probably shouldn't open it here. Check parent class