From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 00:02:37 +0000 (+0200) Subject: Some work on ActivityModeration with notice deletion X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=88f7bb1ed5faded5563eeb1d75d5fb44126b0712;p=quix0rs-gnu-social.git Some work on ActivityModeration with notice deletion Let's now create an event called DeleteNotice and also make sure we handle the onNoticeDeleteRelated properly in ActivityModeration to avoid possible endless loops etc. --- diff --git a/classes/Deleted_notice.php b/classes/Deleted_notice.php deleted file mode 100644 index 23bbea1bab..0000000000 --- a/classes/Deleted_notice.php +++ /dev/null @@ -1,54 +0,0 @@ -. - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Table Definition for deleted_notice - */ - -class Deleted_notice extends Managed_DataObject -{ - public $__table = 'deleted_notice'; // table name - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) not_null - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $created; // datetime() not_null - public $deleted; // datetime() not_null - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), - 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), - 'deleted' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'deleted_notice_uri_key' => array('uri'), - ), - 'indexes' => array( - 'deleted_notice_profile_id_idx' => array('profile_id'), - ), - ); - } -} diff --git a/classes/Notice.php b/classes/Notice.php index 44d5adb1b7..737f186a10 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -163,34 +163,11 @@ class Notice extends Managed_DataObject if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) { return $this->delete(); } - throw new AuthorizationException('You are not allowed to delete other user\'s notices'); + throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); } - function delete($useWhere=false) + public function delete($useWhere=false) { - // For auditing purposes, save a record that the notice - // was deleted. - - // @fixme we have some cases where things get re-run and so the - // insert fails. - $deleted = Deleted_notice::getKV('id', $this->id); - - if (!$deleted instanceof Deleted_notice) { - $deleted = Deleted_notice::getKV('uri', $this->uri); - } - - if (!$deleted instanceof Deleted_notice) { - $deleted = new Deleted_notice(); - - $deleted->id = $this->id; - $deleted->profile_id = $this->profile_id; - $deleted->uri = $this->uri; - $deleted->created = $this->created; - $deleted->deleted = common_sql_now(); - - $deleted->insert(); - } - if (Event::handle('NoticeDeleteRelated', array($this))) { // Clear related records diff --git a/db/core.php b/db/core.php index d779717fd4..f654d79d99 100644 --- a/db/core.php +++ b/db/core.php @@ -72,7 +72,6 @@ $classes = array('Schema_version', 'Group_block', 'Group_alias', 'Session', - 'Deleted_notice', 'Config', 'Profile_role', 'Location_namespace', diff --git a/lib/default.php b/lib/default.php index 38b8bcb1af..554d3ae63c 100644 --- a/lib/default.php +++ b/lib/default.php @@ -306,6 +306,7 @@ $default = array('core' => array( 'ActivityVerb' => array(), 'ActivityVerbPost' => array(), + 'ActivityModeration' => array(), 'AuthCrypt' => array(), 'Cronish' => array(), 'Favorite' => array(), diff --git a/lib/deletenoticeform.php b/lib/deletenoticeform.php deleted file mode 100644 index d06004613a..0000000000 --- a/lib/deletenoticeform.php +++ /dev/null @@ -1,67 +0,0 @@ -notice = $formOpts['notice']; - } - - function id() - { - return 'form_notice_delete-' . $this->notice->getID(); - } - - function formClass() - { - return 'form_settings'; - } - - function action() - { - return common_local_url('deletenotice', array('notice' => $this->notice->getID())); - } - - function formLegend() - { - $this->out->element('legend', null, _('Delete notice')); - } - - function formData() - { - $this->out->element('p', null, _('Are you sure you want to delete this notice?')); - } - - /** - * Action elements - * - * @return void - */ - function formActions() - { - $this->out->submit('form_action-no', - // TRANS: Button label on the delete notice form. - _m('BUTTON','No'), - 'submit form_action-primary', - 'no', - // TRANS: Submit button title for 'No' when deleting a notice. - _('Do not delete this notice.')); - $this->out->submit('form_action-yes', - // TRANS: Button label on the delete notice form. - _m('BUTTON','Yes'), - 'submit form_action-secondary', - 'yes', - // TRANS: Submit button title for 'Yes' when deleting a notice. - _('Delete this notice.')); - } -} diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php new file mode 100644 index 0000000000..1494833992 --- /dev/null +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -0,0 +1,118 @@ + + */ +class ActivityModerationPlugin extends ActivityVerbHandlerPlugin +{ + public function tag() + { + return 'actmod'; + } + + public function types() + { + return array(); + } + + public function verbs() + { + return array(ActivityVerb::DELETE); + } + + public function onBeforePluginCheckSchema() + { + Deleted_notice::beforeSchemaUpdate(); + return true; + } + + public function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('deleted_notice', Deleted_notice::schemaDef()); + return true; + } + + protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // FIXME: switch based on action type + return _m('TITLE', 'Notice moderation'); + } + + protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // pass + } + + protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + switch (true) { + case ActivityUtils::compareVerbs($verb, array(ActivityVerb::DELETE)): + // do whatever preparation is necessary to delete a verb + $target->delete(); + break; + default: + throw new ServerException('ActivityVerb POST not handled by plugin that was supposed to do it.'); + } + } + + public function deleteRelated(Notice $notice) + { + if ($notice->getProfile()->hasRole(Profile_role::DELETED)) { + // Don't save a new Deleted_notice entry if the profile is being deleted + return true; + } + + // For auditing purposes, save a record that the notice was deleted. + return Deleted_notice::addNew($notice); + } + + /** + * This is run when a 'delete' verb activity comes in. + * + * @return boolean hook flag + */ + protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array()) + { + // Let's see if this has been deleted already. + $deleted = Deleted_notice::getKV('uri', $act->id); + if ($deleted instanceof Deleted_notice) { + return $deleted; + } + + common_debug('DELETING notice: ' . $act->objects[0]->id); + $target = Notice::getByUri($act->objects[0]->id); + + $deleted = new Deleted_notice(); + + $deleted->id = $target->getID(); + $deleted->profile_id = $target->getProfile()->getID(); + $deleted->uri = Deleted_notice::newUri($target->getProfile(), $target); + $deleted->act_uri = $target->getUri(); + $deleted->act_created = $target->created; + $deleted->created = common_sql_now(); + + common_debug('DELETING notice, storing Deleted_notice entry'); + $deleted->insert(); + + common_debug('DELETING notice, actually deleting now!'); + $target->delete(); + + return $deleted; + } + + public function activityObjectFromNotice(Notice $notice) + { + $object = Deleted_notice::fromStored($notice); + return $object->asActivityObject(); + } + + protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + if (!$scoped instanceof Profile || !($scoped->sameAs($target->getProfile()) || $scoped->hasRight(Right::DELETEOTHERSNOTICE))) { + throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); + } + return DeletenoticeForm($action, array('notice'=>$target)); + } +} diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php new file mode 100644 index 0000000000..c450c0eb63 --- /dev/null +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -0,0 +1,224 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Table Definition for deleted_notice + */ + +class Deleted_notice extends Managed_DataObject +{ + public $__table = 'deleted_notice'; // table name + public $id; // int(4) primary_key not_null + public $profile_id; // int(4) not_null + public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space + public $act_uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space + public $created; // datetime() not_null + public $deleted; // datetime() not_null + + public static function schemaDef() + { + return array( + 'fields' => array( + 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'), + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of 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'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was deleted'), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'deleted_notice_act_uri_key' => array('act_uri'), + ), + 'indexes' => array( + 'deleted_notice_profile_id_idx' => array('profile_id'), + ), + ); + } + + public static function addNew(Notice $notice) + { + $actor = $notice->getProfile(); + + if ($actor->hasRole(Profile_role::DELETED)) { + // Don't emit notices if the user is deleted + return true; + } + + $act = new Activity(); + $act->type = ActivityObject::ACTIVITY; + $act->verb = ActivityVerb::DELETE; + $act->time = time(); + $act->id = TagURI::mint('deleted_notice:%d:%d:%s', + $actor->getID(), + $notice->getID(), + common_date_iso8601(common_sql_now())); + + $act->content = sprintf(_m('%2$s deleted notice {{%4$s}}.'), + htmlspecialchars($actor->getUrl()), + htmlspecialchars($actor->getBestName()), + htmlspecialchars($notice->getUrl()), + htmlspecialchars($notice->getUri()) + ); + + $act->actor = $actor->asActivityObject(); + $act->target = new ActivityObject(); + $act->target->id = $notice->getUri(); + $act->objects = array(clone($act->target)); + + $url = $notice->getUrl(); + $act->selfLink = $url; + $act->editLink = $url; + + // This will make ActivityModeration run saveObjectFromActivity which adds + // a new Deleted_notice entry in the database as well as deletes the notice + // if the actor has permission to do so. + $stored = Notice::saveActivity($act, $actor); + + return $stored; + } + + static public function fromStored(Notice $stored) + { + $class = get_called_class(); + $object = new $class; + $object->act_uri = $stored->getUri(); + if (!$object->find(true)) { + throw new NoResultException($object); + } + return $object; + } + + public function getActor() + { + return Profile::getByID($this->profile_id); + } + + static public function getObjectType() + { + return 'activity'; + } + + protected $_stored = array(); + + public function getStored() + { + $uri = $this->getTargetUri(); + if (!isset($this->_stored[$uri])) { + $stored = new Notice(); + $stored->uri = $uri; + if (!$stored->find(true)) { + throw new NoResultException($stored); + } + $this->_stored[$uri] = $stored; + } + return $this->_stored[$uri]; + } + + public function getTargetUri() + { + return $this->uri; + } + + public function getUri() + { + return $this->act_uri; + } + + public function asActivityObject(Profile $scoped=null) + { + $actobj = new ActivityObject(); + $actobj->id = $this->getUri(); + $actobj->type = ActivityUtils::resolveUri(self::getObjectType()); + $actobj->actor = $this->getActorObject(); + $actobj->target = new ActivityObject(); + $actobj->target->id = $this->getTargetUri(); + $actobj->objects = array(clone($actobj->target)); + $actobj->verb = ActivityVerb::DELETE; + $actobj->title = ActivityUtils::verbToTitle($actobj->verb); + + $actor = $this->getActor(); + $actobj->content = sprintf(_m('%2$s deleted notice {{%3$s}}.'), + htmlspecialchars($actor->getUrl()), + htmlspecialchars($actor->getBestName()), + htmlspecialchars($actor->getTargetUri()) + ); + + return $actobj; + } + + static function newUri(Profile $actor, Managed_DataObject $object, $created=null) + { + if (is_null($created)) { + $created = common_sql_now(); + } + return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s', + $actor->getID(), + ActivityUtils::resolveUri(self::getObjectType(), true), + $object->getID(), + common_date_iso8601($created)); + } + + static public function beforeSchemaUpdate() + { + $table = strtolower(get_called_class()); + $schema = Schema::get(); + $schemadef = $schema->getTableDef($table); + + // 2015-10-03 We change the meaning of the 'uri' field and move its + // content to the 'act_uri' for the deleted activity. act_created is + // added too. + if (isset($schemadef['fields']['act_uri'])) { + // We already have the act_uri field, so no need to migrate to it. + return; + } + echo "\nFound old $table table, upgrading it to contain 'act_uri' and 'act_created' field..."; + + $schemadef['fields']['act_uri'] = array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'); + $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'); + unset($schemadef['unique keys']); + $schema->ensureTable($table, $schemadef); + + $deleted = new Deleted_notice(); + $result = $deleted->find(); + if ($result === false) { + print "\nFound no deleted_notice entries, continuing..."; + return true; + } + print "\nFound $result deleted_notice entries, aligning with new database layout: "; + while($deleted->fetch()) { + $orig = clone($deleted); + $deleted->act_uri = $deleted->uri; + // this is a fake URI just to have something to put there to avoid NULL + $deleted->uri = TagURI::mint(strtolower(get_called_class()).':%d:%s:%s:%s', + $deleted->profile_id, + ActivityUtils::resolveUri(self::getObjectType(), true), + 'unknown', + common_date_iso8601($deleted->created)); + $deleted->act_created = $deleted->created; // we don't actually know when the notice was created + $deleted->updateWithKeys($orig, 'id'); + print "."; + } + print "DONE.\n"; + print "Resuming core schema upgrade..."; + } + +} diff --git a/plugins/ActivityModeration/forms/deletenotice.php b/plugins/ActivityModeration/forms/deletenotice.php new file mode 100644 index 0000000000..d06004613a --- /dev/null +++ b/plugins/ActivityModeration/forms/deletenotice.php @@ -0,0 +1,67 @@ +notice = $formOpts['notice']; + } + + function id() + { + return 'form_notice_delete-' . $this->notice->getID(); + } + + function formClass() + { + return 'form_settings'; + } + + function action() + { + return common_local_url('deletenotice', array('notice' => $this->notice->getID())); + } + + function formLegend() + { + $this->out->element('legend', null, _('Delete notice')); + } + + function formData() + { + $this->out->element('p', null, _('Are you sure you want to delete this notice?')); + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit('form_action-no', + // TRANS: Button label on the delete notice form. + _m('BUTTON','No'), + 'submit form_action-primary', + 'no', + // TRANS: Submit button title for 'No' when deleting a notice. + _('Do not delete this notice.')); + $this->out->submit('form_action-yes', + // TRANS: Button label on the delete notice form. + _m('BUTTON','Yes'), + 'submit form_action-secondary', + 'yes', + // TRANS: Submit button title for 'Yes' when deleting a notice. + _('Delete this notice.')); + } +} diff --git a/scripts/upgrade.php b/scripts/upgrade.php index 126ef29036..06a2f74771 100755 --- a/scripts/upgrade.php +++ b/scripts/upgrade.php @@ -88,6 +88,7 @@ function updateSchemaPlugins() { printfnq("Upgrading plugin schema..."); + Event::handle('BeforePluginCheckSchema'); Event::handle('CheckSchema'); printfnq("DONE.\n");