reassignEdits.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /**
  3. * Reassign edits from a user or IP address to another user
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Maintenance
  22. * @author Rob Church <robchur@gmail.com>
  23. * @license GPL-2.0-or-later
  24. */
  25. use Wikimedia\Rdbms\IDatabase;
  26. require_once __DIR__ . '/Maintenance.php';
  27. /**
  28. * Maintenance script that reassigns edits from a user or IP address
  29. * to another user.
  30. *
  31. * @ingroup Maintenance
  32. */
  33. class ReassignEdits extends Maintenance {
  34. public function __construct() {
  35. parent::__construct();
  36. $this->addDescription( 'Reassign edits from one user to another' );
  37. $this->addOption( "force", "Reassign even if the target user doesn't exist" );
  38. $this->addOption( "norc", "Don't update the recent changes table" );
  39. $this->addOption( "report", "Print out details of what would be changed, but don't update it" );
  40. $this->addArg( 'from', 'Old user to take edits from' );
  41. $this->addArg( 'to', 'New user to give edits to' );
  42. }
  43. public function execute() {
  44. if ( $this->hasArg( 0 ) && $this->hasArg( 1 ) ) {
  45. # Set up the users involved
  46. $from = $this->initialiseUser( $this->getArg( 0 ) );
  47. $to = $this->initialiseUser( $this->getArg( 1 ) );
  48. # If the target doesn't exist, and --force is not set, stop here
  49. if ( $to->getId() || $this->hasOption( 'force' ) ) {
  50. # Reassign the edits
  51. $report = $this->hasOption( 'report' );
  52. $this->doReassignEdits( $from, $to, !$this->hasOption( 'norc' ), $report );
  53. # If reporting, and there were items, advise the user to run without --report
  54. if ( $report ) {
  55. $this->output( "Run the script again without --report to update.\n" );
  56. }
  57. } else {
  58. $ton = $to->getName();
  59. $this->error( "User '{$ton}' not found." );
  60. }
  61. }
  62. }
  63. /**
  64. * Reassign edits from one user to another
  65. *
  66. * @param User $from User to take edits from
  67. * @param User $to User to assign edits to
  68. * @param bool $rc Update the recent changes table
  69. * @param bool $report Don't change things; just echo numbers
  70. * @return int Number of entries changed, or that would be changed
  71. */
  72. private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
  73. global $wgActorTableSchemaMigrationStage;
  74. $dbw = $this->getDB( DB_MASTER );
  75. $this->beginTransaction( $dbw, __METHOD__ );
  76. # Count things
  77. $this->output( "Checking current edits..." );
  78. $revQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'rev_user', $from );
  79. $res = $dbw->select(
  80. [ 'revision' ] + $revQueryInfo['tables'],
  81. 'COUNT(*) AS count',
  82. $revQueryInfo['conds'],
  83. __METHOD__,
  84. [],
  85. $revQueryInfo['joins']
  86. );
  87. $row = $dbw->fetchObject( $res );
  88. $cur = $row->count;
  89. $this->output( "found {$cur}.\n" );
  90. $this->output( "Checking deleted edits..." );
  91. $arQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'ar_user', $from, false );
  92. $res = $dbw->select(
  93. [ 'archive' ] + $arQueryInfo['tables'],
  94. 'COUNT(*) AS count',
  95. $arQueryInfo['conds'],
  96. __METHOD__,
  97. [],
  98. $arQueryInfo['joins']
  99. );
  100. $row = $dbw->fetchObject( $res );
  101. $del = $row->count;
  102. $this->output( "found {$del}.\n" );
  103. # Don't count recent changes if we're not supposed to
  104. if ( $rc ) {
  105. $this->output( "Checking recent changes..." );
  106. $rcQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'rc_user', $from, false );
  107. $res = $dbw->select(
  108. [ 'recentchanges' ] + $rcQueryInfo['tables'],
  109. 'COUNT(*) AS count',
  110. $rcQueryInfo['conds'],
  111. __METHOD__,
  112. [],
  113. $rcQueryInfo['joins']
  114. );
  115. $row = $dbw->fetchObject( $res );
  116. $rec = $row->count;
  117. $this->output( "found {$rec}.\n" );
  118. } else {
  119. $rec = 0;
  120. }
  121. $total = $cur + $del + $rec;
  122. $this->output( "\nTotal entries to change: {$total}\n" );
  123. if ( !$report ) {
  124. if ( $total ) {
  125. # Reassign edits
  126. $this->output( "\nReassigning current edits..." );
  127. if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
  128. $dbw->update(
  129. 'revision',
  130. [
  131. 'rev_user' => $to->getId(),
  132. 'rev_user_text' =>
  133. $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $to->getName() : ''
  134. ],
  135. $from->isLoggedIn()
  136. ? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
  137. __METHOD__
  138. );
  139. }
  140. if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
  141. $dbw->update(
  142. 'revision_actor_temp',
  143. [ 'revactor_actor' => $to->getActorId( $dbw ) ],
  144. [ 'revactor_actor' => $from->getActorId() ],
  145. __METHOD__
  146. );
  147. }
  148. $this->output( "done.\nReassigning deleted edits..." );
  149. $dbw->update( 'archive',
  150. $this->userSpecification( $dbw, $to, 'ar_user', 'ar_user_text', 'ar_actor' ),
  151. [ $arQueryInfo['conds'] ], __METHOD__ );
  152. $this->output( "done.\n" );
  153. # Update recent changes if required
  154. if ( $rc ) {
  155. $this->output( "Updating recent changes..." );
  156. $dbw->update( 'recentchanges',
  157. $this->userSpecification( $dbw, $to, 'rc_user', 'rc_user_text', 'rc_actor' ),
  158. [ $rcQueryInfo['conds'] ], __METHOD__ );
  159. $this->output( "done.\n" );
  160. }
  161. }
  162. }
  163. $this->commitTransaction( $dbw, __METHOD__ );
  164. return (int)$total;
  165. }
  166. /**
  167. * Return user specifications
  168. * i.e. user => id, user_text => text
  169. *
  170. * @param IDatabase $dbw Database handle
  171. * @param User $user User for the spec
  172. * @param string $idfield Field name containing the identifier
  173. * @param string $utfield Field name containing the user text
  174. * @param string $acfield Field name containing the actor ID
  175. * @return array
  176. */
  177. private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
  178. global $wgActorTableSchemaMigrationStage;
  179. $ret = [];
  180. if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
  181. $ret += [
  182. $idfield => $user->getId(),
  183. $utfield => $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $user->getName() : '',
  184. ];
  185. }
  186. if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
  187. $ret += [ $acfield => $user->getActorId( $dbw ) ];
  188. }
  189. return $ret;
  190. }
  191. /**
  192. * Initialise the user object
  193. *
  194. * @param string $username Username or IP address
  195. * @return User
  196. */
  197. private function initialiseUser( $username ) {
  198. if ( User::isIP( $username ) ) {
  199. $user = new User();
  200. $user->setId( 0 );
  201. $user->setName( $username );
  202. } else {
  203. $user = User::newFromName( $username );
  204. if ( !$user ) {
  205. $this->fatalError( "Invalid username" );
  206. }
  207. }
  208. $user->load();
  209. return $user;
  210. }
  211. }
  212. $maintClass = ReassignEdits::class;
  213. require_once RUN_MAINTENANCE_IF_MAIN;