Matthias Mullie has uploaded a new change for review.
https://gerrit.wikimedia.org/r/99668
Change subject: User renaming needs to be handleable by the RenameUser extension
......................................................................
User renaming needs to be handleable by the RenameUser extension
Implemented hook for RenameUser extensions; will query database for data that
needs to be updated & then uses Flow data access methods to update the records.
Added indexes on user columns.
Added some methods to models to set the user name.
Also had to make a couple of changes to RevisionStorage.
* Changed method splitUpdate; it will now accept a column prefix & return only
columns matching that prefix (updateRelated can fetch it's own using
splitUpdate with the appropriate prefix)
* Changed updateRelated: it'll now accept (all, not just related) $changes,
(whereupon it can call splitUpdate itself) & old $row
* PostRevisionStorage::updateRelated is no longer unimplemented: it will be run
if the changed columns are in $allowedUpdateColumns (similar to update())
Change-Id: Ie4aeaa40b216995fbbf4e0f3670a19f0955f77cd
---
M Flow.php
M Hooks.php
M container.php
A db_patches/patch-user_idx.sql
M flow.sql
M includes/Data/RevisionStorage.php
M includes/Model/AbstractRevision.php
M includes/Model/PostRevision.php
M includes/Model/Workflow.php
A includes/RenameUser/RenameUser.php
10 files changed, 365 insertions(+), 25 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow
refs/changes/68/99668/1
diff --git a/Flow.php b/Flow.php
index fa6c37f..bc19cf2 100755
--- a/Flow.php
+++ b/Flow.php
@@ -125,6 +125,7 @@
$wgAutoloadClasses['Flow\Log\Logger'] = $dir . 'includes/Log/Logger.php';
$wgAutoloadClasses['Flow\Log\Formatter'] = $dir . 'includes/Log/Formatter.php';
$wgAutoloadClasses['Flow\Log\PostModerationLogger'] = $dir .
'includes/Log/PostModerationLogger.php';
+$wgAutoloadClasses['Flow\RenameUser\RenameUser'] = $dir .
'includes/RenameUser/RenameUser.php';
// database interaction for singular models
$wgAutoloadClasses['Flow\Data\RevisionStorage'] = $dir .
'includes/Data/RevisionStorage.php';
@@ -161,6 +162,7 @@
$wgHooks['SpecialWatchlistGetNonRevisionTypes'][] =
'FlowHooks::onSpecialWatchlistGetNonRevisionTypes';
$wgHooks['UserGetReservedNames'][] = 'FlowHooks::onUserGetReservedNames';
$wgHooks['ResourceLoaderGetConfigVars'][] =
'FlowHooks::onResourceLoaderGetConfigVars';
+$wgHooks['RenameUserComplete'][] = 'FlowHooks::onRenameUserComplete';
// Extension initialization
$wgExtensionFunctions[] = 'FlowHooks::initFlowExtension';
diff --git a/Hooks.php b/Hooks.php
index 01b8338..eae26cd 100644
--- a/Hooks.php
+++ b/Hooks.php
@@ -46,6 +46,7 @@
$updater->modifyExtensionField( 'flow_revision',
'rev_change_type', "$dir/db_patches/patch-rev_change_type_update.sql" );
$updater->modifyExtensionField( 'recentchanges', 'rc_source',
"$dir/db_patches/patch-rc_source.sql" );
$updater->modifyExtensionField( 'flow_revision',
'rev_change_type', "$dir/db_patches/patch-censor_to_suppress.sql" );
+ $updater->addExtensionIndex( 'flow_workflow',
'flow_workflow_user', "$dir/db_patches/patch-user_idx.sql" );
require_once
__DIR__.'/maintenance/FlowInsertDefaultDefinitions.php';
$updater->addPostDatabaseUpdateMaintenance(
'FlowInsertDefaultDefinitions' );
@@ -257,4 +258,21 @@
return true;
}
+
+ /**
+ * @param int $userId The user id
+ * @param $old string The old username
+ * @param $new string The new username
+ * @return bool
+ */
+ public static function onRenameUserComplete( $userId, $old, $new ) {
+ $renamer = Container::get( 'renameuser' );
+
+ return
+ $renamer->workflow( $userId, $old, $new ) &&
+ $renamer->revisionUser( $userId, $old, $new ) &&
+ $renamer->revisionModeratedByUser( $userId, $old, $new
) &&
+ $renamer->revisionLastEditUser( $userId, $old, $new ) &&
+ $renamer->treeRevision( $userId, $old, $new );
+ }
}
diff --git a/container.php b/container.php
index e2fb479..8494a14 100644
--- a/container.php
+++ b/container.php
@@ -403,4 +403,11 @@
);
} );
+$c['renameuser'] = $c->share( function( $c ) {
+ return new Flow\RenameUser\RenameUser(
+ $c['db.factory'],
+ $c['storage']
+ );
+} );
+
return $c;
diff --git a/db_patches/patch-user_idx.sql b/db_patches/patch-user_idx.sql
new file mode 100644
index 0000000..32c665f
--- /dev/null
+++ b/db_patches/patch-user_idx.sql
@@ -0,0 +1,10 @@
+-- RenameUser will run a couple of queries on WHERE <user_id> = X AND
<user_text> = Y
+-- to find data that needs to be updated when changing a username on
Special:RenameUser
+
+CREATE INDEX /*i*/flow_workflow_user ON /*_*/flow_workflow (workflow_user_id,
workflow_user_text);
+
+CREATE INDEX /*i*/flow_rev_user ON /*_*/flow_revision (rev_user_id,
rev_user_text);
+CREATE INDEX /*i*/flow_rev_nod_user ON /*_*/flow_revision (rev_mod_user_id,
rev_mod_user_text);
+CREATE INDEX /*i*/flow_rev_edit_user ON /*_*/flow_revision (rev_edit_user_id,
rev_edit_user_text);
+
+CREATE INDEX /*i*/flow_tree_orig_user ON /*_*/flow_tree_revision
(tree_orig_user_id, tree_orig_user_text);
diff --git a/flow.sql b/flow.sql
index e54f618..5b4a702 100644
--- a/flow.sql
+++ b/flow.sql
@@ -32,6 +32,7 @@
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/flow_workflow_lookup ON /*_*/flow_workflow (workflow_wiki,
workflow_namespace, workflow_title_text, workflow_definition_id);
+CREATE INDEX /*i*/flow_workflow_user ON /*_*/flow_workflow (workflow_user_id,
workflow_user_text);
CREATE TABLE /*_*/flow_subscription (
subscription_workflow_id int unsigned not null,
@@ -71,6 +72,8 @@
CREATE UNIQUE INDEX /*i*/flow_tree_descendant_id_revisions
ON /*_*/flow_tree_revision ( tree_rev_descendant_id, tree_rev_id );
+
+CREATE INDEX /*i*/flow_tree_orig_user ON /*_*/flow_tree_revision
(tree_orig_user_id, tree_orig_user_text);
-- Header Content
-- Instead of header, should this be more generic 'revisioned scratchpad'
@@ -137,6 +140,10 @@
CREATE UNIQUE INDEX /*i*/flow_revision_unique_parent ON
/*_*/flow_revision (rev_parent_id);
+CREATE INDEX /*i*/flow_rev_user ON /*_*/flow_revision (rev_user_id,
rev_user_text);
+CREATE INDEX /*i*/flow_rev_nod_user ON /*_*/flow_revision (rev_mod_user_id,
rev_mod_user_text);
+CREATE INDEX /*i*/flow_rev_edit_user ON /*_*/flow_revision (rev_edit_user_id,
rev_edit_user_text);
+
-- Closure table implementation of tree storage in sql
-- We may be able to go simpler than this
CREATE TABLE /*_*/flow_tree_node (
diff --git a/includes/Data/RevisionStorage.php
b/includes/Data/RevisionStorage.php
index 008d923..fa46ca9 100644
--- a/includes/Data/RevisionStorage.php
+++ b/includes/Data/RevisionStorage.php
@@ -11,11 +11,13 @@
abstract class RevisionStorage extends DbStorage {
static protected $allowedUpdateColumns = array(
+ 'rev_user_text', // Special:RenameUser
'rev_mod_state',
'rev_mod_user_id',
- 'rev_mod_user_text',
+ 'rev_mod_user_text', // Special:RenameUser
'rev_mod_timestamp',
'rev_mod_reason',
+ 'rev_edit_user_text', // Special:RenameUser
);
protected $externalStores;
@@ -23,8 +25,8 @@
abstract protected function relatedPk();
abstract protected function joinField();
- abstract protected function insertRelated( array $row, array $related );
- abstract protected function updateRelated( array $rev, array $related );
+ abstract protected function insertRelated( array $row );
+ abstract protected function updateRelated( array $changes, array $old );
abstract protected function removeRelated( array $row );
public function __construct( DbFactory $dbFactory, $externalStore ) {
@@ -232,7 +234,7 @@
if ( $this->externalStore && !isset( $row['rev_content_url'] )
) {
$row = $this->insertExternalStore( $row );
}
- list( $rev, $related ) = $this->splitUpdate( $row );
+ $rev = $this->splitUpdate( $row, 'rev' );
// If a content url is available store that in the db
// instead of real content.
if ( isset( $rev['rev_content_url'] ) ) {
@@ -251,7 +253,7 @@
return false;
}
- return $this->insertRelated( $row, $related );
+ return $this->insertRelated( $row );
}
protected function insertExternalStore( array $row ) {
@@ -274,12 +276,12 @@
// for suppressing?
public function update( array $old, array $new ) {
$changeSet = ObjectManager::calcUpdates( $old, $new );
- $extra = array_diff( array_keys( $changeSet ),
self::$allowedUpdateColumns );
+ $extra = array_diff( array_keys( $changeSet ),
static::$allowedUpdateColumns );
if ( $extra ) {
throw new \MWException( 'Update not allowed on: ' .
implode( ', ', $extra ) );
}
- list( $rev, $related ) = $this->splitUpdate( $changeSet );
+ $rev = $this->splitUpdate( $changeSet, 'rev' );
if ( $rev ) {
$dbw = $this->dbFactory->getDB( DB_MASTER );
@@ -293,8 +295,8 @@
return false;
}
}
- // TODO: this probably wont work, it needs $row
- return $this->updateRelated( $rev, $related );
+
+ return $this->updateRelated( $changeSet, $old );
}
@@ -326,24 +328,37 @@
throw new \MWException( 'Not Implemented' );
}
- // Separates $row into two arrays, one with the rev_ prefix
- // and the other with everything else. May need to split more
- // specifically if we want > 2 prefixes.
- protected function splitUpdate( array $row ) {
- $rev = $related = array();
+ /**
+ * Gets all columns from $row that start with a given prefix and omits
other
+ * columns.
+ *
+ * @param array $row Rows to split
+ * @param string[optional] $prefix
+ * @return array Remaining rows
+ */
+ protected function splitUpdate( array $row, $prefix = 'rev' ) {
+ $rev = array();
foreach ( $row as $key => $value ) {
- $prefix = substr( $key, 0, 4 );
- if ( $prefix === 'rev_' ) {
+ $keyPrefix = strstr( $key, '_', true );
+ if ( $keyPrefix === $prefix ) {
$rev[$key] = $value;
- } else {
- $related[$key] = $value;
}
}
- return array( $rev, $related );
+ return $rev;
}
}
class PostRevisionStorage extends RevisionStorage {
+ static protected $allowedUpdateColumns = array(
+ 'tree_orig_user_text', // Special:RenameUser
+ 'rev_user_text', // Special:RenameUser
+ 'rev_mod_state',
+ 'rev_mod_user_id',
+ 'rev_mod_user_text', // Special:RenameUser
+ 'rev_mod_timestamp',
+ 'rev_mod_reason',
+ 'rev_edit_user_text', // Special:RenameUser
+ );
public function __construct( DbFactory $dbFactory, $externalStore,
TreeRepository $treeRepo ) {
parent::__construct( $dbFactory, $externalStore );
@@ -362,7 +377,9 @@
return 'tree_rev_id';
}
- protected function insertRelated( array $row, array $tree ) {
+ protected function insertRelated( array $row ) {
+ $tree = $this->splitUpdate( $row, 'tree' );
+
$dbw = $this->dbFactory->getDB( DB_MASTER );
$res = $dbw->insert(
$this->joinTable(),
@@ -389,10 +406,32 @@
// Topic split will primarily be done through the TreeRepository
directly, but
// we will need to accept updates to the denormalized tree_parent_id
field for
// the new root post
- protected function updateRelated( array $row, array $treeChanges ) {
- if ( $treeChanges ) {
- throw new \MWException( 'Update not allowed' );
+ protected function updateRelated( array $changes, array $old ) {
+ $treeChanges = $this->splitUpdate( $changes, 'tree' );
+
+ // no changes to be performed
+ if ( !$treeChanges ) {
+ return $changes;
}
+
+ $extra = array_diff( array_keys( $treeChanges ),
static::$allowedUpdateColumns );
+ if ( $extra ) {
+ throw new \MWException( 'Update not allowed on: ' .
implode( ', ', $extra ) );
+ }
+
+ $dbw = $this->dbFactory->getDB( DB_MASTER );
+ $res = $dbw->update(
+ $this->joinTable(),
+ $this->preprocessSqlArray( $treeChanges ),
+ array( 'tree_rev_id' => $old['tree_rev_id'] ),
+ __METHOD__
+ );
+
+ if ( !$res ) {
+ return false;
+ }
+
+ return $changes;
}
// this doesnt delete the whole post, it just deletes the revision.
@@ -420,7 +459,8 @@
return 'header_rev_id';
}
- protected function insertRelated( array $row, array $header ) {
+ protected function insertRelated( array $row ) {
+ $header = $this->splitUpdate( $row, 'header' );
$res = $this->dbFactory->getDB( DB_MASTER )->insert(
$this->joinTable(),
$this->preprocessSqlArray( $header ),
@@ -433,7 +473,8 @@
}
// There is changable data in the header half, it just points to the
correct workflow
- protected function updateRelated( array $rev, array $headerChanges ) {
+ protected function updateRelated( array $changes, array $old ) {
+ $headerChanges = $this->splitUpdate( $changes, 'header' );
if ( $headerChanges ) {
throw new \MWException( 'No update allowed' );
}
diff --git a/includes/Model/AbstractRevision.php
b/includes/Model/AbstractRevision.php
index c721484..2e54826 100644
--- a/includes/Model/AbstractRevision.php
+++ b/includes/Model/AbstractRevision.php
@@ -485,4 +485,25 @@
public function getModeratedByUserId() {
return $this->moderatedByUserId;
}
+
+ /**
+ * @param string $name User name
+ */
+ public function setUserText( $name ) {
+ $this->userText = $name;
+ }
+
+ /**
+ * @param string $name User name
+ */
+ public function setModeratedByUserText( $name ) {
+ $this->moderatedByUserText = $name;
+ }
+
+ /**
+ * @param string $name User name
+ */
+ public function setLastEditUserText( $name ) {
+ $this->lastEditUserText = $name;
+ }
}
diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php
index 697ba8c..d76ea3c 100644
--- a/includes/Model/PostRevision.php
+++ b/includes/Model/PostRevision.php
@@ -405,4 +405,11 @@
}
return $user->getId() == $this->getCreatorId();
}
+
+ /**
+ * @param string $name User name
+ */
+ public function setOrigUserText( $name ) {
+ $this->origUserText = $name;
+ }
}
diff --git a/includes/Model/Workflow.php b/includes/Model/Workflow.php
index 20e71aa..28286a2 100644
--- a/includes/Model/Workflow.php
+++ b/includes/Model/Workflow.php
@@ -153,5 +153,12 @@
$state = end( $this->lockState );
return $state['state'] === self::LOCKED;
}
+
+ /**
+ * @param string $name User name
+ */
+ public function setUserText( $name ) {
+ $this->userText = $name;
+ }
}
diff --git a/includes/RenameUser/RenameUser.php
b/includes/RenameUser/RenameUser.php
new file mode 100644
index 0000000..6019579
--- /dev/null
+++ b/includes/RenameUser/RenameUser.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Flow\RenameUser;
+
+use Flow\Data\ManagerGroup;
+use Flow\DbFactory;
+use Flow\Model\UUID;
+
+class RenameUser {
+ /**
+ * @var DbFactory
+ */
+ protected $dbFactory;
+
+ /**
+ * @var ManagerGroup
+ */
+ protected $storage;
+
+ /**
+ * Class name map, used to resolve revisions to the correct objects
using
+ * the value of rev_type.
+ *
+ * @var array
+ */
+ protected $revisionClassMap = array(
+ // rev_type => class name
+ 'post' => 'PostRevision',
+ 'header' => 'Header',
+ );
+
+ /**
+ * @param DbFactory $dbFactory
+ * @param ManagerGroup $storage
+ */
+ public function __construct( DbFactory $dbFactory, ManagerGroup
$storage ) {
+ $this->dbFactory = $dbFactory;
+ $this->storage = $storage;
+ }
+
+ /**
+ * @param int $userId User id
+ * @param string $old Old user name
+ * @param string $new New user name
+ * @return bool
+ */
+ public function workflow( $userId, $old, $new ) {
+ $storage = $this->storage->getStorage( 'Workflow' );
+ $db = $this->dbFactory->getDB( DB_SLAVE );
+
+ $workflows = $db->select(
+ 'flow_workflow',
+ 'workflow_id',
+ array(
+ 'workflow_user_id' => $userId,
+ 'workflow_user_text' => $old,
+ 'workflow_wiki' => wfWikiID()
+ )
+ );
+
+ foreach ( $workflows as $workflow ) {
+ $workflow = $storage->get( UUID::create(
$workflow->workflow_id ) );
+ if ( !$workflow ) {
+ return false;
+ }
+
+ $workflow->setUserText( $new );
+ $storage->put( $workflow );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $userId User id
+ * @param string $old Old user name
+ * @param string $new New user name
+ * @return bool
+ */
+ function revisionUser( $userId, $old, $new ) {
+ $db = $this->dbFactory->getDB( DB_SLAVE );
+
+ $revisions = $db->select(
+ 'flow_revision',
+ array(
+ 'rev_id',
+ 'rev_type'
+ ),
+ array(
+ 'rev_user_id' => $userId,
+ 'rev_user_text' => $old
+// 'workflow_wiki' => wfWikiID() // @todo: how to
check for wiki here?
+ )
+ );
+
+ foreach ( $revisions as $revision ) {
+ $className =
$this->revisionClassMap[$revision->rev_type];
+ $storage = $this->storage->getStorage( $className );
+
+ $revision = $storage->get( UUID::create(
$revision->rev_id ) );
+ if ( !$revision ) {
+ return false;
+ }
+
+ $revision->setUserText( $new );
+ $storage->put( $revision );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $userId User id
+ * @param string $old Old user name
+ * @param string $new New user name
+ * @return bool
+ */
+ function revisionModeratedByUser( $userId, $old, $new ) {
+ $db = $this->dbFactory->getDB( DB_SLAVE );
+
+ $revisions = $db->select(
+ 'flow_revision',
+ array(
+ 'rev_id',
+ 'rev_type'
+ ),
+ array(
+ 'rev_mod_user_id' => $userId,
+ 'rev_mod_user_text' => $old
+// 'workflow_wiki' => wfWikiID() // @todo: how to
check for wiki here?
+ )
+ );
+
+ foreach ( $revisions as $revision ) {
+ $className =
$this->revisionClassMap[$revision->rev_type];
+ $storage = $this->storage->getStorage( $className );
+
+ $revision = $storage->get( UUID::create(
$revision->rev_id ) );
+ if ( !$revision ) {
+ return false;
+ }
+
+ $revision->setModeratedByUserText( $new );
+ $storage->put( $revision );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $userId User id
+ * @param string $old Old user name
+ * @param string $new New user name
+ * @return bool
+ */
+ function revisionLastEditUser( $userId, $old, $new ) {
+ $db = $this->dbFactory->getDB( DB_SLAVE );
+
+ $revisions = $db->select(
+ 'flow_revision',
+ array(
+ 'rev_id',
+ 'rev_type'
+ ),
+ array(
+ 'rev_edit_user_id' => $userId,
+ 'rev_edit_user_text' => $old
+// 'workflow_wiki' => wfWikiID() // @todo: how to
check for wiki here?
+ )
+ );
+
+ foreach ( $revisions as $revision ) {
+ $className =
$this->revisionClassMap[$revision->rev_type];
+ $storage = $this->storage->getStorage( $className );
+
+ $revision = $storage->get( UUID::create(
$revision->rev_id ) );
+ if ( !$revision ) {
+ return false;
+ }
+
+ $revision->setLastEditUserText( $new );
+ $storage->put( $revision );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param int $userId User id
+ * @param string $old Old user name
+ * @param string $new New user name
+ * @return bool
+ */
+ public function treeRevision( $userId, $old, $new ) {
+ $storage = $this->storage->getStorage( 'PostRevision' );
+ $db = $this->dbFactory->getDB( DB_SLAVE );
+
+ $revisions = $db->select(
+ 'flow_tree_revision',
+ 'tree_rev_id',
+ array(
+ 'tree_orig_user_id' => $userId,
+ 'tree_orig_user_text' => $old
+// 'workflow_wiki' => wfWikiID() // @todo: how to
check for wiki here?
+ )
+ );
+
+ foreach ( $revisions as $revision ) {
+ $revision = $storage->get( UUID::create(
$revision->tree_rev_id ) );
+ if ( !$revision ) {
+ return false;
+ }
+
+ $revision->setOrigUserText( $new );
+ $storage->put( $revision );
+ }
+
+ return true;
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/99668
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ie4aeaa40b216995fbbf4e0f3670a19f0955f77cd
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits