MediaWiki  REL1_19
cleanupImages.php
Go to the documentation of this file.
00001 <?php
00032 require_once( dirname( __FILE__ ) . '/cleanupTable.inc' );
00033 
00034 class ImageCleanup extends TableCleanup {
00035         protected $defaultParams = array(
00036                 'table' => 'image',
00037                 'conds' => array(),
00038                 'index' => 'img_name',
00039                 'callback' => 'processRow',
00040         );
00041 
00042         public function __construct() {
00043                 parent::__construct();
00044                 $this->mDescription = "Script to clean up broken, unparseable upload filenames";
00045         }
00046 
00047         protected function processRow( $row ) {
00048                 global $wgContLang;
00049 
00050                 $source = $row->img_name;
00051                 if ( $source == '' ) {
00052                         // Ye olde empty rows. Just kill them.
00053                         $this->killRow( $source );
00054                         return $this->progress( 1 );
00055                 }
00056 
00057                 $cleaned = $source;
00058 
00059                 // About half of old bad image names have percent-codes
00060                 $cleaned = rawurldecode( $cleaned );
00061 
00062                 // We also have some HTML entities there
00063                 $cleaned = Sanitizer::decodeCharReferences( $cleaned );
00064 
00065                 // Some are old latin-1
00066                 $cleaned = $wgContLang->checkTitleEncoding( $cleaned );
00067 
00068                 // Many of remainder look like non-normalized unicode
00069                 $cleaned = $wgContLang->normalize( $cleaned );
00070 
00071                 $title = Title::makeTitleSafe( NS_FILE, $cleaned );
00072 
00073                 if ( is_null( $title ) ) {
00074                         $this->output( "page $source ($cleaned) is illegal.\n" );
00075                         $safe = $this->buildSafeTitle( $cleaned );
00076                         if ( $safe === false ) {
00077                                 return $this->progress( 0 );
00078                         }
00079                         $this->pokeFile( $source, $safe );
00080                         return $this->progress( 1 );
00081                 }
00082 
00083                 if ( $title->getDBkey() !== $source ) {
00084                         $munged = $title->getDBkey();
00085                         $this->output( "page $source ($munged) doesn't match self.\n" );
00086                         $this->pokeFile( $source, $munged );
00087                         return $this->progress( 1 );
00088                 }
00089 
00090                 return $this->progress( 0 );
00091         }
00092 
00096         private function killRow( $name ) {
00097                 if ( $this->dryrun ) {
00098                         $this->output( "DRY RUN: would delete bogus row '$name'\n" );
00099                 } else {
00100                         $this->output( "deleting bogus row '$name'\n" );
00101                         $db = wfGetDB( DB_MASTER );
00102                         $db->delete( 'image',
00103                                 array( 'img_name' => $name ),
00104                                 __METHOD__ );
00105                 }
00106         }
00107 
00108         private function filePath( $name ) {
00109                 if ( !isset( $this->repo ) ) {
00110                         $this->repo = RepoGroup::singleton()->getLocalRepo();
00111                 }
00112                 return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
00113         }
00114 
00115         private function imageExists( $name, $db ) {
00116                 return $db->selectField( 'image', '1', array( 'img_name' => $name ), __METHOD__ );
00117         }
00118 
00119         private function pageExists( $name, $db ) {
00120                 return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ );
00121         }
00122 
00123         private function pokeFile( $orig, $new ) {
00124                 $path = $this->filePath( $orig );
00125                 if ( !file_exists( $path ) ) {
00126                         $this->output( "missing file: $path\n" );
00127                         $this->killRow( $orig );
00128                         return;
00129                 }
00130 
00131                 $db = wfGetDB( DB_MASTER );
00132 
00133                 /*
00134                  * To prevent key collisions in the update() statements below,
00135                  * if the target title exists in the image table, or if both the
00136                  * original and target titles exist in the page table, append
00137                  * increasing version numbers until the target title exists in
00138                  * neither.  (See also bug 16916.)
00139                  */
00140                 $version = 0;
00141                 $final = $new;
00142                 $conflict = ( $this->imageExists( $final, $db ) ||
00143                                 ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) );
00144 
00145                 while ( $conflict ) {
00146                         $this->output( "Rename conflicts with '$final'...\n" );
00147                         $version++;
00148                         $final = $this->appendTitle( $new, "_$version" );
00149                         $conflict = ( $this->imageExists( $final, $db ) || $this->pageExists( $final, $db ) );
00150                 }
00151 
00152                 $finalPath = $this->filePath( $final );
00153 
00154                 if ( $this->dryrun ) {
00155                         $this->output( "DRY RUN: would rename $path to $finalPath\n" );
00156                 } else {
00157                         $this->output( "renaming $path to $finalPath\n" );
00158                         // @todo FIXME: Should this use File::move()?
00159                         $db->begin();
00160                         $db->update( 'image',
00161                                 array( 'img_name' => $final ),
00162                                 array( 'img_name' => $orig ),
00163                                 __METHOD__ );
00164                         $db->update( 'oldimage',
00165                                 array( 'oi_name' => $final ),
00166                                 array( 'oi_name' => $orig ),
00167                                 __METHOD__ );
00168                         $db->update( 'page',
00169                                 array( 'page_title' => $final ),
00170                                 array( 'page_title' => $orig, 'page_namespace' => NS_FILE ),
00171                                 __METHOD__ );
00172                         $dir = dirname( $finalPath );
00173                         if ( !file_exists( $dir ) ) {
00174                                 if ( !wfMkdirParents( $dir, null, __METHOD__ ) ) {
00175                                         $this->output( "RENAME FAILED, COULD NOT CREATE $dir" );
00176                                         $db->rollback();
00177                                         return;
00178                                 }
00179                         }
00180                         if ( rename( $path, $finalPath ) ) {
00181                                 $db->commit();
00182                         } else {
00183                                 $this->error( "RENAME FAILED" );
00184                                 $db->rollback();
00185                         }
00186                 }
00187         }
00188 
00189         private function appendTitle( $name, $suffix ) {
00190                 return preg_replace( '/^(.*)(\..*?)$/',
00191                         "\\1$suffix\\2", $name );
00192         }
00193 
00194         private function buildSafeTitle( $name ) {
00195                 global $wgLegalTitleChars;
00196                 $x = preg_replace_callback(
00197                         "/([^$wgLegalTitleChars]|~)/",
00198                         array( $this, 'hexChar' ),
00199                         $name );
00200 
00201                 $test = Title::makeTitleSafe( NS_FILE, $x );
00202                 if ( is_null( $test ) || $test->getDBkey() !== $x ) {
00203                         $this->error( "Unable to generate safe title from '$name', got '$x'" );
00204                         return false;
00205                 }
00206 
00207                 return $x;
00208         }
00209 }
00210 
00211 $maintClass = "ImageCleanup";
00212 require_once( RUN_MAINTENANCE_IF_MAIN );