MediaWiki  1.23.2
cleanupImages.php
Go to the documentation of this file.
1 <?php
32 require_once __DIR__ . '/cleanupTable.inc';
33 
39 class ImageCleanup extends TableCleanup {
40  protected $defaultParams = array(
41  'table' => 'image',
42  'conds' => array(),
43  'index' => 'img_name',
44  'callback' => 'processRow',
45  );
46 
47  public function __construct() {
48  parent::__construct();
49  $this->mDescription = "Script to clean up broken, unparseable upload filenames";
50  }
51 
52  protected function processRow( $row ) {
54 
55  $source = $row->img_name;
56  if ( $source == '' ) {
57  // Ye olde empty rows. Just kill them.
58  $this->killRow( $source );
59  return $this->progress( 1 );
60  }
61 
62  $cleaned = $source;
63 
64  // About half of old bad image names have percent-codes
65  $cleaned = rawurldecode( $cleaned );
66 
67  // We also have some HTML entities there
68  $cleaned = Sanitizer::decodeCharReferences( $cleaned );
69 
70  // Some are old latin-1
71  $cleaned = $wgContLang->checkTitleEncoding( $cleaned );
72 
73  // Many of remainder look like non-normalized unicode
74  $cleaned = $wgContLang->normalize( $cleaned );
75 
76  $title = Title::makeTitleSafe( NS_FILE, $cleaned );
77 
78  if ( is_null( $title ) ) {
79  $this->output( "page $source ($cleaned) is illegal.\n" );
80  $safe = $this->buildSafeTitle( $cleaned );
81  if ( $safe === false ) {
82  return $this->progress( 0 );
83  }
84  $this->pokeFile( $source, $safe );
85  return $this->progress( 1 );
86  }
87 
88  if ( $title->getDBkey() !== $source ) {
89  $munged = $title->getDBkey();
90  $this->output( "page $source ($munged) doesn't match self.\n" );
91  $this->pokeFile( $source, $munged );
92  return $this->progress( 1 );
93  }
94 
95  return $this->progress( 0 );
96  }
97 
101  private function killRow( $name ) {
102  if ( $this->dryrun ) {
103  $this->output( "DRY RUN: would delete bogus row '$name'\n" );
104  } else {
105  $this->output( "deleting bogus row '$name'\n" );
106  $db = wfGetDB( DB_MASTER );
107  $db->delete( 'image',
108  array( 'img_name' => $name ),
109  __METHOD__ );
110  }
111  }
112 
113  private function filePath( $name ) {
114  if ( !isset( $this->repo ) ) {
115  $this->repo = RepoGroup::singleton()->getLocalRepo();
116  }
117  return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
118  }
119 
120  private function imageExists( $name, $db ) {
121  return $db->selectField( 'image', '1', array( 'img_name' => $name ), __METHOD__ );
122  }
123 
124  private function pageExists( $name, $db ) {
125  return $db->selectField( 'page', '1', array( 'page_namespace' => NS_FILE, 'page_title' => $name ), __METHOD__ );
126  }
127 
128  private function pokeFile( $orig, $new ) {
129  $path = $this->filePath( $orig );
130  if ( !file_exists( $path ) ) {
131  $this->output( "missing file: $path\n" );
132  $this->killRow( $orig );
133  return;
134  }
135 
136  $db = wfGetDB( DB_MASTER );
137 
138  /*
139  * To prevent key collisions in the update() statements below,
140  * if the target title exists in the image table, or if both the
141  * original and target titles exist in the page table, append
142  * increasing version numbers until the target title exists in
143  * neither. (See also bug 16916.)
144  */
145  $version = 0;
146  $final = $new;
147  $conflict = ( $this->imageExists( $final, $db ) ||
148  ( $this->pageExists( $orig, $db ) && $this->pageExists( $final, $db ) ) );
149 
150  while ( $conflict ) {
151  $this->output( "Rename conflicts with '$final'...\n" );
152  $version++;
153  $final = $this->appendTitle( $new, "_$version" );
154  $conflict = ( $this->imageExists( $final, $db ) || $this->pageExists( $final, $db ) );
155  }
156 
157  $finalPath = $this->filePath( $final );
158 
159  if ( $this->dryrun ) {
160  $this->output( "DRY RUN: would rename $path to $finalPath\n" );
161  } else {
162  $this->output( "renaming $path to $finalPath\n" );
163  // @todo FIXME: Should this use File::move()?
164  $db->begin( __METHOD__ );
165  $db->update( 'image',
166  array( 'img_name' => $final ),
167  array( 'img_name' => $orig ),
168  __METHOD__ );
169  $db->update( 'oldimage',
170  array( 'oi_name' => $final ),
171  array( 'oi_name' => $orig ),
172  __METHOD__ );
173  $db->update( 'page',
174  array( 'page_title' => $final ),
175  array( 'page_title' => $orig, 'page_namespace' => NS_FILE ),
176  __METHOD__ );
177  $dir = dirname( $finalPath );
178  if ( !file_exists( $dir ) ) {
179  if ( !wfMkdirParents( $dir, null, __METHOD__ ) ) {
180  $this->output( "RENAME FAILED, COULD NOT CREATE $dir" );
181  $db->rollback( __METHOD__ );
182  return;
183  }
184  }
185  if ( rename( $path, $finalPath ) ) {
186  $db->commit( __METHOD__ );
187  } else {
188  $this->error( "RENAME FAILED" );
189  $db->rollback( __METHOD__ );
190  }
191  }
192  }
193 
194  private function appendTitle( $name, $suffix ) {
195  return preg_replace( '/^(.*)(\..*?)$/',
196  "\\1$suffix\\2", $name );
197  }
198 
199  private function buildSafeTitle( $name ) {
200  $x = preg_replace_callback(
201  '/([^' . Title::legalChars() . ']|~)/',
202  array( $this, 'hexChar' ),
203  $name );
204 
206  if ( is_null( $test ) || $test->getDBkey() !== $x ) {
207  $this->error( "Unable to generate safe title from '$name', got '$x'" );
208  return false;
209  }
210 
211  return $x;
212  }
213 }
214 
215 $maintClass = "ImageCleanup";
216 require_once RUN_MAINTENANCE_IF_MAIN;
DB_MASTER
const DB_MASTER
Definition: Defines.php:56
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:53
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
wfMkdirParents
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
Definition: GlobalFunctions.php:2590
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3650
NS_FILE
const NS_FILE
Definition: Defines.php:85
RUN_MAINTENANCE_IF_MAIN
require_once RUN_MAINTENANCE_IF_MAIN
Definition: maintenance.txt:50
ImageCleanup\__construct
__construct()
Default constructor.
Definition: cleanupImages.php:47
ImageCleanup\appendTitle
appendTitle( $name, $suffix)
Definition: cleanupImages.php:194
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
ImageCleanup\imageExists
imageExists( $name, $db)
Definition: cleanupImages.php:120
ImageCleanup\buildSafeTitle
buildSafeTitle( $name)
Definition: cleanupImages.php:199
ImageCleanup\killRow
killRow( $name)
Definition: cleanupImages.php:101
$test
$test
Definition: Utf8Test.php:89
ImageCleanup\pageExists
pageExists( $name, $db)
Definition: cleanupImages.php:124
ImageCleanup\$defaultParams
$defaultParams
Definition: cleanupImages.php:40
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
TableCleanup\progress
progress( $updated)
Definition: cleanupTable.inc:74
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:422
TableCleanup
Generic class to cleanup a database table.
Definition: cleanupTable.inc:31
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
$maintClass
$maintClass
Definition: cleanupImages.php:215
ImageCleanup\pokeFile
pokeFile( $orig, $new)
Definition: cleanupImages.php:128
$version
$version
Definition: parserTests.php:86
ImageCleanup
Maintenance script to clean up broken, unparseable upload filenames.
Definition: cleanupImages.php:39
$dir
if(count( $args)==0) $dir
Definition: importImages.php:49
ImageCleanup\filePath
filePath( $name)
Definition: cleanupImages.php:113
$path
$path
Definition: NoLocalSettings.php:35
$source
if(PHP_SAPI !='cli') $source
Definition: mwdoc-filter.php:18
Maintenance\error
error( $err, $die=0)
Throw an error to the user.
Definition: Maintenance.php:333
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:314
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:529
Sanitizer\decodeCharReferences
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:1396
ImageCleanup\processRow
processRow( $row)
Definition: cleanupImages.php:52