00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 require_once( dirname(__FILE__) . '/cleanupTable.inc' );
00032
00033 class ImageCleanup extends TableCleanup {
00034 protected $defaultParams = array(
00035 'table' => 'image',
00036 'conds' => array(),
00037 'index' => 'img_name',
00038 'callback' => 'processRow',
00039 );
00040
00041 public function __construct() {
00042 parent::__construct();
00043 $this->mDescription = "Script to clean up broken, unparseable upload filenames";
00044 }
00045
00046 protected function processRow( $row ) {
00047 global $wgContLang;
00048
00049 $source = $row->img_name;
00050 if( $source == '' ) {
00051
00052 $this->killRow( $source );
00053 return $this->progress( 1 );
00054 }
00055
00056 $cleaned = $source;
00057
00058
00059 $cleaned = rawurldecode( $cleaned );
00060
00061
00062 $cleaned = Sanitizer::decodeCharReferences( $cleaned );
00063
00064
00065 $cleaned = $wgContLang->checkTitleEncoding( $cleaned );
00066
00067
00068 $cleaned = $wgContLang->normalize( $cleaned );
00069
00070 $title = Title::makeTitleSafe( NS_FILE, $cleaned );
00071
00072 if( is_null( $title ) ) {
00073 $this->output( "page $source ($cleaned) is illegal.\n" );
00074 $safe = $this->buildSafeTitle( $cleaned );
00075 if( $safe === false )
00076 return $this->progress( 0 );
00077 $this->pokeFile( $source, $safe );
00078 return $this->progress( 1 );
00079 }
00080
00081 if( $title->getDBkey() !== $source ) {
00082 $munged = $title->getDBkey();
00083 $this->output( "page $source ($munged) doesn't match self.\n" );
00084 $this->pokeFile( $source, $munged );
00085 return $this->progress( 1 );
00086 }
00087
00088 $this->progress( 0 );
00089 }
00090
00091 private function killRow( $name ) {
00092 if( $this->dryrun ) {
00093 $this->output( "DRY RUN: would delete bogus row '$name'\n" );
00094 } else {
00095 $this->output( "deleting bogus row '$name'\n" );
00096 $db = wfGetDB( DB_MASTER );
00097 $db->delete( 'image',
00098 array( 'img_name' => $name ),
00099 __METHOD__ );
00100 }
00101 }
00102
00103 private function filePath( $name ) {
00104 if ( !isset( $this->repo ) ) {
00105 $this->repo = RepoGroup::singleton()->getLocalRepo();
00106 }
00107 return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
00108 }
00109
00110 private function imageExists( $name, $db ) {
00111 return $db->selectField( 'image', '1', array( 'img_name' => $name ), __METHOD__ );
00112 }
00113
00114 private function pageExists( $name, $db ) {
00115 return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ );
00116 }
00117
00118 private function pokeFile( $orig, $new ) {
00119 $path = $this->filePath( $orig );
00120 if( !file_exists( $path ) ) {
00121 $this->output( "missing file: $path\n" );
00122 return $this->killRow( $orig );
00123 }
00124
00125 $db = wfGetDB( DB_MASTER );
00126
00127
00128
00129
00130
00131
00132
00133
00134 $version = 0;
00135 $final = $new;
00136 $conflict = ( $this->imageExists( $final, $db ) ||
00137 ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) );
00138
00139 while( $conflict ) {
00140 $this->output( "Rename conflicts with '$final'...\n" );
00141 $version++;
00142 $final = $this->appendTitle( $new, "_$version" );
00143 $conflict = ( $this->imageExists( $final, $db ) || $this->pageExists( $final, $db ) );
00144 }
00145
00146 $finalPath = $this->filePath( $final );
00147
00148 if( $this->dryrun ) {
00149 $this->output( "DRY RUN: would rename $path to $finalPath\n" );
00150 } else {
00151 $this->output( "renaming $path to $finalPath\n" );
00152
00153 $db->begin();
00154 $db->update( 'image',
00155 array( 'img_name' => $final ),
00156 array( 'img_name' => $orig ),
00157 __METHOD__ );
00158 $db->update( 'oldimage',
00159 array( 'oi_name' => $final ),
00160 array( 'oi_name' => $orig ),
00161 __METHOD__ );
00162 $db->update( 'page',
00163 array( 'page_title' => $final ),
00164 array( 'page_title' => $orig, 'page_namespace' => NS_FILE ),
00165 __METHOD__ );
00166 $dir = dirname( $finalPath );
00167 if( !file_exists( $dir ) ) {
00168 if( !wfMkdirParents( $dir ) ) {
00169 $this->log( "RENAME FAILED, COULD NOT CREATE $dir" );
00170 $db->rollback();
00171 return;
00172 }
00173 }
00174 if( rename( $path, $finalPath ) ) {
00175 $db->commit();
00176 } else {
00177 $this->error( "RENAME FAILED" );
00178 $db->rollback();
00179 }
00180 }
00181 }
00182
00183 private function appendTitle( $name, $suffix ) {
00184 return preg_replace( '/^(.*)(\..*?)$/',
00185 "\\1$suffix\\2", $name );
00186 }
00187
00188 private function buildSafeTitle( $name ) {
00189 global $wgLegalTitleChars;
00190 $x = preg_replace_callback(
00191 "/([^$wgLegalTitleChars]|~)/",
00192 array( $this, 'hexChar' ),
00193 $name );
00194
00195 $test = Title::makeTitleSafe( NS_FILE, $x );
00196 if( is_null( $test ) || $test->getDBkey() !== $x ) {
00197 $this->error( "Unable to generate safe title from '$name', got '$x'" );
00198 return false;
00199 }
00200
00201 return $x;
00202 }
00203 }
00204
00205 $maintClass = "ImageCleanup";
00206 require_once( DO_MAINTENANCE );