From 6376b78a80bc2d912a93d9eeefdbefd30a691849 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 10 Oct 2015 11:08:17 +0200 Subject: [PATCH] I think I have made the delete verb generate proper AS ActivityStreams had a different/better definition than me in how to define the Delete verb data: http://wiki.activitystrea.ms/w/page/23541872/Delete --- classes/Notice.php | 19 ++-- .../ActivityModerationPlugin.php | 95 ++++++++++++++----- .../classes/Deleted_notice.php | 36 +++---- 3 files changed, 97 insertions(+), 53 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 29125c6d83..5c7d9f1026 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -281,9 +281,7 @@ class Notice extends Managed_DataObject } public function getObjectType($canonical=false) { - return $canonical - ? ActivityObject::canonicalType($this->object_type) - : $this->object_type; + return ActivityUtils::resolveUri($this->object_type, $canonical); } public static function getByUri($uri) @@ -771,8 +769,9 @@ class Notice extends Managed_DataObject } extract($options, EXTR_SKIP); + // dupe check $stored = new Notice(); - if (!empty($uri)) { + if (!empty($uri) && !ActivityUtils::compareVerbs($act->verb, array(ActivityVerb::DELETE))) { $stored->uri = $uri; if ($stored->find()) { common_debug('cannot create duplicate Notice URI: '.$stored->uri); @@ -886,11 +885,15 @@ class Notice extends Managed_DataObject } if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { - $stored->object_type = $act->type ?: $act->objects[0]->type; - if (empty($stored->object_type)) { + if (empty($act->objects[0]->type)) { // Default type for the post verb is 'note', but we know it's // a 'comment' if it is in reply to something. $stored->object_type = empty($stored->reply_to) ? ActivityObject::NOTE : ActivityObject::COMMENT; + } else { + //TODO: Is it safe to always return a relative URI? The + // JSON version of ActivityStreams always use it, so we + // should definitely be able to handle it... + $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type, true); } } @@ -898,7 +901,7 @@ class Notice extends Managed_DataObject // XXX: some of these functions write to the DB try { - $stored->insert(); // throws exception on error + $result = $stored->insert(); // throws exception on error if ($notloc instanceof Notice_location) { $notloc->notice_id = $stored->getID(); @@ -910,7 +913,7 @@ class Notice extends Managed_DataObject $object = null; Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); if (empty($object)) { - throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->uri . ': '.$act->asString()); + throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->getUri() . ': '.$act->asString()); } // If it's not part of a conversation, it's the beginning diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php index 34735ebf2c..4ddcf27c25 100644 --- a/plugins/ActivityModeration/ActivityModerationPlugin.php +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -83,22 +83,84 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin */ protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array()) { + // Everything is done in the StartNoticeSave event + return true; + } + + // FIXME: Put this in lib/activityhandlerplugin.php when we're ready + // with the other microapps/activityhandlers as well. + // Also it should be StartNoticeAsActivity (with a prepped Activity, including ->context etc.) + public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null) + { + if (!$this->isMyNotice($stored)) { + return true; + } + + common_debug('Extending activity '.$stored->id.' with '.get_called_class()); + $this->extendActivity($stored, $act, $scoped); + return false; + } + + /** + * This is run before ->insert, so our task in this function is just to + * delete if it is the delete verb. + */ + public function onStartNoticeSave(Notice $stored) + { + // DELETE is a bit special, we have to remove the existing entry and then + // add a new one with the same URI in order to trigger the distribution. + // (that's why we don't use $this->isMyNotice(...)) + if (!ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::DELETE))) { + return true; + } + + try { + $target = Notice::getByUri($stored->uri); + } catch (NoResultException $e) { + throw new AlreadyFulfilledException('Notice URI not found, so we have nothing to delete.'); + } + + $actor = $stored->getProfile(); + $owner = $target->getProfile(); + + if ($owner->hasRole(Profile_role::DELETED)) { + // Don't bother with replacing notices if its author is being deleted. + // The later "StoreActivityObject" will pick this up and execute + // the deletion then. + // (the "delete verb notice" is too new to ever pass through Notice::saveNew + // which otherwise wouldn't execute the StoreActivityObject event) + return true; + } + + // Since the user deleting may not be the same as the notice's owner, + // double-check this and also set the "re-stored" notice profile_id. + if (!$actor->sameAs($owner) && !$actor->hasRight(Right::DELETEOTHERSNOTICE)) { + throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.')); + } + + // We copy the identifying fields and replace the sensitive ones. + //$stored->id = $target->id; // We can't copy this since DB_DataObject won't inject it anyway + $props = array('uri', 'profile_id', 'conversation', 'reply_to', 'created', 'repeat_of', 'object_type', 'is_local', 'scope'); + foreach($props as $prop) { + $stored->$prop = $target->$prop; + } + //$stored->content = $stored->content ?: _('Notice deleted.'); + //$stored->rendered = $stored->rendered ?: $stored->rendered; + common_debug('DELETENOTICE: Replacement notice has been prepared: '.var_export($stored, true)); + // Let's see if this has been deleted already. - $deleted = Deleted_notice::getKV('uri', $act->id); + $deleted = Deleted_notice::getKV('uri', $stored->getUri()); if ($deleted instanceof Deleted_notice) { return $deleted; } - $target = Notice::getByUri($act->objects[0]->id); - common_debug('DELETING notice: ' . $act->objects[0]->id . ' on behalf of profile id==' . $target->getProfile()->getID()); - $deleted = new Deleted_notice(); $deleted->id = $target->getID(); - $deleted->profile_id = $target->getProfile()->getID(); - $deleted->uri = $act->id; - $deleted->act_uri = $target->getUri(); - $deleted->act_created = $target->created; + $deleted->profile_id = $actor->getID(); + $deleted->uri = $stored->getUri(); + $deleted->act_uri = $stored->getUri(); + $deleted->act_created = $stored->created; $deleted->created = common_sql_now(); $result = $deleted->insert(); @@ -106,23 +168,10 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin throw new ServerException('Could not insert Deleted_notice entry into database!'); } + // Now we delete the original notice, leaving the id and uri free. $target->delete(); - return $deleted; - } - - // FIXME: Put this in lib/activityhandlerplugin.php when we're ready - // with the other microapps/activityhandlers as well. - // Also it should be StartNoticeAsActivity (with a prepped Activity, including ->context etc.) - public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null) - { - if (!$this->isMyNotice($stored)) { - return true; - } - - common_debug('Extending activity '.$stored->id.' with '.get_called_class()); - $this->extendActivity($stored, $act, $scoped); - return false; + return true; } public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index 2dc16b827f..b7413cfb27 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -38,7 +38,7 @@ class Deleted_notice extends Managed_DataObject return array( 'fields' => array( 'id' => array('type' => 'int', 'not null' => true, 'description' => 'notice ID'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'), + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile that deleted the notice'), 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the deleted notice'), 'act_uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'), 'act_created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), @@ -67,10 +67,9 @@ class Deleted_notice extends Managed_DataObject } $act = new Activity(); - $act->type = ActivityObject::ACTIVITY; $act->verb = ActivityVerb::DELETE; $act->time = time(); - $act->id = self::newUri($actor, $notice); + $act->id = $notice->getUri(); $act->content = sprintf(_m('%2$s deleted notice {{%4$s}}.'), htmlspecialchars($actor->getUrl()), @@ -82,6 +81,7 @@ class Deleted_notice extends Managed_DataObject $act->actor = $actor->asActivityObject(); $act->target = new ActivityObject(); // We don't save the notice object, as it's supposed to be removed! $act->target->id = $notice->getUri(); + $act->target->type = $notice->getObjectType(); $act->objects = array(clone($act->target)); $url = $notice->getUrl(); @@ -107,11 +107,13 @@ class Deleted_notice extends Managed_DataObject return $object; } + // The one who deleted the notice, not the notice's author public function getActor() { return Profile::getByID($this->profile_id); } + // As above: The one who deleted the notice, not the notice's author public function getActorObject() { return $this->getActor()->asActivityObject(); @@ -126,23 +128,13 @@ class Deleted_notice extends Managed_DataObject public function getStored() { - $uri = $this->getTargetUri(); + $uri = $this->getUri(); if (!isset($this->_stored[$uri])) { - $stored = new Notice(); - $stored->uri = $uri; - if (!$stored->find(true)) { - throw new NoResultException($stored); - } - $this->_stored[$uri] = $stored; + $this->_stored[$uri] = Notice::getByPK('uri', $uri); } return $this->_stored[$uri]; } - public function getTargetUri() - { - return $this->act_uri; - } - public function getUri() { return $this->uri; @@ -155,7 +147,8 @@ class Deleted_notice extends Managed_DataObject $actobj->type = ActivityObject::ACTIVITY; $actobj->actor = $this->getActorObject(); $actobj->target = new ActivityObject(); - $actobj->target->id = $this->getTargetUri(); + $actobj->target->id = $this->getUri(); + // FIXME: actobj->target->type? as in extendActivity, and actobj->target->actor maybe? $actobj->objects = array(clone($actobj->target)); $actobj->verb = ActivityVerb::DELETE; $actobj->title = ActivityUtils::verbToTitle($actobj->verb); @@ -164,7 +157,7 @@ class Deleted_notice extends Managed_DataObject $actobj->content = sprintf(_m('%2$s deleted notice {{%3$s}}.'), htmlspecialchars($actor->getUrl()), htmlspecialchars($actor->getBestName()), - htmlspecialchars($this->getTargetUri()) + htmlspecialchars($this->getUri()) ); return $actobj; @@ -172,14 +165,13 @@ class Deleted_notice extends Managed_DataObject static public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) { - // the original notice is deleted, but we have stored some important data - $object = self::fromStored($stored); - + // the original notice id and type is still stored in the Notice table + // so we use that information to describe the delete activity $act->target = new ActivityObject(); - $act->target->id = $object->getTargetUri(); + $act->target->id = $stored->getUri(); + $act->target->type = $stored->getObjectType(); $act->objects = array(clone($act->target)); - $act->context->replyToID = $object->getTargetUri(); $act->title = ActivityUtils::verbToTitle($act->verb); } -- 2.39.5