[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/maintenance/ -> userDupes.inc (source)

   1  <?php
   2  /**
   3   * Helper class for update.php.
   4   *
   5   * Copyright © 2005 Brion Vibber <[email protected]>
   6   * https://www.mediawiki.org/
   7   *
   8   * This program is free software; you can redistribute it and/or modify
   9   * it under the terms of the GNU General Public License as published by
  10   * the Free Software Foundation; either version 2 of the License, or
  11   * (at your option) any later version.
  12   *
  13   * This program is distributed in the hope that it will be useful,
  14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16   * GNU General Public License for more details.
  17   *
  18   * You should have received a copy of the GNU General Public License along
  19   * with this program; if not, write to the Free Software Foundation, Inc.,
  20   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21   * http://www.gnu.org/copyleft/gpl.html
  22   *
  23   * @file
  24   * @ingroup Maintenance
  25   */
  26  
  27  /**
  28   * Look for duplicate user table entries and optionally prune them.
  29   *
  30   * This is still used by our MysqlUpdater at:
  31   * includes/installer/MysqlUpdater.php
  32   *
  33   * @ingroup Maintenance
  34   */
  35  class UserDupes {
  36      private $db;
  37      private $reassigned;
  38      private $trimmed;
  39      private $failed;
  40      private $outputCallback;
  41  
  42  	function __construct( &$database, $outputCallback ) {
  43          $this->db = $database;
  44          $this->outputCallback = $outputCallback;
  45      }
  46  
  47      /**
  48       * Output some text via the output callback provided
  49       * @param string $str Text to print
  50       */
  51  	private function out( $str ) {
  52          call_user_func( $this->outputCallback, $str );
  53      }
  54  
  55      /**
  56       * Check if this database's user table has already had a unique
  57       * user_name index applied.
  58       * @return bool
  59       */
  60  	function hasUniqueIndex() {
  61          $info = $this->db->indexInfo( 'user', 'user_name', __METHOD__ );
  62          if ( !$info ) {
  63              $this->out( "WARNING: doesn't seem to have user_name index at all!\n" );
  64  
  65              return false;
  66          }
  67  
  68          # Confusingly, 'Non_unique' is 0 for *unique* indexes,
  69          # and 1 for *non-unique* indexes. Pass the crack, MySQL,
  70          # it's obviously some good stuff!
  71          return ( $info[0]->Non_unique == 0 );
  72      }
  73  
  74      /**
  75       * Checks the database for duplicate user account records
  76       * and remove them in preparation for application of a unique
  77       * index on the user_name field. Returns true if the table is
  78       * clean or if duplicates have been resolved automatically.
  79       *
  80       * May return false if there are unresolvable problems.
  81       * Status information will be echo'd to stdout.
  82       *
  83       * @return bool
  84       */
  85  	function clearDupes() {
  86          return $this->checkDupes( true );
  87      }
  88  
  89      /**
  90       * Checks the database for duplicate user account records
  91       * in preparation for application of a unique index on the
  92       * user_name field. Returns true if the table is clean or
  93       * if duplicates can be resolved automatically.
  94       *
  95       * Returns false if there are duplicates and resolution was
  96       * not requested. (If doing resolution, edits may be reassigned.)
  97       * Status information will be echo'd to stdout.
  98       *
  99       * @param bool $doDelete Pass true to actually remove things
 100       *   from the database; false to just check.
 101       * @return bool
 102       */
 103  	function checkDupes( $doDelete = false ) {
 104          if ( $this->hasUniqueIndex() ) {
 105              echo wfWikiID() . " already has a unique index on its user table.\n";
 106  
 107              return true;
 108          }
 109  
 110          $this->lock();
 111  
 112          $this->out( "Checking for duplicate accounts...\n" );
 113          $dupes = $this->getDupes();
 114          $count = count( $dupes );
 115  
 116          $this->out( "Found $count accounts with duplicate records on " . wfWikiID() . ".\n" );
 117          $this->trimmed = 0;
 118          $this->reassigned = 0;
 119          $this->failed = 0;
 120          foreach ( $dupes as $name ) {
 121              $this->examine( $name, $doDelete );
 122          }
 123  
 124          $this->unlock();
 125  
 126          $this->out( "\n" );
 127  
 128          if ( $this->reassigned > 0 ) {
 129              if ( $doDelete ) {
 130                  $this->out( "$this->reassigned duplicate accounts had edits "
 131                      . "reassigned to a canonical record id.\n" );
 132              } else {
 133                  $this->out( "$this->reassigned duplicate accounts need to have edits reassigned.\n" );
 134              }
 135          }
 136  
 137          if ( $this->trimmed > 0 ) {
 138              if ( $doDelete ) {
 139                  $this->out( "$this->trimmed duplicate user records were deleted from "
 140                      . wfWikiID() . ".\n" );
 141              } else {
 142                  $this->out( "$this->trimmed duplicate user accounts were found on "
 143                      . wfWikiID() . " which can be removed safely.\n" );
 144              }
 145          }
 146  
 147          if ( $this->failed > 0 ) {
 148              $this->out( "Something terribly awry; $this->failed duplicate accounts were not removed.\n" );
 149  
 150              return false;
 151          }
 152  
 153          if ( $this->trimmed == 0 || $doDelete ) {
 154              $this->out( "It is now safe to apply the unique index on user_name.\n" );
 155  
 156              return true;
 157          } else {
 158              $this->out( "Run this script again with the --fix option to automatically delete them.\n" );
 159  
 160              return false;
 161          }
 162      }
 163  
 164      /**
 165       * We don't want anybody to mess with our stuff...
 166       * @access private
 167       */
 168  	function lock() {
 169          $set = array( 'user', 'revision' );
 170          $names = array_map( array( $this, 'lockTable' ), $set );
 171          $tables = implode( ',', $names );
 172  
 173          $this->db->query( "LOCK TABLES $tables", __METHOD__ );
 174      }
 175  
 176  	function lockTable( $table ) {
 177          return $this->db->tableName( $table ) . ' WRITE';
 178      }
 179  
 180      /**
 181       * @access private
 182       */
 183  	function unlock() {
 184          $this->db->query( "UNLOCK TABLES", __METHOD__ );
 185      }
 186  
 187      /**
 188       * Grab usernames for which multiple records are present in the database.
 189       * @return array
 190       * @access private
 191       */
 192  	function getDupes() {
 193          $user = $this->db->tableName( 'user' );
 194          $result = $this->db->query(
 195              "SELECT user_name,COUNT(*) AS n
 196                  FROM $user
 197              GROUP BY user_name
 198                HAVING n > 1", __METHOD__ );
 199  
 200          $list = array();
 201          foreach ( $result as $row ) {
 202              $list[] = $row->user_name;
 203          }
 204  
 205          return $list;
 206      }
 207  
 208      /**
 209       * Examine user records for the given name. Try to see which record
 210       * will be the one that actually gets used, then check remaining records
 211       * for edits. If the dupes have no edits, we can safely remove them.
 212       * @param string $name
 213       * @param bool $doDelete
 214       * @access private
 215       */
 216  	function examine( $name, $doDelete ) {
 217          $result = $this->db->select( 'user',
 218              array( 'user_id' ),
 219              array( 'user_name' => $name ),
 220              __METHOD__ );
 221  
 222          $firstRow = $this->db->fetchObject( $result );
 223          $firstId = $firstRow->user_id;
 224          $this->out( "Record that will be used for '$name' is user_id=$firstId\n" );
 225  
 226          foreach ( $result as $row ) {
 227              $dupeId = $row->user_id;
 228              $this->out( "... dupe id $dupeId: " );
 229              $edits = $this->editCount( $dupeId );
 230              if ( $edits > 0 ) {
 231                  $this->reassigned++;
 232                  $this->out( "has $edits edits! " );
 233                  if ( $doDelete ) {
 234                      $this->reassignEdits( $dupeId, $firstId );
 235                      $newEdits = $this->editCount( $dupeId );
 236                      if ( $newEdits == 0 ) {
 237                          $this->out( "confirmed cleaned. " );
 238                      } else {
 239                          $this->failed++;
 240                          $this->out( "WARNING! $newEdits remaining edits for $dupeId; NOT deleting user.\n" );
 241                          continue;
 242                      }
 243                  } else {
 244                      $this->out( "(will need to reassign edits on fix)" );
 245                  }
 246              } else {
 247                  $this->out( "ok, no edits. " );
 248              }
 249              $this->trimmed++;
 250              if ( $doDelete ) {
 251                  $this->trimAccount( $dupeId );
 252              }
 253              $this->out( "\n" );
 254          }
 255      }
 256  
 257      /**
 258       * Count the number of edits attributed to this user.
 259       * Does not currently check log table or other things
 260       * where it might show up...
 261       * @param int $userid
 262       * @return int
 263       * @access private
 264       */
 265  	function editCount( $userid ) {
 266          return intval( $this->db->selectField(
 267              'revision',
 268              'COUNT(*)',
 269              array( 'rev_user' => $userid ),
 270              __METHOD__ ) );
 271      }
 272  
 273      /**
 274       * @param int $from
 275       * @param int $to
 276       * @access private
 277       */
 278  	function reassignEdits( $from, $to ) {
 279          $this->out( 'reassigning... ' );
 280          $this->db->update( 'revision',
 281              array( 'rev_user' => $to ),
 282              array( 'rev_user' => $from ),
 283              __METHOD__ );
 284          $this->out( "ok. " );
 285      }
 286  
 287      /**
 288       * Remove a user account line.
 289       * @param int $userid
 290       * @access private
 291       */
 292  	function trimAccount( $userid ) {
 293          $this->out( "deleting..." );
 294          $this->db->delete( 'user', array( 'user_id' => $userid ), __METHOD__ );
 295          $this->out( " ok" );
 296      }
 297  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1