MediaWiki master
ApiQueryLogEvents.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
28use LogPage;
44
51
52 private CommentStore $commentStore;
53 private CommentFormatter $commentFormatter;
54 private NameTableStore $changeTagDefStore;
55 private ChangeTagsStore $changeTagsStore;
56 private UserNameUtils $userNameUtils;
57 private LogFormatterFactory $logFormatterFactory;
58
60 private $formattedComments;
61
62 public function __construct(
63 ApiQuery $query,
64 string $moduleName,
65 CommentStore $commentStore,
66 RowCommentFormatter $commentFormatter,
67 NameTableStore $changeTagDefStore,
68 ChangeTagsStore $changeTagsStore,
69 UserNameUtils $userNameUtils,
70 LogFormatterFactory $logFormatterFactory
71 ) {
72 parent::__construct( $query, $moduleName, 'le' );
73 $this->commentStore = $commentStore;
74 $this->commentFormatter = $commentFormatter;
75 $this->changeTagDefStore = $changeTagDefStore;
76 $this->changeTagsStore = $changeTagsStore;
77 $this->userNameUtils = $userNameUtils;
78 $this->logFormatterFactory = $logFormatterFactory;
79 }
80
81 private bool $fld_ids = false;
82 private bool $fld_title = false;
83 private bool $fld_type = false;
84 private bool $fld_user = false;
85 private bool $fld_userid = false;
86 private bool $fld_timestamp = false;
87 private bool $fld_comment = false;
88 private bool $fld_parsedcomment = false;
89 private bool $fld_details = false;
90 private bool $fld_tags = false;
91
92 public function execute() {
94 $db = $this->getDB();
95 $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' );
96
97 $prop = array_fill_keys( $params['prop'], true );
98
99 $this->fld_ids = isset( $prop['ids'] );
100 $this->fld_title = isset( $prop['title'] );
101 $this->fld_type = isset( $prop['type'] );
102 $this->fld_user = isset( $prop['user'] );
103 $this->fld_userid = isset( $prop['userid'] );
104 $this->fld_timestamp = isset( $prop['timestamp'] );
105 $this->fld_comment = isset( $prop['comment'] );
106 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
107 $this->fld_details = isset( $prop['details'] );
108 $this->fld_tags = isset( $prop['tags'] );
109
110 $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getAuthority() );
111 if ( $hideLogs !== false ) {
112 $this->addWhere( $hideLogs );
113 }
114
115 $this->addTables( 'logging' );
116
117 $this->addFields( [
118 'log_id',
119 'log_type',
120 'log_action',
121 'log_timestamp',
122 'log_deleted',
123 ] );
124
125 $user = $params['user'];
126 if ( $this->fld_user || $this->fld_userid || $user !== null ) {
127 $this->addTables( 'actor' );
128 $this->addJoinConds( [
129 'actor' => [ 'JOIN', 'actor_id=log_actor' ],
130 ] );
131 $this->addFieldsIf( [ 'actor_name', 'actor_user' ], $this->fld_user );
132 $this->addFieldsIf( 'actor_user', $this->fld_userid );
133 if ( $user !== null ) {
134 $this->addWhereFld( 'actor_name', $user );
135 }
136 }
137
138 if ( $this->fld_ids ) {
139 $this->addTables( 'page' );
140 $this->addJoinConds( [
141 'page' => [ 'LEFT JOIN',
142 [ 'log_namespace=page_namespace',
143 'log_title=page_title' ] ]
144 ] );
145 // log_page is the page_id saved at log time, whereas page_id is from a
146 // join at query time. This leads to different results in various
147 // scenarios, e.g. deletion, recreation.
148 $this->addFields( [ 'page_id', 'log_page' ] );
149 }
150 $this->addFieldsIf(
151 [ 'log_namespace', 'log_title' ],
152 $this->fld_title || $this->fld_parsedcomment
153 );
154 $this->addFieldsIf( 'log_params', $this->fld_details || $this->fld_ids );
155
156 if ( $this->fld_comment || $this->fld_parsedcomment ) {
157 $commentQuery = $this->commentStore->getJoin( 'log_comment' );
158 $this->addTables( $commentQuery['tables'] );
159 $this->addFields( $commentQuery['fields'] );
160 $this->addJoinConds( $commentQuery['joins'] );
161 }
162
163 if ( $this->fld_tags ) {
164 $this->addFields( [
165 'ts_tags' => $this->changeTagsStore->makeTagSummarySubquery( 'logging' )
166 ] );
167 }
168
169 if ( $params['tag'] !== null ) {
170 $this->addTables( 'change_tag' );
171 $this->addJoinConds( [ 'change_tag' => [ 'JOIN',
172 [ 'log_id=ct_log_id' ] ] ] );
173 try {
174 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
175 } catch ( NameTableAccessException $exception ) {
176 // Return nothing.
177 $this->addWhere( '1=0' );
178 }
179 }
180
181 if ( $params['action'] !== null ) {
182 // Do validation of action param, list of allowed actions can contains wildcards
183 // Allow the param, when the actions is in the list or a wildcard version is listed.
184 $logAction = $params['action'];
185 if ( !str_contains( $logAction, '/' ) ) {
186 // all items in the list have a slash
187 $valid = false;
188 } else {
189 $logActions = array_fill_keys( $this->getAllowedLogActions(), true );
190 [ $type, $action ] = explode( '/', $logAction, 2 );
191 $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] );
192 }
193
194 if ( !$valid ) {
195 $encParamName = $this->encodeParamName( 'action' );
196 $this->dieWithError(
197 [ 'apierror-unrecognizedvalue', $encParamName, wfEscapeWikiText( $logAction ) ],
198 "unknown_$encParamName"
199 );
200 }
201
202 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable T240141
203 $this->addWhereFld( 'log_type', $type );
204 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable T240141
205 $this->addWhereFld( 'log_action', $action );
206 } elseif ( $params['type'] !== null ) {
207 $this->addWhereFld( 'log_type', $params['type'] );
208 }
209
211 'log_timestamp',
212 $params['dir'],
213 $params['start'],
214 $params['end']
215 );
216 // Include in ORDER BY for uniqueness
217 $this->addWhereRange( 'log_id', $params['dir'], null, null );
218
219 if ( $params['continue'] !== null ) {
220 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
221 $op = ( $params['dir'] === 'newer' ? '>=' : '<=' );
222 $this->addWhere( $db->buildComparison( $op, [
223 'log_timestamp' => $db->timestamp( $cont[0] ),
224 'log_id' => $cont[1],
225 ] ) );
226 }
227
228 $limit = $params['limit'];
229 $this->addOption( 'LIMIT', $limit + 1 );
230
231 $title = $params['title'];
232 if ( $title !== null ) {
233 $titleObj = Title::newFromText( $title );
234 if ( $titleObj === null || $titleObj->isExternal() ) {
235 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
236 }
237 $this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
238 $this->addWhereFld( 'log_title', $titleObj->getDBkey() );
239 }
240
241 if ( $params['namespace'] !== null ) {
242 $this->addWhereFld( 'log_namespace', $params['namespace'] );
243 }
244
245 $prefix = $params['prefix'];
246
247 if ( $prefix !== null ) {
248 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
249 $this->dieWithError( 'apierror-prefixsearchdisabled' );
250 }
251
252 $title = Title::newFromText( $prefix );
253 if ( $title === null || $title->isExternal() ) {
254 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $prefix ) ] );
255 }
256 $this->addWhereFld( 'log_namespace', $title->getNamespace() );
257 $this->addWhere(
258 $db->expr( 'log_title', IExpression::LIKE, new LikeValue( $title->getDBkey(), $db->anyString() ) )
259 );
260 }
261
262 // Paranoia: avoid brute force searches (T19342)
263 if ( $params['namespace'] !== null || $title !== null || $user !== null ) {
264 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
265 $titleBits = LogPage::DELETED_ACTION;
266 $userBits = LogPage::DELETED_USER;
267 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
268 $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
269 $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
270 } else {
271 $titleBits = 0;
272 $userBits = 0;
273 }
274 if ( ( $params['namespace'] !== null || $title !== null ) && $titleBits ) {
275 $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" );
276 }
277 if ( $user !== null && $userBits ) {
278 $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" );
279 }
280 }
281
282 // T220999: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` before
283 // `logging` and filesorting is somehow better than querying $limit+1 rows from `logging`.
284 // Tell it not to reorder the query. But not when `letag` was used, as it seems as likely
285 // to be harmed as helped in that case.
286 // If "user" was specified, it's obviously correct to query actor first (T282122)
287 if ( $params['tag'] === null && $user === null ) {
288 $this->addOption( 'STRAIGHT_JOIN' );
289 }
290
291 $this->addOption(
292 'MAX_EXECUTION_TIME',
294 );
295
296 $count = 0;
297 $res = $this->select( __METHOD__ );
298
299 if ( $this->fld_title ) {
300 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__, 'log' );
301 }
302 if ( $this->fld_parsedcomment ) {
303 $this->formattedComments = $this->commentFormatter->formatItems(
304 $this->commentFormatter->rows( $res )
305 ->commentKey( 'log_comment' )
306 ->indexField( 'log_id' )
307 ->namespaceField( 'log_namespace' )
308 ->titleField( 'log_title' )
309 );
310 }
311
312 $result = $this->getResult();
313 foreach ( $res as $row ) {
314 if ( ++$count > $limit ) {
315 // We've reached the one extra which shows that there are
316 // additional pages to be had. Stop here...
317 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
318 break;
319 }
320
321 $vals = $this->extractRowInfo( $row );
322 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
323 if ( !$fit ) {
324 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
325 break;
326 }
327 }
328 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
329 }
330
331 private function extractRowInfo( $row ) {
332 $logEntry = DatabaseLogEntry::newFromRow( $row );
333 $vals = [
334 ApiResult::META_TYPE => 'assoc',
335 ];
336 $anyHidden = false;
337
338 if ( $this->fld_ids ) {
339 $vals['logid'] = (int)$row->log_id;
340 }
341
342 if ( $this->fld_title ) {
343 $title = Title::makeTitle( $row->log_namespace, $row->log_title );
344 }
345
346 $authority = $this->getAuthority();
347 if ( $this->fld_title || $this->fld_ids || ( $this->fld_details && $row->log_params !== '' ) ) {
348 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
349 $vals['actionhidden'] = true;
350 $anyHidden = true;
351 }
352 if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $authority ) ) {
353 if ( $this->fld_title ) {
354 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
355 // title is set when used
356 ApiQueryBase::addTitleInfo( $vals, $title );
357 }
358 if ( $this->fld_ids ) {
359 $vals['pageid'] = (int)$row->page_id;
360 $vals['logpage'] = (int)$row->log_page;
361 $revId = $logEntry->getAssociatedRevId();
362 if ( $revId ) {
363 $vals['revid'] = (int)$revId;
364 }
365 }
366 if ( $this->fld_details ) {
367 $vals['params'] = $this->logFormatterFactory->newFromEntry( $logEntry )->formatParametersForApi();
368 }
369 }
370 }
371
372 if ( $this->fld_type ) {
373 $vals['type'] = $row->log_type;
374 $vals['action'] = $row->log_action;
375 }
376
377 if ( $this->fld_user || $this->fld_userid ) {
378 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
379 $vals['userhidden'] = true;
380 $anyHidden = true;
381 }
382 if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $authority ) ) {
383 if ( $this->fld_user ) {
384 $vals['user'] = $row->actor_name;
385 }
386 if ( $this->fld_userid ) {
387 $vals['userid'] = (int)$row->actor_user;
388 }
389
390 if ( isset( $vals['user'] ) && $this->userNameUtils->isTemp( $vals['user'] ) ) {
391 $vals['temp'] = true;
392 }
393
394 if ( !$row->actor_user ) {
395 $vals['anon'] = true;
396 }
397 }
398 }
399 if ( $this->fld_timestamp ) {
400 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp );
401 }
402
403 if ( $this->fld_comment || $this->fld_parsedcomment ) {
404 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
405 $vals['commenthidden'] = true;
406 $anyHidden = true;
407 }
408 if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $authority ) ) {
409 if ( $this->fld_comment ) {
410 $vals['comment'] = $this->commentStore->getComment( 'log_comment', $row )->text;
411 }
412
413 if ( $this->fld_parsedcomment ) {
414 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
415 $vals['parsedcomment'] = $this->formattedComments[$row->log_id];
416 }
417 }
418 }
419
420 if ( $this->fld_tags ) {
421 if ( $row->ts_tags ) {
422 $tags = explode( ',', $row->ts_tags );
423 ApiResult::setIndexedTagName( $tags, 'tag' );
424 $vals['tags'] = $tags;
425 } else {
426 $vals['tags'] = [];
427 }
428 }
429
430 if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
431 $vals['suppressed'] = true;
432 }
433
434 return $vals;
435 }
436
440 private function getAllowedLogActions() {
441 $config = $this->getConfig();
442 return array_keys( array_merge(
443 $config->get( MainConfigNames::LogActions ),
445 ) );
446 }
447
448 public function getCacheMode( $params ) {
449 if ( $this->userCanSeeRevDel() ) {
450 return 'private';
451 }
452 if ( $params['prop'] !== null && in_array( 'parsedcomment', $params['prop'] ) ) {
453 // MediaWiki\CommentFormatter\CommentFormatter::formatItems() calls wfMessage() among other things
454 return 'anon-public-user-private';
455 } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getAuthority() )
456 === LogEventsList::getExcludeClause( $this->getDB(), 'public' )
457 ) { // Output can only contain public data.
458 return 'public';
459 } else {
460 return 'anon-public-user-private';
461 }
462 }
463
464 public function getAllowedParams( $flags = 0 ) {
465 $config = $this->getConfig();
466 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
467 $logActions = $this->getAllowedLogActions();
468 sort( $logActions );
469 } else {
470 $logActions = null;
471 }
472 $ret = [
473 'prop' => [
474 ParamValidator::PARAM_ISMULTI => true,
475 ParamValidator::PARAM_DEFAULT => 'ids|title|type|user|timestamp|comment|details',
476 ParamValidator::PARAM_TYPE => [
477 'ids',
478 'title',
479 'type',
480 'user',
481 'userid',
482 'timestamp',
483 'comment',
484 'parsedcomment',
485 'details',
486 'tags'
487 ],
489 ],
490 'type' => [
491 ParamValidator::PARAM_TYPE => LogPage::validTypes(),
492 ],
493 'action' => [
494 // validation on request is done in execute()
495 ParamValidator::PARAM_TYPE => $logActions
496 ],
497 'start' => [
498 ParamValidator::PARAM_TYPE => 'timestamp'
499 ],
500 'end' => [
501 ParamValidator::PARAM_TYPE => 'timestamp'
502 ],
503 'dir' => [
504 ParamValidator::PARAM_DEFAULT => 'older',
505 ParamValidator::PARAM_TYPE => [
506 'newer',
507 'older'
508 ],
509 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
511 'newer' => 'api-help-paramvalue-direction-newer',
512 'older' => 'api-help-paramvalue-direction-older',
513 ],
514 ],
515 'user' => [
516 ParamValidator::PARAM_TYPE => 'user',
517 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
518 ],
519 'title' => null,
520 'namespace' => [
521 ParamValidator::PARAM_TYPE => 'namespace',
522 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ NS_MEDIA, NS_SPECIAL ],
523 ],
524 'prefix' => [],
525 'tag' => null,
526 'limit' => [
527 ParamValidator::PARAM_DEFAULT => 10,
528 ParamValidator::PARAM_TYPE => 'limit',
529 IntegerDef::PARAM_MIN => 1,
530 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
531 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
532 ],
533 'continue' => [
534 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
535 ],
536 ];
537
538 if ( $config->get( MainConfigNames::MiserMode ) ) {
539 $ret['prefix'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
540 }
541
542 return $ret;
543 }
544
545 protected function getExamplesMessages() {
546 return [
547 'action=query&list=logevents'
548 => 'apihelp-query+logevents-example-simple',
549 ];
550 }
551
552 public function getHelpUrls() {
553 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Logevents';
554 }
555}
556
558class_alias( ApiQueryLogEvents::class, 'ApiQueryLogEvents' );
const NS_SPECIAL
Definition Defines.php:54
const NS_MEDIA
Definition Defines.php:53
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
array $params
The job parameters.
A value class to process existing log entries.
static newFromRow( $row)
Constructs new LogEntry from database result row.
Class to simplify the use of log pages.
Definition LogPage.php:46
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1531
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:571
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1722
getResult()
Get the result object.
Definition ApiBase.php:710
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:224
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:1025
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:251
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:829
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When this is set, the result could take longer to generate,...
Definition ApiBase.php:262
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:249
This is a base class for all Query modules.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
getDB()
Get the Query database connection (read-only)
select( $method, $extraQuery=[], ?array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
userCanSeeRevDel()
Check whether the current user has permission to view revision-deleted fields.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addFields( $value)
Add a set of fields to select to the internal array.
addWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, and an ORDER BY clause to sort in the right direction.
Query action to List the log events, with optional filtering by various parameters.
getHelpUrls()
Return links to more detailed help pages about the module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getExamplesMessages()
Returns usage examples for this module.
__construct(ApiQuery $query, string $moduleName, CommentStore $commentStore, RowCommentFormatter $commentFormatter, NameTableStore $changeTagDefStore, ChangeTagsStore $changeTagsStore, UserNameUtils $userNameUtils, LogFormatterFactory $logFormatterFactory)
getCacheMode( $params)
Get the cache mode for the data generated by this module.
This is the main query class.
Definition ApiQuery.php:48
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
const META_TYPE
Key for the 'type' metadata item.
Read-write access to the change_tags table.
This is the main service interface for converting single-line comments from various DB comment fields...
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
Handle database storage of comments such as edit summaries and log reasons.
A class containing constants representing the names of configuration variables.
const MaxExecutionTimeForExpensiveQueries
Name constant for the MaxExecutionTimeForExpensiveQueries setting, for use with Config::get()
const LogActionsHandlers
Name constant for the LogActionsHandlers setting, for use with Config::get()
const LogActions
Name constant for the LogActions setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
Type definition for namespace types.
Type definition for user types.
Definition UserDef.php:27
Exception representing a failure to look up a row from a name table.
Represents a title within MediaWiki.
Definition Title.php:78
UserNameUtils service.
Service for formatting and validating API parameters.
Type definition for integer types.
Content of like value.
Definition LikeValue.php:14