summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/deleteuser.php10
-rw-r--r--classes/Profile.php3
-rw-r--r--classes/Profile_role.php1
-rw-r--r--lib/deluserqueuehandler.php95
-rw-r--r--lib/queuemanager.php3
-rw-r--r--lib/userprofile.php11
6 files changed, 122 insertions, 1 deletions
diff --git a/actions/deleteuser.php b/actions/deleteuser.php
index c4f84fad2..4e6b27395 100644
--- a/actions/deleteuser.php
+++ b/actions/deleteuser.php
@@ -162,7 +162,15 @@ class DeleteuserAction extends ProfileFormAction
function handlePost()
{
if (Event::handle('StartDeleteUser', array($this, $this->user))) {
- $this->user->delete();
+ // Mark the account as deleted and shove low-level deletion tasks
+ // to background queues. Removing a lot of posts can take a while...
+ if (!$this->user->hasRole(Profile_role::DELETED)) {
+ $this->user->grantRole(Profile_role::DELETED);
+ }
+
+ $qm = QueueManager::get();
+ $qm->enqueue($this->user, 'deluser');
+
Event::handle('EndDeleteUser', array($this, $this->user));
}
}
diff --git a/classes/Profile.php b/classes/Profile.php
index 91f6e4692..eded1ff71 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -732,6 +732,9 @@ class Profile extends Memcached_DataObject
function hasRight($right)
{
$result = false;
+ if ($this->hasRole(Profile_role::DELETED)) {
+ return false;
+ }
if (Event::handle('UserRightsCheck', array($this, $right, &$result))) {
switch ($right)
{
diff --git a/classes/Profile_role.php b/classes/Profile_role.php
index d0a0b31f0..e7aa1f0f0 100644
--- a/classes/Profile_role.php
+++ b/classes/Profile_role.php
@@ -53,6 +53,7 @@ class Profile_role extends Memcached_DataObject
const ADMINISTRATOR = 'administrator';
const SANDBOXED = 'sandboxed';
const SILENCED = 'silenced';
+ const DELETED = 'deleted'; // Pending final deletion of notices...
public static function isValid($role)
{
diff --git a/lib/deluserqueuehandler.php b/lib/deluserqueuehandler.php
new file mode 100644
index 000000000..4a1233a5e
--- /dev/null
+++ b/lib/deluserqueuehandler.php
@@ -0,0 +1,95 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Background job to delete prolific users without disrupting front-end too much.
+ *
+ * Up to 50 messages are deleted on each run through; when all messages are gone,
+ * the actual account is deleted.
+ *
+ * @package QueueHandler
+ * @maintainer Brion Vibber <brion@status.net>
+ */
+
+class DelUserQueueHandler extends QueueHandler
+{
+ const DELETION_WINDOW = 50;
+
+ public function transport()
+ {
+ return 'deluser';
+ }
+
+ public function handle($user)
+ {
+ if (!($user instanceof User)) {
+ common_log(LOG_ERR, "Got a bogus user, not deleting");
+ return true;
+ }
+
+ $user = User::staticGet('id', $user->id);
+ if (!$user) {
+ common_log(LOG_INFO, "User {$user->nickname} was deleted before we got here.");
+ return true;
+ }
+
+ if (!$user->hasRole(Profile_role::DELETED)) {
+ common_log(LOG_INFO, "User {$user->nickname} is not pending deletion; aborting.");
+ return true;
+ }
+
+ $notice = $this->getNextBatch($user);
+ if ($notice->N) {
+ common_log(LOG_INFO, "Deleting next {$notice->N} notices by {$user->nickname}");
+ while ($notice->fetch()) {
+ $del = clone($notice);
+ $del->delete();
+ }
+
+ // @todo improve reliability in case we died during the above deletions
+ // with a fatal error. If the job is lost, we should perform some kind
+ // of garbage collection later.
+
+ // Queue up the next batch.
+ $qm = QueueManager::get();
+ $qm->enqueue($user, 'deluser');
+ } else {
+ // Out of notices? Let's finish deleting this guy!
+ $user->delete();
+ common_log(LOG_INFO, "User $user->id $user->nickname deleted.");
+ return true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Fetch the next self::DELETION_WINDOW messages for this user.
+ * @return Notice
+ */
+ protected function getNextBatch(User $user)
+ {
+ $notice = new Notice();
+ $notice->profile_id = $user->id;
+ $notice->limit(self::DELETION_WINDOW);
+ $notice->find();
+ return $notice;
+ }
+
+}
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
index 87bd356aa..0829c8a8b 100644
--- a/lib/queuemanager.php
+++ b/lib/queuemanager.php
@@ -264,6 +264,9 @@ abstract class QueueManager extends IoManager
$this->connect('sms', 'SmsQueueHandler');
}
+ // Background user management tasks...
+ $this->connect('deluser', 'DelUserQueueHandler');
+
// Broadcasting profile updates to OMB remote subscribers
$this->connect('profile', 'ProfileQueueHandler');
diff --git a/lib/userprofile.php b/lib/userprofile.php
index 8464c2446..2c3b1ea45 100644
--- a/lib/userprofile.php
+++ b/lib/userprofile.php
@@ -228,6 +228,17 @@ class UserProfile extends Widget
function showEntityActions()
{
+ if ($this->profile->hasRole(Profile_role::DELETED)) {
+ $this->out->elementStart('div', 'entity_actions');
+ $this->out->element('h2', null, _('User actions'));
+ $this->out->elementStart('ul');
+ $this->out->elementStart('p', array('class' => 'profile_deleted'));
+ $this->out->text(_('User deletion in progress...'));
+ $this->out->elementEnd('p');
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
+ return;
+ }
if (Event::handle('StartProfilePageActionsSection', array(&$this->out, $this->profile))) {
$cur = common_current_user();