OStatus: consolidate the low-level notice save code between Salmon and PuSH input paths. Validation etc remains at higher levels.
This commit is contained in:
parent
2aaf8d4e30
commit
584b87cfe5
|
@ -314,7 +314,7 @@ class OStatusPlugin extends Plugin
|
||||||
{
|
{
|
||||||
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
|
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
|
||||||
if ($oprofile) {
|
if ($oprofile) {
|
||||||
$oprofile->processFeed($feed);
|
$oprofile->processFeed($feed, 'push');
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
|
common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,7 +488,7 @@ class Ostatus_profile extends Memcached_DataObject
|
||||||
*
|
*
|
||||||
* @param DOMDocument $feed
|
* @param DOMDocument $feed
|
||||||
*/
|
*/
|
||||||
public function processFeed($feed)
|
public function processFeed($feed, $source)
|
||||||
{
|
{
|
||||||
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
|
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
|
||||||
if ($entries->length == 0) {
|
if ($entries->length == 0) {
|
||||||
|
@ -498,7 +498,7 @@ class Ostatus_profile extends Memcached_DataObject
|
||||||
|
|
||||||
for ($i = 0; $i < $entries->length; $i++) {
|
for ($i = 0; $i < $entries->length; $i++) {
|
||||||
$entry = $entries->item($i);
|
$entry = $entries->item($i);
|
||||||
$this->processEntry($entry, $feed);
|
$this->processEntry($entry, $feed, $source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,15 +508,12 @@ class Ostatus_profile extends Memcached_DataObject
|
||||||
* @param DOMElement $entry
|
* @param DOMElement $entry
|
||||||
* @param DOMElement $feed for context
|
* @param DOMElement $feed for context
|
||||||
*/
|
*/
|
||||||
protected function processEntry($entry, $feed)
|
public function processEntry($entry, $feed, $source)
|
||||||
{
|
{
|
||||||
$activity = new Activity($entry, $feed);
|
$activity = new Activity($entry, $feed);
|
||||||
|
|
||||||
$debug = var_export($activity, true);
|
|
||||||
common_log(LOG_DEBUG, $debug);
|
|
||||||
|
|
||||||
if ($activity->verb == ActivityVerb::POST) {
|
if ($activity->verb == ActivityVerb::POST) {
|
||||||
$this->processPost($activity);
|
$this->processPost($activity, $source);
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
|
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
|
||||||
}
|
}
|
||||||
|
@ -525,35 +522,47 @@ class Ostatus_profile extends Memcached_DataObject
|
||||||
/**
|
/**
|
||||||
* Process an incoming post activity from this remote feed.
|
* Process an incoming post activity from this remote feed.
|
||||||
* @param Activity $activity
|
* @param Activity $activity
|
||||||
|
* @param string $method 'push' or 'salmon'
|
||||||
|
* @return mixed saved Notice or false
|
||||||
* @fixme break up this function, it's getting nasty long
|
* @fixme break up this function, it's getting nasty long
|
||||||
*/
|
*/
|
||||||
protected function processPost($activity)
|
public function processPost($activity, $method)
|
||||||
{
|
{
|
||||||
if ($this->isGroup()) {
|
if ($this->isGroup()) {
|
||||||
|
// A group feed will contain posts from multiple authors.
|
||||||
// @fixme validate these profiles in some way!
|
// @fixme validate these profiles in some way!
|
||||||
$oprofile = self::ensureActorProfile($activity);
|
$oprofile = self::ensureActorProfile($activity);
|
||||||
|
if ($oprofile->isGroup()) {
|
||||||
|
// Groups can't post notices in StatusNet.
|
||||||
|
common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Individual user feeds may contain only posts from themselves.
|
||||||
|
// Authorship is validated against the profile URI on upper layers,
|
||||||
|
// through PuSH setup or Salmon signature checks.
|
||||||
$actorUri = self::getActorProfileURI($activity);
|
$actorUri = self::getActorProfileURI($activity);
|
||||||
if ($actorUri == $this->uri) {
|
if ($actorUri == $this->uri) {
|
||||||
// @fixme check if profile info has changed and update it
|
// @fixme check if profile info has changed and update it
|
||||||
} else {
|
} else {
|
||||||
// @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely
|
common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
|
||||||
common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri");
|
return false;
|
||||||
//return;
|
|
||||||
}
|
}
|
||||||
$oprofile = $this;
|
$oprofile = $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The id URI will be used as a unique identifier for for the notice,
|
||||||
|
// protecting against duplicate saves. It isn't required to be a URL;
|
||||||
|
// tag: URIs for instance are found in Google Buzz feeds.
|
||||||
$sourceUri = $activity->object->id;
|
$sourceUri = $activity->object->id;
|
||||||
|
|
||||||
$dupe = Notice::staticGet('uri', $sourceUri);
|
$dupe = Notice::staticGet('uri', $sourceUri);
|
||||||
|
|
||||||
if ($dupe) {
|
if ($dupe) {
|
||||||
common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
|
common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll also want to save a web link to the original notice, if provided.
|
||||||
$sourceUrl = null;
|
$sourceUrl = null;
|
||||||
|
|
||||||
if ($activity->object->link) {
|
if ($activity->object->link) {
|
||||||
$sourceUrl = $activity->object->link;
|
$sourceUrl = $activity->object->link;
|
||||||
} else if ($activity->link) {
|
} else if ($activity->link) {
|
||||||
|
@ -563,103 +572,126 @@ class Ostatus_profile extends Memcached_DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get (safe!) HTML and text versions of the content
|
// Get (safe!) HTML and text versions of the content
|
||||||
|
$rendered = $this->purify($activity->object->content);
|
||||||
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
|
|
||||||
|
|
||||||
$html = $activity->object->content;
|
|
||||||
|
|
||||||
$purifier = new HTMLPurifier();
|
|
||||||
|
|
||||||
$rendered = $purifier->purify($html);
|
|
||||||
|
|
||||||
$content = html_entity_decode(strip_tags($rendered));
|
$content = html_entity_decode(strip_tags($rendered));
|
||||||
|
|
||||||
$params = array('is_local' => Notice::REMOTE_OMB,
|
$options = array('is_local' => Notice::REMOTE_OMB,
|
||||||
'url' => $sourceUrl,
|
'url' => $sourceUrl,
|
||||||
'uri' => $sourceUri,
|
'uri' => $sourceUri,
|
||||||
'rendered' => $rendered);
|
'rendered' => $rendered,
|
||||||
|
'replies' => array(),
|
||||||
|
'groups' => array());
|
||||||
|
|
||||||
|
// Check for optional attributes...
|
||||||
|
|
||||||
|
if (!empty($activity->time)) {
|
||||||
|
$options['created'] = common_sql_date($activity->time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($activity->context) {
|
||||||
|
// Any individual or group attn: targets?
|
||||||
|
$replies = $activity->context->attention;
|
||||||
|
$options['groups'] = $this->filterReplies($oprofile, $replies);
|
||||||
|
$options['replies'] = $replies;
|
||||||
|
|
||||||
|
// Maintain direct reply associations
|
||||||
|
// @fixme what about conversation ID?
|
||||||
|
if (!empty($activity->context->replyToID)) {
|
||||||
|
$orig = Notice::staticGet('uri',
|
||||||
|
$activity->context->replyToID);
|
||||||
|
if (!empty($orig)) {
|
||||||
|
$options['reply_to'] = $orig->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$location = $activity->context->location;
|
$location = $activity->context->location;
|
||||||
|
|
||||||
if ($location) {
|
if ($location) {
|
||||||
$params['lat'] = $location->lat;
|
$options['lat'] = $location->lat;
|
||||||
$params['lon'] = $location->lon;
|
$options['lon'] = $location->lon;
|
||||||
if ($location->location_id) {
|
if ($location->location_id) {
|
||||||
$params['location_ns'] = $location->location_ns;
|
$options['location_ns'] = $location->location_ns;
|
||||||
$params['location_id'] = $location->location_id;
|
$options['location_id'] = $location->location_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$profile = $oprofile->localProfile();
|
try {
|
||||||
$params['groups'] = array();
|
$saved = Notice::saveNew($oprofile->profile_id,
|
||||||
$params['replies'] = array();
|
$content,
|
||||||
if ($activity->context) {
|
'ostatus',
|
||||||
foreach ($activity->context->attention as $recipient) {
|
$options);
|
||||||
$roprofile = Ostatus_profile::staticGet('uri', $recipient);
|
if ($saved) {
|
||||||
if ($roprofile) {
|
Ostatus_source::saveNew($saved, $this, $method);
|
||||||
if ($roprofile->isGroup()) {
|
|
||||||
// Deliver to local recipients of this remote group.
|
|
||||||
// @fixme sender verification?
|
|
||||||
$params['groups'][] = $roprofile->group_id;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Delivery to remote users is the source service's job.
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
common_log(LOG_ERR, "OStatus save of remote message $sourceUri failed: " . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
common_log(LOG_INFO, "OStatus saved remote message $sourceUri as notice id $saved->id");
|
||||||
|
return $saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up HTML
|
||||||
|
*/
|
||||||
|
protected function purify($html)
|
||||||
|
{
|
||||||
|
// @fixme disable caching or set a sane temp dir
|
||||||
|
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
|
||||||
|
$purifier = new HTMLPurifier();
|
||||||
|
return $purifier->purify($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a list of recipient ID URIs to just those for local delivery.
|
||||||
|
* @param Ostatus_profile local profile of sender
|
||||||
|
* @param array in/out &$attention_uris set of URIs, will be pruned on output
|
||||||
|
* @return array of group IDs
|
||||||
|
*/
|
||||||
|
protected function filterReplies($sender, &$attention_uris)
|
||||||
|
{
|
||||||
|
$groups = array();
|
||||||
|
$replies = array();
|
||||||
|
foreach ($attention_uris as $recipient) {
|
||||||
|
// Is the recipient a local user?
|
||||||
$user = User::staticGet('uri', $recipient);
|
$user = User::staticGet('uri', $recipient);
|
||||||
if ($user) {
|
if ($user) {
|
||||||
// An @-reply directed to a local user.
|
|
||||||
// @fixme sender verification, spam etc?
|
// @fixme sender verification, spam etc?
|
||||||
$params['replies'][] = $recipient;
|
$replies[] = $recipient;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the recipient a remote group?
|
||||||
|
$oprofile = Ostatus_profile::staticGet('uri', $recipient);
|
||||||
|
if ($oprofile) {
|
||||||
|
if ($oprofile->isGroup()) {
|
||||||
|
// Deliver to local members of this remote group.
|
||||||
|
// @fixme sender verification?
|
||||||
|
$groups[] = $oprofile->group_id;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the recipient a local group?
|
||||||
// @fixme we need a uri on user_group
|
// @fixme we need a uri on user_group
|
||||||
// $group = User_group::staticGet('uri', $recipient);
|
// $group = User_group::staticGet('uri', $recipient);
|
||||||
$template = common_local_url('groupbyid', array('id' => '31337'));
|
$template = common_local_url('groupbyid', array('id' => '31337'));
|
||||||
$template = preg_quote($template, '/');
|
$template = preg_quote($template, '/');
|
||||||
$template = str_replace('31337', '(\d+)', $template);
|
$template = str_replace('31337', '(\d+)', $template);
|
||||||
common_log(LOG_DEBUG, $template);
|
|
||||||
if (preg_match("/$template/", $recipient, $matches)) {
|
if (preg_match("/$template/", $recipient, $matches)) {
|
||||||
$id = $matches[1];
|
$id = $matches[1];
|
||||||
$group = User_group::staticGet('id', $id);
|
$group = User_group::staticGet('id', $id);
|
||||||
if ($group) {
|
if ($group) {
|
||||||
// Deliver to all members of this local group.
|
// Deliver to all members of this local group if allowed.
|
||||||
// @fixme sender verification?
|
if ($sender->localProfile()->isMember($group)) {
|
||||||
if ($profile->isMember($group)) {
|
$groups[] = $group->id;
|
||||||
common_log(LOG_DEBUG, "delivering to group $id $group->nickname");
|
|
||||||
$params['groups'][] = $group->id;
|
|
||||||
} else {
|
|
||||||
common_log(LOG_DEBUG, "not delivering to group $id $group->nickname because sender $profile->nickname is not a member");
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
|
||||||
common_log(LOG_DEBUG, "not delivering to missing group $id");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
common_log(LOG_DEBUG, "not delivering to groups for $recipient");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$attention_uris = $replies;
|
||||||
try {
|
return $groups;
|
||||||
$saved = Notice::saveNew($profile->id,
|
|
||||||
$content,
|
|
||||||
'ostatus',
|
|
||||||
$params);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
common_log(LOG_ERR, "Failed saving notice entry for $sourceUri: " . $e->getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record which feed this came through...
|
|
||||||
try {
|
|
||||||
Ostatus_source::saveNew($saved, $this, 'push');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
common_log(LOG_ERR, "Failed saving ostatus_source entry for $saved->notice_id: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -185,54 +185,6 @@ class SalmonAction extends Action
|
||||||
function saveNotice()
|
function saveNotice()
|
||||||
{
|
{
|
||||||
$oprofile = $this->ensureProfile();
|
$oprofile = $this->ensureProfile();
|
||||||
|
return $oprofile->processPost($this->act, 'salmon');
|
||||||
// Get (safe!) HTML and text versions of the content
|
|
||||||
|
|
||||||
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
|
|
||||||
|
|
||||||
$html = $this->act->object->content;
|
|
||||||
|
|
||||||
$purifier = new HTMLPurifier();
|
|
||||||
|
|
||||||
$rendered = $purifier->purify($html);
|
|
||||||
|
|
||||||
$content = html_entity_decode(strip_tags($rendered));
|
|
||||||
|
|
||||||
$options = array('is_local' => Notice::REMOTE_OMB,
|
|
||||||
'uri' => $this->act->object->id,
|
|
||||||
'url' => $this->act->object->link,
|
|
||||||
'rendered' => $rendered,
|
|
||||||
'replies' => $this->act->context->attention);
|
|
||||||
|
|
||||||
if (!empty($this->act->context->location)) {
|
|
||||||
$options['lat'] = $location->lat;
|
|
||||||
$options['lon'] = $location->lon;
|
|
||||||
if ($location->location_id) {
|
|
||||||
$options['location_ns'] = $location->location_ns;
|
|
||||||
$options['location_id'] = $location->location_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($this->act->context->replyToID)) {
|
|
||||||
$orig = Notice::staticGet('uri',
|
|
||||||
$this->act->context->replyToID);
|
|
||||||
if (!empty($orig)) {
|
|
||||||
$options['reply_to'] = $orig->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($this->act->time)) {
|
|
||||||
$options['created'] = common_sql_date($this->act->time);
|
|
||||||
}
|
|
||||||
|
|
||||||
$saved = Notice::saveNew($oprofile->profile_id,
|
|
||||||
$content,
|
|
||||||
'ostatus+salmon',
|
|
||||||
$options);
|
|
||||||
|
|
||||||
// Record that this was saved through a validated Salmon source
|
|
||||||
// @fixme actually do the signature validation!
|
|
||||||
Ostatus_source::saveNew($saved, $oprofile, 'salmon');
|
|
||||||
return $saved;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user