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/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/lib/deletenoticeform.php b/plugins/ActivityModeration/forms/deletenotice.php similarity index 100% rename from lib/deletenoticeform.php rename to plugins/ActivityModeration/forms/deletenotice.php 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");