MediaWiki  master
uppercaseTitlesForUnicodeTransition.php
Go to the documentation of this file.
1 <?php
27 
28 require_once __DIR__ . '/Maintenance.php';
29 
37 
38  private const MOVE = 0;
39  private const INPLACE_MOVE = 1;
40  private const UPPERCASE = 2;
41 
43  private $run = false;
44 
46  private $charmap = [];
47 
49  private $user;
50 
52  private $reason = 'Uppercasing title for Unicode upgrade';
53 
55  private $tags = [];
56 
58  private $seenUsers = [];
59 
61  private $namespaces = null;
62 
64  private $prefix = null, $suffix = null;
65 
67  private $prefixNs = null;
68 
70  private $tables = null;
71 
72  public function __construct() {
73  parent::__construct();
74  $this->addDescription(
75  "Rename titles when changing behavior of Language::ucfirst().\n"
76  . "\n"
77  . "This script skips User and User_talk pages for registered users, as renaming of users "
78  . "is too complex to try to implement here. Use something like Extension:Renameuser to "
79  . "clean those up; this script can provide a list of user names affected."
80  );
81  $this->addOption(
82  'charmap', 'Character map generated by maintenance/language/generateUcfirstOverrides.php',
83  true, true
84  );
85  $this->addOption(
86  'user', 'System user to use to do the renames. Default is "Maintenance script".', false, true
87  );
88  $this->addOption(
89  'steal',
90  'If the username specified by --user exists, specify this to force conversion to a system user.'
91  );
92  $this->addOption(
93  'run', 'If not specified, the script will not actually perform any moves (i.e. it will dry-run).'
94  );
95  $this->addOption(
96  'prefix', 'When the new title already exists, add this prefix.', false, true
97  );
98  $this->addOption(
99  'suffix', 'When the new title already exists, add this suffix.', false, true
100  );
101  $this->addOption( 'reason', 'Reason to use when moving pages.', false, true );
102  $this->addOption( 'tag', 'Change tag to apply when moving pages.', false, true );
103  $this->addOption( 'tables', 'Comma-separated list of database tables to process.', false, true );
104  $this->addOption(
105  'userlist', 'Filename to which to output usernames needing rename. ' .
106  'This file can then be used directly by renameInvalidUsernames.php maintenance script',
107  false,
108  true
109  );
110  $this->setBatchSize( 1000 );
111  }
112 
113  public function execute() {
114  $this->run = $this->getOption( 'run', false );
115 
116  if ( $this->run ) {
117  $username = $this->getOption( 'user', User::MAINTENANCE_SCRIPT_USER );
118  $steal = $this->getOption( 'steal', false );
119  $this->user = User::newSystemUser( $username, [ 'steal' => $steal ] );
120  if ( !$this->user ) {
121  $user = User::newFromName( $username );
122  if ( !$steal && $user && $user->isRegistered() ) {
123  $this->fatalError( "User $username already exists.\n"
124  . "Use --steal if you really want to steal it from the human who currently owns it."
125  );
126  }
127  $this->fatalError( "Could not obtain system user $username." );
128  }
129  }
130 
131  $tables = $this->getOption( 'tables' );
132  if ( $tables !== null ) {
133  $this->tables = explode( ',', $tables );
134  }
135 
136  $prefix = $this->getOption( 'prefix' );
137  if ( $prefix !== null ) {
138  $title = Title::newFromText( $prefix . 'X' );
139  if ( !$title || substr( $title->getDBkey(), -1 ) !== 'X' ) {
140  $this->fatalError( 'Invalid --prefix.' );
141  }
142  if ( $title->getNamespace() <= NS_MAIN || $title->isExternal() ) {
143  $this->fatalError( 'Invalid --prefix. It must not be in namespace 0 and must not be external' );
144  }
145  $this->prefixNs = $title->getNamespace();
146  $this->prefix = substr( $title->getText(), 0, -1 );
147  }
148  $this->suffix = $this->getOption( 'suffix' );
149 
150  $this->reason = $this->getOption( 'reason' ) ?: $this->reason;
151  $this->tags = (array)$this->getOption( 'tag', null );
152 
153  $charmapFile = $this->getOption( 'charmap' );
154  if ( !file_exists( $charmapFile ) ) {
155  $this->fatalError( "Charmap file $charmapFile does not exist." );
156  }
157  if ( !is_file( $charmapFile ) || !is_readable( $charmapFile ) ) {
158  $this->fatalError( "Charmap file $charmapFile is not readable." );
159  }
160  $this->charmap = require $charmapFile;
161  if ( !is_array( $this->charmap ) ) {
162  $this->fatalError( "Charmap file $charmapFile did not return a PHP array." );
163  }
164  $this->charmap = array_filter(
165  $this->charmap,
166  function ( $v, $k ) {
167  if ( mb_strlen( $k ) !== 1 ) {
168  $this->error( "Ignoring mapping from multi-character key '$k' to '$v'" );
169  return false;
170  }
171  return $k !== $v;
172  },
173  ARRAY_FILTER_USE_BOTH
174  );
175  if ( !$this->charmap ) {
176  $this->fatalError( "Charmap file $charmapFile did not contain any usable character mappings." );
177  }
178 
179  $db = $this->getDB( $this->run ? DB_PRIMARY : DB_REPLICA );
180 
181  // Process inplace moves first, before actual moves, so mungeTitle() doesn't get confused
182  $this->processTable(
183  $db, self::INPLACE_MOVE, 'archive', 'ar_namespace', 'ar_title', [ 'ar_timestamp', 'ar_id' ]
184  );
185  $this->processTable(
186  $db, self::INPLACE_MOVE, 'filearchive', NS_FILE, 'fa_name', [ 'fa_timestamp', 'fa_id' ]
187  );
188  $this->processTable(
189  $db, self::INPLACE_MOVE, 'logging', 'log_namespace', 'log_title', [ 'log_id' ]
190  );
191  $this->processTable(
192  $db, self::INPLACE_MOVE, 'protected_titles', 'pt_namespace', 'pt_title', []
193  );
194  $this->processTable( $db, self::MOVE, 'page', 'page_namespace', 'page_title', [ 'page_id' ] );
195  $this->processTable( $db, self::MOVE, 'image', NS_FILE, 'img_name', [] );
196  $this->processTable(
197  $db, self::UPPERCASE, 'redirect', 'rd_namespace', 'rd_title', [ 'rd_from' ]
198  );
199  $this->processUsers( $db );
200  }
201 
209  private function getLikeBatches( IDatabase $db, $field, $batchSize = 100 ) {
210  $ret = [];
211  $likes = [];
212  foreach ( $this->charmap as $from => $to ) {
213  $likes[] = $field . $db->buildLike( $from, $db->anyString() );
214  if ( count( $likes ) >= $batchSize ) {
215  $ret[] = $db->makeList( $likes, $db::LIST_OR );
216  $likes = [];
217  }
218  }
219  if ( $likes ) {
220  $ret[] = $db->makeList( $likes, $db::LIST_OR );
221  }
222  return $ret;
223  }
224 
233  private function getNamespaces() {
234  if ( $this->namespaces === null ) {
235  $nsinfo = MediaWikiServices::getInstance()->getNamespaceInfo();
236  $this->namespaces = array_filter(
237  array_keys( $nsinfo->getCanonicalNamespaces() ),
238  static function ( $ns ) use ( $nsinfo ) {
239  return $nsinfo->isMovable( $ns ) && $nsinfo->isCapitalized( $ns );
240  }
241  );
242  usort( $this->namespaces, static function ( $ns1, $ns2 ) use ( $nsinfo ) {
243  if ( $ns1 === $ns2 ) {
244  return 0;
245  }
246 
247  $s1 = $nsinfo->getSubject( $ns1 );
248  $s2 = $nsinfo->getSubject( $ns2 );
249 
250  // Order by subject namespace number first
251  if ( $s1 !== $s2 ) {
252  return $s1 < $s2 ? -1 : 1;
253  }
254 
255  // Second, put subject namespaces before non-subject namespaces
256  if ( $s1 === $ns1 ) {
257  return -1;
258  }
259  if ( $s2 === $ns2 ) {
260  return 1;
261  }
262 
263  // Don't care about the relative order if there are somehow
264  // multiple non-subject namespaces for a namespace.
265  return 0;
266  } );
267  }
268 
269  return $this->namespaces;
270  }
271 
279  private function isUserPage( IDatabase $db, $ns, $title ) {
280  if ( $ns !== NS_USER && $ns !== NS_USER_TALK ) {
281  return false;
282  }
283 
284  list( $base ) = explode( '/', $title, 2 );
285  if ( !isset( $this->seenUsers[$base] ) ) {
286  // Can't use User directly because it might uppercase the name
287  $this->seenUsers[$base] = (bool)$db->selectField(
288  'user',
289  'user_id',
290  [ 'user_name' => strtr( $base, '_', ' ' ) ],
291  __METHOD__
292  );
293  }
294  return $this->seenUsers[$base];
295  }
296 
304  private function mungeTitle( IDatabase $db, Title $oldTitle, Title &$newTitle ) {
305  $nt = $newTitle->getPrefixedText();
306 
307  $munge = false;
308  if ( $this->isUserPage( $db, $newTitle->getNamespace(), $newTitle->getText() ) ) {
309  $munge = 'Target title\'s user exists';
310  } else {
311  $mpFactory = MediaWikiServices::getInstance()->getMovePageFactory();
312  $status = $mpFactory->newMovePage( $oldTitle, $newTitle )->isValidMove();
313  if ( !$status->isOK() && (
314  $status->hasMessage( 'articleexists' ) || $status->hasMessage( 'redirectexists' ) ) ) {
315  $munge = 'Target title exists';
316  }
317  }
318  if ( !$munge ) {
319  return true;
320  }
321 
322  if ( $this->prefix !== null ) {
323  $newTitle = Title::makeTitle(
324  $this->prefixNs,
325  $this->prefix . $oldTitle->getPrefixedText() . ( $this->suffix ?? '' )
326  );
327  } elseif ( $this->suffix !== null ) {
328  $dbkey = $newTitle->getText();
329  $i = $newTitle->getNamespace() === NS_FILE ? strrpos( $dbkey, '.' ) : false;
330  if ( $i !== false ) {
331  $newTitle = Title::makeTitle(
332  $newTitle->getNamespace(),
333  substr( $dbkey, 0, $i ) . $this->suffix . substr( $dbkey, $i )
334  );
335  } else {
336  $newTitle = Title::makeTitle( $newTitle->getNamespace(), $dbkey . $this->suffix );
337  }
338  } else {
339  $this->error(
340  "Cannot move {$oldTitle->getPrefixedText()} → $nt: "
341  . "$munge and no --prefix or --suffix was given"
342  );
343  return false;
344  }
345 
346  if ( !$newTitle->canExist() ) {
347  $this->error(
348  "Cannot move {$oldTitle->getPrefixedText()} → $nt: "
349  . "$munge and munged title '{$newTitle->getPrefixedText()}' is not valid"
350  );
351  return false;
352  }
353  if ( $newTitle->exists() ) {
354  $this->error(
355  "Cannot move {$oldTitle->getPrefixedText()} → $nt: "
356  . "$munge and munged title '{$newTitle->getPrefixedText()}' also exists"
357  );
358  return false;
359  }
360 
361  return true;
362  }
363 
371  private function doMove( IDatabase $db, $ns, $title ) {
372  $char = mb_substr( $title, 0, 1 );
373  if ( !array_key_exists( $char, $this->charmap ) ) {
374  $this->error(
375  "Query returned NS$ns $title, which does not begin with a character in the charmap."
376  );
377  return false;
378  }
379 
380  if ( $this->isUserPage( $db, $ns, $title ) ) {
381  $this->output( "... Skipping user page NS$ns $title\n" );
382  return null;
383  }
384 
385  $oldTitle = Title::makeTitle( $ns, $title );
386  $newTitle = Title::makeTitle( $ns, $this->charmap[$char] . mb_substr( $title, 1 ) );
387  $deletionReason = $this->shouldDelete( $db, $oldTitle, $newTitle );
388  if ( !$this->mungeTitle( $db, $oldTitle, $newTitle ) ) {
389  return false;
390  }
391 
392  $services = MediaWikiServices::getInstance();
393  $mpFactory = $services->getMovePageFactory();
394  $movePage = $mpFactory->newMovePage( $oldTitle, $newTitle );
395  $status = $movePage->isValidMove();
396  if ( !$status->isOK() ) {
397  $this->error(
398  "Invalid move {$oldTitle->getPrefixedText()} → {$newTitle->getPrefixedText()}: "
399  . $status->getMessage( false, false, 'en' )->useDatabase( false )->plain()
400  );
401  return false;
402  }
403 
404  if ( !$this->run ) {
405  $this->output(
406  "Would rename {$oldTitle->getPrefixedText()} → {$newTitle->getPrefixedText()}\n"
407  );
408  if ( $deletionReason ) {
409  $this->output(
410  "Would then delete {$newTitle->getPrefixedText()}: $deletionReason\n"
411  );
412  }
413  return true;
414  }
415 
416  $status = $movePage->move( $this->user, $this->reason, false, $this->tags );
417  if ( !$status->isOK() ) {
418  $this->error(
419  "Move {$oldTitle->getPrefixedText()} → {$newTitle->getPrefixedText()} failed: "
420  . $status->getMessage( false, false, 'en' )->useDatabase( false )->plain()
421  );
422  }
423  $this->output( "Renamed {$oldTitle->getPrefixedText()} → {$newTitle->getPrefixedText()}\n" );
424 
425  // The move created a log entry under the old invalid title. Fix it.
426  $db->update(
427  'logging',
428  [
429  'log_title' => $this->charmap[$char] . mb_substr( $title, 1 ),
430  ],
431  [
432  'log_namespace' => $oldTitle->getNamespace(),
433  'log_title' => $oldTitle->getDBkey(),
434  'log_page' => $newTitle->getArticleID(),
435  ],
436  __METHOD__
437  );
438 
439  if ( $deletionReason !== null ) {
440  $page = $services->getWikiPageFactory()->newFromTitle( $newTitle );
441  $error = '';
442  $status = $page->doDeleteArticleReal(
443  $deletionReason,
444  $this->user,
445  false, // don't suppress
446  null, // unused
447  $error,
448  null, // unused
449  [], // tags
450  'delete',
451  true // immediate
452  );
453  if ( !$status->isOK() ) {
454  $this->error(
455  "Deletion of {$newTitle->getPrefixedText()} failed: "
456  . $status->getMessage( false, false, 'en' )->useDatabase( false )->plain()
457  );
458  return false;
459  }
460  $this->output( "Deleted {$newTitle->getPrefixedText()}\n" );
461  }
462 
463  return true;
464  }
465 
480  private function shouldDelete( IDatabase $db, Title $oldTitle, Title $newTitle ) {
481  $oldRow = $db->selectRow(
482  [ 'page', 'redirect' ],
483  [ 'ns' => 'rd_namespace', 'title' => 'rd_title' ],
484  [ 'page_namespace' => $oldTitle->getNamespace(), 'page_title' => $oldTitle->getDBkey() ],
485  __METHOD__,
486  [],
487  [ 'redirect' => [ 'JOIN', 'rd_from = page_id' ] ]
488  );
489  if ( !$oldRow ) {
490  // Not a redirect
491  return null;
492  }
493 
494  if ( (int)$oldRow->ns === $newTitle->getNamespace() &&
495  $oldRow->title === $newTitle->getDBkey()
496  ) {
497  return $this->reason . ", and found that [[{$oldTitle->getPrefixedText()}]] is "
498  . "already a redirect to [[{$newTitle->getPrefixedText()}]]";
499  } else {
500  $newRow = $db->selectRow(
501  [ 'page', 'redirect' ],
502  [ 'ns' => 'rd_namespace', 'title' => 'rd_title' ],
503  [ 'page_namespace' => $newTitle->getNamespace(), 'page_title' => $newTitle->getDBkey() ],
504  __METHOD__,
505  [],
506  [ 'redirect' => [ 'JOIN', 'rd_from = page_id' ] ]
507  );
508  if ( $newRow && $oldRow->ns === $newRow->ns && $oldRow->title === $newRow->title ) {
509  $nt = Title::makeTitle( $newRow->ns, $newRow->title );
510  return $this->reason . ", and found that [[{$oldTitle->getPrefixedText()}]] and "
511  . "[[{$newTitle->getPrefixedText()}]] both redirect to [[{$nt->getPrefixedText()}]].";
512  }
513  }
514 
515  return null;
516  }
517 
530  private function doUpdate( IDatabase $db, $op, $table, $nsField, $titleField, $row ) {
531  $ns = is_int( $nsField ) ? $nsField : (int)$row->$nsField;
532  $title = $row->$titleField;
533 
534  $char = mb_substr( $title, 0, 1 );
535  if ( !array_key_exists( $char, $this->charmap ) ) {
536  $r = json_encode( $row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
537  $this->error(
538  "Query returned $r, but title does not begin with a character in the charmap."
539  );
540  return false;
541  }
542 
543  $oldTitle = Title::makeTitle( $ns, $title );
544  $newTitle = Title::makeTitle( $ns, $this->charmap[$char] . mb_substr( $title, 1 ) );
545  if ( $op !== self::UPPERCASE && !$this->mungeTitle( $db, $oldTitle, $newTitle ) ) {
546  return false;
547  }
548 
549  if ( $this->run ) {
550  $db->update(
551  $table,
552  array_merge(
553  is_int( $nsField ) ? [] : [ $nsField => $newTitle->getNamespace() ],
554  [ $titleField => $newTitle->getDBkey() ]
555  ),
556  (array)$row,
557  __METHOD__
558  );
559  $r = json_encode( $row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
560  $this->output( "Set $r to {$newTitle->getPrefixedText()}\n" );
561  } else {
562  $r = json_encode( $row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
563  $this->output( "Would set $r to {$newTitle->getPrefixedText()}\n" );
564  }
565 
566  return true;
567  }
568 
582  private function processTable( IDatabase $db, $op, $table, $nsField, $titleField, $pkFields ) {
583  if ( $this->tables !== null && !in_array( $table, $this->tables, true ) ) {
584  $this->output( "Skipping table `$table`, not in --tables.\n" );
585  return;
586  }
587 
588  $batchSize = $this->getBatchSize();
589  $namespaces = $this->getNamespaces();
590  $likes = $this->getLikeBatches( $db, $titleField );
591  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
592 
593  if ( is_int( $nsField ) ) {
594  $namespaces = array_intersect( $namespaces, [ $nsField ] );
595  }
596 
597  if ( !$namespaces ) {
598  $this->output( "Skipping table `$table`, no valid namespaces.\n" );
599  return;
600  }
601 
602  $this->output( "Processing table `$table`...\n" );
603 
604  $selectFields = array_merge(
605  is_int( $nsField ) ? [] : [ $nsField ],
606  [ $titleField ],
607  $pkFields
608  );
609  $contFields = array_reverse( array_merge( [ $titleField ], $pkFields ) );
610 
611  $lastReplicationWait = 0.0;
612  $count = 0;
613  $errors = 0;
614  foreach ( $namespaces as $ns ) {
615  foreach ( $likes as $like ) {
616  $cont = [];
617  do {
618  $res = $db->select(
619  $table,
620  $selectFields,
621  array_merge( [ "$nsField = $ns", $like ], $cont ),
622  __METHOD__,
623  [ 'ORDER BY' => array_merge( [ $titleField ], $pkFields ), 'LIMIT' => $batchSize ]
624  );
625  $cont = [];
626  foreach ( $res as $row ) {
627  $cont = '';
628  foreach ( $contFields as $field ) {
629  $v = $db->addQuotes( $row->$field );
630  if ( $cont === '' ) {
631  $cont = "$field > $v";
632  } else {
633  $cont = "$field > $v OR $field = $v AND ($cont)";
634  }
635  }
636  $cont = [ $cont ];
637 
638  if ( $op === self::MOVE ) {
639  $ns = is_int( $nsField ) ? $nsField : (int)$row->$nsField;
640  $ret = $this->doMove( $db, $ns, $row->$titleField );
641  } else {
642  $ret = $this->doUpdate( $db, $op, $table, $nsField, $titleField, $row );
643  }
644  if ( $ret === true ) {
645  $count++;
646  } elseif ( $ret === false ) {
647  $errors++;
648  }
649  }
650 
651  if ( $this->run ) {
652  $r = $cont ? json_encode( $row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) : '<end>';
653  $this->output( "... $table: $count renames, $errors errors at $r\n" );
654  $lbFactory->waitForReplication(
655  [ 'timeout' => 30, 'ifWritesSince' => $lastReplicationWait ]
656  );
657  $lastReplicationWait = microtime( true );
658  }
659  } while ( $cont );
660  }
661  }
662 
663  $this->output( "Done processing table `$table`.\n" );
664  }
665 
670  private function processUsers( IDatabase $db ) {
671  $userlistFile = $this->getOption( 'userlist' );
672  if ( $userlistFile === null ) {
673  $this->output( "Not generating user list, --userlist was not specified.\n" );
674  return;
675  }
676 
677  $fh = fopen( $userlistFile, 'ab' );
678  if ( !$fh ) {
679  $this->error( "Could not open user list file $userlistFile" );
680  return;
681  }
682 
683  $this->output( "Generating user list...\n" );
684  $count = 0;
685  $batchSize = $this->getBatchSize();
686  foreach ( $this->getLikeBatches( $db, 'user_name' ) as $like ) {
687  $cont = [];
688  while ( true ) {
689  $rows = $db->select(
690  'user',
691  [ 'user_id', 'user_name' ],
692  array_merge( [ $like ], $cont ),
693  __METHOD__,
694  [ 'ORDER BY' => 'user_name', 'LIMIT' => $batchSize ]
695  );
696 
697  if ( !$rows->numRows() ) {
698  break;
699  }
700 
701  foreach ( $rows as $row ) {
702  $char = mb_substr( $row->user_name, 0, 1 );
703  if ( !array_key_exists( $char, $this->charmap ) ) {
704  $this->error(
705  "Query returned $row->user_name, but user name does not " .
706  "begin with a character in the charmap."
707  );
708  continue;
709  }
710  $newName = $this->charmap[$char] . mb_substr( $row->user_name, 1 );
711  fprintf( $fh, "%s\t%s\t%s\n", WikiMap::getCurrentWikiId(), $row->user_id, $newName );
712  $count++;
713  $cont = [ 'user_name > ' . $db->addQuotes( $row->user_name ) ];
714  }
715  $this->output( "... at $row->user_name, $count names so far\n" );
716  }
717  }
718 
719  if ( !fclose( $fh ) ) {
720  $this->error( "fclose on $userlistFile failed" );
721  }
722  $this->output( "User list output to $userlistFile, $count users need renaming.\n" );
723  }
724 }
725 
726 $maintClass = UppercaseTitlesForUnicodeTransition::class;
727 require_once RUN_MAINTENANCE_IF_MAIN;
LIST_OR
const LIST_OR
Definition: Defines.php:46
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:377
UppercaseTitlesForUnicodeTransition\UPPERCASE
const UPPERCASE
Definition: uppercaseTitlesForUnicodeTransition.php:40
UppercaseTitlesForUnicodeTransition\getNamespaces
getNamespaces()
Get the list of namespaces to operate on.
Definition: uppercaseTitlesForUnicodeTransition.php:233
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:2530
UppercaseTitlesForUnicodeTransition\$prefixNs
int null $prefixNs
Definition: uppercaseTitlesForUnicodeTransition.php:67
UppercaseTitlesForUnicodeTransition\INPLACE_MOVE
const INPLACE_MOVE
Definition: uppercaseTitlesForUnicodeTransition.php:39
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
UppercaseTitlesForUnicodeTransition\MOVE
const MOVE
Definition: uppercaseTitlesForUnicodeTransition.php:38
Maintenance\fatalError
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Definition: Maintenance.php:489
Maintenance\addDescription
addDescription( $text)
Set the description text.
Definition: Maintenance.php:329
UppercaseTitlesForUnicodeTransition\$suffix
string null $suffix
Definition: uppercaseTitlesForUnicodeTransition.php:64
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1896
UppercaseTitlesForUnicodeTransition\__construct
__construct()
Default constructor.
Definition: uppercaseTitlesForUnicodeTransition.php:72
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:595
Maintenance
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:59
Wikimedia\Rdbms\IDatabase\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
$res
$res
Definition: testCompression.php:57
$base
$base
Definition: generateLocalAutoload.php:11
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
UppercaseTitlesForUnicodeTransition\getLikeBatches
getLikeBatches(IDatabase $db, $field, $batchSize=100)
Get batched LIKE conditions from the charmap.
Definition: uppercaseTitlesForUnicodeTransition.php:209
UppercaseTitlesForUnicodeTransition\$reason
string $reason
Definition: uppercaseTitlesForUnicodeTransition.php:52
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
UppercaseTitlesForUnicodeTransition\$seenUsers
array $seenUsers
Definition: uppercaseTitlesForUnicodeTransition.php:58
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
User\newSystemUser
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition: User.php:803
Wikimedia\Rdbms\IDatabase\update
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:1057
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1066
Maintenance\$lastReplicationWait
float $lastReplicationWait
UNIX timestamp.
Definition: Maintenance.php:134
Maintenance\addOption
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
Definition: Maintenance.php:249
UppercaseTitlesForUnicodeTransition\$user
User $user
Definition: uppercaseTitlesForUnicodeTransition.php:49
UppercaseTitlesForUnicodeTransition\doUpdate
doUpdate(IDatabase $db, $op, $table, $nsField, $titleField, $row)
Directly update a database row.
Definition: uppercaseTitlesForUnicodeTransition.php:530
$title
$title
Definition: testCompression.php:38
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:648
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Title\canExist
canExist()
Can this title represent a page in the wiki's database?
Definition: Title.php:1227
UppercaseTitlesForUnicodeTransition\isUserPage
isUserPage(IDatabase $db, $ns, $title)
Check if a ns+title is a registered user's page.
Definition: uppercaseTitlesForUnicodeTransition.php:279
UppercaseTitlesForUnicodeTransition\$namespaces
array null $namespaces
Definition: uppercaseTitlesForUnicodeTransition.php:61
$maintClass
$maintClass
Definition: uppercaseTitlesForUnicodeTransition.php:726
Wikimedia\Rdbms\IDatabase\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
UppercaseTitlesForUnicodeTransition\processTable
processTable(IDatabase $db, $op, $table, $nsField, $titleField, $pkFields)
Rename entries in other tables.
Definition: uppercaseTitlesForUnicodeTransition.php:582
UppercaseTitlesForUnicodeTransition\$tables
string[] null $tables
Definition: uppercaseTitlesForUnicodeTransition.php:70
Maintenance\getDB
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
Definition: Maintenance.php:1375
Wikimedia\Rdbms\IDatabase\anyString
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
NS_USER
const NS_USER
Definition: Defines.php:66
UppercaseTitlesForUnicodeTransition\mungeTitle
mungeTitle(IDatabase $db, Title $oldTitle, Title &$newTitle)
Munge a target title, if necessary.
Definition: uppercaseTitlesForUnicodeTransition.php:304
UppercaseTitlesForUnicodeTransition\$charmap
array $charmap
Definition: uppercaseTitlesForUnicodeTransition.php:46
Title
Represents a title within MediaWiki.
Definition: Title.php:47
Maintenance\getOption
getOption( $name, $default=null)
Get an option, or return the default.
Definition: Maintenance.php:286
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
UppercaseTitlesForUnicodeTransition\$run
bool $run
Definition: uppercaseTitlesForUnicodeTransition.php:43
Maintenance\getBatchSize
getBatchSize()
Returns batch size.
Definition: Maintenance.php:368
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
Title\exists
exists( $flags=0)
Check if page exists.
Definition: Title.php:3496
Wikimedia\Rdbms\IDatabase\select
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Maintenance\error
error( $err, $die=0)
Throw an error to the user.
Definition: Maintenance.php:464
Maintenance\output
output( $out, $channel=null)
Throw some output to the user.
Definition: Maintenance.php:435
UppercaseTitlesForUnicodeTransition\doMove
doMove(IDatabase $db, $ns, $title)
Use MovePage to move a title.
Definition: uppercaseTitlesForUnicodeTransition.php:371
UppercaseTitlesForUnicodeTransition\$prefix
string null $prefix
Definition: uppercaseTitlesForUnicodeTransition.php:64
NS_FILE
const NS_FILE
Definition: Defines.php:70
UppercaseTitlesForUnicodeTransition
Maintenance script to rename titles affected by changes to Unicode (or otherwise to Language::ucfirst...
Definition: uppercaseTitlesForUnicodeTransition.php:36
Wikimedia\Rdbms\IDatabase\buildLike
buildLike( $param,... $params)
LIKE statement wrapper.
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
UppercaseTitlesForUnicodeTransition\processUsers
processUsers(IDatabase $db)
List users needing renaming.
Definition: uppercaseTitlesForUnicodeTransition.php:670
Wikimedia\Rdbms\IDatabase\makeList
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Title\getText
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:1039
UppercaseTitlesForUnicodeTransition\shouldDelete
shouldDelete(IDatabase $db, Title $oldTitle, Title $newTitle)
Determine whether the old title should be deleted.
Definition: uppercaseTitlesForUnicodeTransition.php:480
UppercaseTitlesForUnicodeTransition\execute
execute()
Do the actual work.
Definition: uppercaseTitlesForUnicodeTransition.php:113
UppercaseTitlesForUnicodeTransition\$tags
string[] $tags
Definition: uppercaseTitlesForUnicodeTransition.php:55
User\MAINTENANCE_SCRIPT_USER
const MAINTENANCE_SCRIPT_USER
Username used for various maintenance scripts.
Definition: User.php:113
Maintenance\setBatchSize
setBatchSize( $s=0)
Definition: Maintenance.php:375