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

Reply via email to