MediaWiki
REL1_24
|
00001 <?php 00035 class UserDupes { 00036 private $db; 00037 private $reassigned; 00038 private $trimmed; 00039 private $failed; 00040 private $outputCallback; 00041 00042 function __construct( &$database, $outputCallback ) { 00043 $this->db = $database; 00044 $this->outputCallback = $outputCallback; 00045 } 00046 00051 private function out( $str ) { 00052 call_user_func( $this->outputCallback, $str ); 00053 } 00054 00060 function hasUniqueIndex() { 00061 $info = $this->db->indexInfo( 'user', 'user_name', __METHOD__ ); 00062 if ( !$info ) { 00063 $this->out( "WARNING: doesn't seem to have user_name index at all!\n" ); 00064 00065 return false; 00066 } 00067 00068 # Confusingly, 'Non_unique' is 0 for *unique* indexes, 00069 # and 1 for *non-unique* indexes. Pass the crack, MySQL, 00070 # it's obviously some good stuff! 00071 return ( $info[0]->Non_unique == 0 ); 00072 } 00073 00085 function clearDupes() { 00086 return $this->checkDupes( true ); 00087 } 00088 00103 function checkDupes( $doDelete = false ) { 00104 if ( $this->hasUniqueIndex() ) { 00105 echo wfWikiID() . " already has a unique index on its user table.\n"; 00106 00107 return true; 00108 } 00109 00110 $this->lock(); 00111 00112 $this->out( "Checking for duplicate accounts...\n" ); 00113 $dupes = $this->getDupes(); 00114 $count = count( $dupes ); 00115 00116 $this->out( "Found $count accounts with duplicate records on " . wfWikiID() . ".\n" ); 00117 $this->trimmed = 0; 00118 $this->reassigned = 0; 00119 $this->failed = 0; 00120 foreach ( $dupes as $name ) { 00121 $this->examine( $name, $doDelete ); 00122 } 00123 00124 $this->unlock(); 00125 00126 $this->out( "\n" ); 00127 00128 if ( $this->reassigned > 0 ) { 00129 if ( $doDelete ) { 00130 $this->out( "$this->reassigned duplicate accounts had edits " 00131 . "reassigned to a canonical record id.\n" ); 00132 } else { 00133 $this->out( "$this->reassigned duplicate accounts need to have edits reassigned.\n" ); 00134 } 00135 } 00136 00137 if ( $this->trimmed > 0 ) { 00138 if ( $doDelete ) { 00139 $this->out( "$this->trimmed duplicate user records were deleted from " 00140 . wfWikiID() . ".\n" ); 00141 } else { 00142 $this->out( "$this->trimmed duplicate user accounts were found on " 00143 . wfWikiID() . " which can be removed safely.\n" ); 00144 } 00145 } 00146 00147 if ( $this->failed > 0 ) { 00148 $this->out( "Something terribly awry; $this->failed duplicate accounts were not removed.\n" ); 00149 00150 return false; 00151 } 00152 00153 if ( $this->trimmed == 0 || $doDelete ) { 00154 $this->out( "It is now safe to apply the unique index on user_name.\n" ); 00155 00156 return true; 00157 } else { 00158 $this->out( "Run this script again with the --fix option to automatically delete them.\n" ); 00159 00160 return false; 00161 } 00162 } 00163 00168 function lock() { 00169 $set = array( 'user', 'revision' ); 00170 $names = array_map( array( $this, 'lockTable' ), $set ); 00171 $tables = implode( ',', $names ); 00172 00173 $this->db->query( "LOCK TABLES $tables", __METHOD__ ); 00174 } 00175 00176 function lockTable( $table ) { 00177 return $this->db->tableName( $table ) . ' WRITE'; 00178 } 00179 00183 function unlock() { 00184 $this->db->query( "UNLOCK TABLES", __METHOD__ ); 00185 } 00186 00192 function getDupes() { 00193 $user = $this->db->tableName( 'user' ); 00194 $result = $this->db->query( 00195 "SELECT user_name,COUNT(*) AS n 00196 FROM $user 00197 GROUP BY user_name 00198 HAVING n > 1", __METHOD__ ); 00199 00200 $list = array(); 00201 foreach ( $result as $row ) { 00202 $list[] = $row->user_name; 00203 } 00204 00205 return $list; 00206 } 00207 00216 function examine( $name, $doDelete ) { 00217 $result = $this->db->select( 'user', 00218 array( 'user_id' ), 00219 array( 'user_name' => $name ), 00220 __METHOD__ ); 00221 00222 $firstRow = $this->db->fetchObject( $result ); 00223 $firstId = $firstRow->user_id; 00224 $this->out( "Record that will be used for '$name' is user_id=$firstId\n" ); 00225 00226 foreach ( $result as $row ) { 00227 $dupeId = $row->user_id; 00228 $this->out( "... dupe id $dupeId: " ); 00229 $edits = $this->editCount( $dupeId ); 00230 if ( $edits > 0 ) { 00231 $this->reassigned++; 00232 $this->out( "has $edits edits! " ); 00233 if ( $doDelete ) { 00234 $this->reassignEdits( $dupeId, $firstId ); 00235 $newEdits = $this->editCount( $dupeId ); 00236 if ( $newEdits == 0 ) { 00237 $this->out( "confirmed cleaned. " ); 00238 } else { 00239 $this->failed++; 00240 $this->out( "WARNING! $newEdits remaining edits for $dupeId; NOT deleting user.\n" ); 00241 continue; 00242 } 00243 } else { 00244 $this->out( "(will need to reassign edits on fix)" ); 00245 } 00246 } else { 00247 $this->out( "ok, no edits. " ); 00248 } 00249 $this->trimmed++; 00250 if ( $doDelete ) { 00251 $this->trimAccount( $dupeId ); 00252 } 00253 $this->out( "\n" ); 00254 } 00255 } 00256 00265 function editCount( $userid ) { 00266 return intval( $this->db->selectField( 00267 'revision', 00268 'COUNT(*)', 00269 array( 'rev_user' => $userid ), 00270 __METHOD__ ) ); 00271 } 00272 00278 function reassignEdits( $from, $to ) { 00279 $this->out( 'reassigning... ' ); 00280 $this->db->update( 'revision', 00281 array( 'rev_user' => $to ), 00282 array( 'rev_user' => $from ), 00283 __METHOD__ ); 00284 $this->out( "ok. " ); 00285 } 00286 00292 function trimAccount( $userid ) { 00293 $this->out( "deleting..." ); 00294 $this->db->delete( 'user', array( 'user_id' => $userid ), __METHOD__ ); 00295 $this->out( " ok" ); 00296 } 00297 }