diff options
Diffstat (limited to 'UserMerge/includes/MergeUser.php')
-rw-r--r-- | UserMerge/includes/MergeUser.php | 215 |
1 files changed, 116 insertions, 99 deletions
diff --git a/UserMerge/includes/MergeUser.php b/UserMerge/includes/MergeUser.php index 6a959c2f..07e3b7a4 100644 --- a/UserMerge/includes/MergeUser.php +++ b/UserMerge/includes/MergeUser.php @@ -1,10 +1,11 @@ <?php + +use MediaWiki\Block\DatabaseBlock; use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\IDatabase; /** * Contains the actual database backend logic for merging users - * */ class MergeUser { /** @@ -103,19 +104,21 @@ class MergeUser { /** * @param IDatabase $dbw * @return void - * @suppress PhanTypeMismatchArgument Phan thinks that $newBlock and $oldBlock are both null when - * Block::newFromRow is called, although the previous if/elseif returns if any of them is null. */ private function mergeBlocks( IDatabase $dbw ) { $dbw->startAtomic( __METHOD__ ); // Pull blocks directly from master + $qi = DatabaseBlock::getQueryInfo(); $rows = $dbw->select( - 'ipblocks', - '*', + $qi['tables'], + array_merge( $qi['fields'], [ 'ipb_user' ] ), [ 'ipb_user' => [ $this->oldUser->getId(), $this->newUser->getId() ], - ] + ], + __METHOD__, + [], + $qi['joins'] ); $newBlock = null; @@ -128,15 +131,13 @@ class MergeUser { } } - if ( !$newBlock && !$oldBlock ) { - // No one is blocked, yaaay - $dbw->endAtomic( __METHOD__ ); - return; - } elseif ( $newBlock && !$oldBlock ) { + if ( !$oldBlock ) { + // No one is blocked or // Only the new user is blocked, so nothing to do. $dbw->endAtomic( __METHOD__ ); return; - } elseif ( $oldBlock && !$newBlock ) { + } + if ( !$newBlock ) { // Just move the old block to the new username $dbw->update( 'ipblocks', @@ -150,8 +151,8 @@ class MergeUser { // Okay, let's pick the "strongest" block, and re-apply it to // the new user. - $oldBlockObj = Block::newFromRow( $oldBlock ); - $newBlockObj = Block::newFromRow( $newBlock ); + $oldBlockObj = DatabaseBlock::newFromRow( $oldBlock ); + $newBlockObj = DatabaseBlock::newFromRow( $newBlock ); $winner = $this->chooseBlock( $oldBlockObj, $newBlockObj ); if ( $winner->getId() === $newBlockObj->getId() ) { @@ -170,11 +171,11 @@ class MergeUser { } /** - * @param Block $b1 - * @param Block $b2 - * @return Block + * @param DatabaseBlock $b1 + * @param DatabaseBlock $b2 + * @return DatabaseBlock */ - private function chooseBlock( Block $b1, Block $b2 ) { + private function chooseBlock( DatabaseBlock $b1, DatabaseBlock $b2 ) { // First, see if one is longer than the other. if ( $b1->getExpiry() !== $b2->getExpiry() ) { // This works for infinite blocks because: @@ -187,12 +188,21 @@ class MergeUser { } // Next check what they block, in order + $blockProps = []; + foreach ( [ $b1, $b2 ] as $block ) { + $blockProps[] = [ + 'block' => $block, + 'createaccount' => $block->isCreateAccountBlocked(), + 'sendemail' => $block->isEmailBlocked(), + 'editownusertalk' => !$block->isUsertalkEditAllowed(), + ]; + } foreach ( [ 'createaccount', 'sendemail', 'editownusertalk' ] as $action ) { - if ( $b1->prevents( $action ) xor $b2->prevents( $action ) ) { - if ( $b1->prevents( $action ) ) { - return $b1; + if ( $blockProps[0][$action] xor $blockProps[1][$action] ) { + if ( $blockProps[0][$action] ) { + return $blockProps[0]['block']; } else { - return $b2; + return $blockProps[1]['block']; } } } @@ -210,7 +220,7 @@ class MergeUser { } if ( defined( 'ActorMigration::MIGRATION_STAGE_SCHEMA_COMPAT' ) ) { - return (bool)( $stage & SCHEMA_COMPAT_WRITE_OLD ); + return (bool)( (int)$stage & SCHEMA_COMPAT_WRITE_OLD ); } else { return $stage < MIGRATION_NEW; } @@ -240,43 +250,38 @@ class MergeUser { * @param string $fnameTrxOwner */ private function mergeDatabaseTables( $fnameTrxOwner ) { - global $wgActorTableSchemaMigrationStage; - // Fields to update with the format: // [ // tableName, idField, textField, // 'batchKey' => unique field, 'options' => array(), 'db' => IDatabase // 'actorId' => actor ID field, + // 'actorStage' => actor schema migration stage // ]; // textField, batchKey, db, and options are optional $updateFields = [ [ 'archive', 'ar_user', 'ar_user_text', 'batchKey' => 'ar_id', 'actorId' => 'ar_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'revision', 'rev_user', 'rev_user_text', 'batchKey' => 'rev_id', 'actorId' => '', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'filearchive', 'fa_user', 'fa_user_text', 'batchKey' => 'fa_id', 'actorId' => 'fa_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'image', 'img_user', 'img_user_text', 'batchKey' => 'img_name', 'actorId' => 'img_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'oldimage', 'oi_user', 'oi_user_text', 'batchKey' => 'oi_archive_name', - 'actorId' => 'oi_actor', 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorId' => 'oi_actor', 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'recentchanges', 'rc_user', 'rc_user_text', 'batchKey' => 'rc_id', 'actorId' => 'rc_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'logging', 'log_user', 'log_user_text', 'batchKey' => 'log_id', 'actorId' => 'log_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'ipblocks', 'ipb_by', 'ipb_by_text', 'batchKey' => 'ipb_id', 'actorId' => 'ipb_by_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage ], + 'actorStage' => SCHEMA_COMPAT_NEW ], [ 'watchlist', 'wl_user', 'batchKey' => 'wl_title' ], [ 'user_groups', 'ug_user', 'options' => [ 'IGNORE' ] ], [ 'user_properties', 'up_user', 'options' => [ 'IGNORE' ] ], [ 'user_former_groups', 'ufg_user', 'options' => [ 'IGNORE' ] ], + [ 'revision_actor_temp', 'batchKey' => 'revactor_rev', 'actorId' => 'revactor_actor', + 'actorStage' => SCHEMA_COMPAT_NEW ], ]; - if ( $this->stageNeedsActor( $wgActorTableSchemaMigrationStage ) ) { - $updateFields[] = [ - 'revision_actor_temp', 'batchKey' => 'revactor_rev', 'actorId' => 'revactor_actor', - 'actorStage' => $wgActorTableSchemaMigrationStage - ]; - } Hooks::run( 'UserMergeAccountFields', [ &$updateFields ] ); @@ -298,16 +303,18 @@ class MergeUser { continue; } - $options = isset( $fieldInfo['options'] ) ? $fieldInfo['options'] : []; + $options = $fieldInfo['options'] ?? []; unset( $fieldInfo['options'] ); - $db = isset( $fieldInfo['db'] ) ? $fieldInfo['db'] : $dbw; + $db = $fieldInfo['db'] ?? $dbw; unset( $fieldInfo['db'] ); $tableName = array_shift( $fieldInfo ); $idField = array_shift( $fieldInfo ); - $keyField = isset( $fieldInfo['batchKey'] ) ? $fieldInfo['batchKey'] : null; + $keyField = $fieldInfo['batchKey'] ?? null; unset( $fieldInfo['batchKey'] ); - if ( isset( $fieldInfo['actorId'] ) && !$this->stageNeedsUser( $fieldInfo['actorStage'] ) ) { + if ( isset( $fieldInfo['actorId'] ) && isset( $fieldInfo['actorStage'] ) && + !$this->stageNeedsUser( $fieldInfo['actorStage'] ) + ) { continue; } unset( $fieldInfo['actorId'], $fieldInfo['actorStage'] ); @@ -357,22 +364,22 @@ class MergeUser { } } - if ( $this->stageNeedsActor( $wgActorTableSchemaMigrationStage ) && - $this->oldUser->getActorId() - ) { + if ( $this->oldUser->getActorId() ) { $oldActorId = $this->oldUser->getActorId(); - $newActorId = $this->newUser->getActorId( $db ); + $newActorId = $this->newUser->getActorId( $dbw ); foreach ( $updateFields as $fieldInfo ) { - if ( empty( $fieldInfo['actorId'] ) || !$this->stageNeedsActor( $fieldInfo['actorStage'] ) ) { + if ( empty( $fieldInfo['actorId'] ) || empty( $fieldInfo['actorStage'] ) || + !$this->stageNeedsActor( $fieldInfo['actorStage'] ) + ) { continue; } - $options = isset( $fieldInfo['options'] ) ? $fieldInfo['options'] : []; - $db = isset( $fieldInfo['db'] ) ? $fieldInfo['db'] : $dbw; + $options = $fieldInfo['options'] ?? []; + $db = $fieldInfo['db'] ?? $dbw; $tableName = array_shift( $fieldInfo ); $idField = $fieldInfo['actorId']; - $keyField = isset( $fieldInfo['batchKey'] ) ? $fieldInfo['batchKey'] : null; + $keyField = $fieldInfo['batchKey'] ?? null; if ( $db->trxLevel() || $keyField === null ) { // Can't batch/wait when in a transaction or when no batch key is given @@ -418,7 +425,9 @@ class MergeUser { } } - $dbw->delete( 'user_newtalk', [ 'user_id' => $this->oldUser->getId() ] ); + $dbw->delete( 'user_newtalk', [ 'user_id' => $this->oldUser->getId() ], __METHOD__ ); + $this->oldUser->clearInstanceCache(); + $this->newUser->clearInstanceCache(); Hooks::run( 'MergeAccountFromTo', [ &$this->oldUser, &$this->newUser ] ); } @@ -496,18 +505,17 @@ class MergeUser { * Deletes the old user page when the target user page exists * * @todo This code is a duplicate of Renameuser and GlobalRename - * @suppress PhanParamTooMany Several calls to $message, which is a variadic closure * * @param User $performer * @param callable $msg Function that returns a Message object * @return array Array of old name (string) => new name (Title) where the move failed */ private function movePages( User $performer, /* callable */ $msg ) { - global $wgContLang, $wgUser; + $contLang = MediaWikiServices::getInstance()->getContentLanguage(); $oldusername = trim( str_replace( '_', ' ', $this->oldUser->getName() ) ); $oldusername = Title::makeTitle( NS_USER, $oldusername ); - $newusername = Title::makeTitleSafe( NS_USER, $wgContLang->ucfirst( $this->newUser->getName() ) ); + $newusername = Title::makeTitleSafe( NS_USER, $contLang->ucfirst( $this->newUser->getName() ) ); # select all user pages and sub-pages $dbr = wfGetDB( DB_REPLICA ); @@ -518,17 +526,14 @@ class MergeUser { 'page_namespace' => [ NS_USER, NS_USER_TALK ], 'page_title' . $dbr->buildLike( $oldusername->getDBkey() . '/', $dbr->anyString() ) . ' OR page_title = ' . $dbr->addQuotes( $oldusername->getDBkey() ), - ] + ], + __METHOD__ ); $message = function ( /* ... */ ) use ( $msg ) { return call_user_func_array( $msg, func_get_args() ); }; - // Need to set $wgUser to attribute log properly. - $oldUser = $wgUser; - $wgUser = $performer; - $failedMoves = []; foreach ( $pages as $row ) { $oldPage = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); @@ -537,46 +542,36 @@ class MergeUser { if ( $this->newUser->getName() === 'Anonymous' ) { # delete ALL old pages if ( $oldPage->exists() ) { - $error = ''; - $oldPageArticle = new Article( $oldPage, 0 ); - $oldPageArticle->doDeleteArticle( - $message( 'usermerge-autopagedelete' )->inContentLanguage()->text(), - false, null, null, $error, true - ); + $this->deletePage( $message, $performer, $oldPage ); } } elseif ( $newPage->exists() - && !$oldPage->isValidMoveTarget( $newPage ) + && !MediaWikiServices::getInstance() + ->getMovePageFactory() + ->newMovePage( $oldPage, $newPage ) + ->isValidMove() + ->isOk() && $newPage->getLength() > 0 ) { # delete old pages that can't be moved - $error = ''; - $oldPageArticle = new Article( $oldPage, 0 ); - $oldPageArticle->doDeleteArticle( - $message( 'usermerge-autopagedelete' )->inContentLanguage()->text(), - false, null, null, $error, true - ); - + $this->deletePage( $message, $performer, $oldPage ); } else { # move content to new page # delete target page if it exists and is blank if ( $newPage->exists() ) { - $error = ''; - $newPageArticle = new Article( $newPage, 0 ); - $newPageArticle->doDeleteArticle( - $message( 'usermerge-autopagedelete' )->inContentLanguage()->text(), - false, null, null, $error, true - ); + $this->deletePage( $message, $performer, $newPage ); } # move to target location - $errors = $oldPage->moveTo( - $newPage, - false, - $message( - 'usermerge-move-log', - $oldusername->getText(), - $newusername->getText() )->inContentLanguage()->text() - ); - if ( $errors !== true ) { + $status = MediaWikiServices::getInstance() + ->getMovePageFactory() + ->newMovePage( $oldPage, $newPage ) + ->move( + $performer, + $message( + 'usermerge-move-log', + $oldusername->getText(), + $newusername->getText() )->inContentLanguage()->text() + ); + if ( !$status->isOk() ) { $failedMoves[$oldPage->getPrefixedText()] = $newPage; } @@ -588,22 +583,43 @@ class MergeUser { ); if ( !$dbr->numRows( $res ) ) { # nothing links here, so delete unmoved page/redirect - $error = ''; - $oldPageArticle = new Article( $oldPage, 0 ); - $oldPageArticle->doDeleteArticle( - $message( 'usermerge-autopagedelete' )->inContentLanguage()->text(), - false, null, null, $error, true - ); + $this->deletePage( $message, $performer, $oldPage ); } } } - $wgUser = $oldUser; - return $failedMoves; } /** + * Helper to delete pages + * + * @param callable $msg + * @param User $user + * @param Title $title + */ + private function deletePage( $msg, User $user, Title $title ) { + $wikipage = WikiPage::factory( $title ); + $reason = $msg( 'usermerge-autopagedelete' )->inContentLanguage()->text(); + $error = ''; + if ( version_compare( MW_VERSION, '1.35', '<' ) ) { + $wikipage->doDeleteArticle( $reason, false, null, null, $error, $user, true ); + } else { + $wikipage->doDeleteArticleReal( + $reason, + $user, + false, + null, // Unused + $error, + null, // Unused + [], + 'delete', + true + ); + } + } + + /** * Function to delete users following a successful mergeUser call. * * Removes rows from the user, user_groups, user_properties @@ -635,14 +651,15 @@ class MergeUser { foreach ( $tablesToDelete as $table => $field ) { // Check if a different database object was passed (Echo or Flow) if ( is_array( $field ) ) { - $db = isset( $field['db'] ) ? $field['db'] : $dbw; + $db = $field['db'] ?? $dbw; $field = $field[0]; } else { $db = $dbw; } $db->delete( $table, - [ $field => $this->oldUser->getId() ] + [ $field => $this->oldUser->getId() ], + __METHOD__ ); } |