[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |