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