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