MediaWiki 1.43.1
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 UserNameUtils $userNameUtils;
56 private LogFormatterFactory $logFormatterFactory;
57
59 private $formattedComments;
60
61 public function __construct(
62 ApiQuery $query,
63 string $moduleName,
64 CommentStore $commentStore,
65 RowCommentFormatter $commentFormatter,
66 NameTableStore $changeTagDefStore,
67 UserNameUtils $userNameUtils,
68 LogFormatterFactory $logFormatterFactory
69 ) {
70 parent::__construct( $query, $moduleName, 'le' );
71 $this->commentStore = $commentStore;
72 $this->commentFormatter = $commentFormatter;
73 $this->changeTagDefStore = $changeTagDefStore;
74 $this->userNameUtils = $userNameUtils;
75 $this->logFormatterFactory = $logFormatterFactory;
76 }
77
78 private bool $fld_ids = false;
79 private bool $fld_title = false;
80 private bool $fld_type = false;
81 private bool $fld_user = false;
82 private bool $fld_userid = false;
83 private bool $fld_timestamp = false;
84 private bool $fld_comment = false;
85 private bool $fld_parsedcomment = false;
86 private bool $fld_details = false;
87 private bool $fld_tags = false;
88
89 public function execute() {
91 $db = $this->getDB();
92 $this->requireMaxOneParameter( $params, 'title', 'prefix', 'namespace' );
93
94 $prop = array_fill_keys( $params['prop'], true );
95
96 $this->fld_ids = isset( $prop['ids'] );
97 $this->fld_title = isset( $prop['title'] );
98 $this->fld_type = isset( $prop['type'] );
99 $this->fld_user = isset( $prop['user'] );
100 $this->fld_userid = isset( $prop['userid'] );
101 $this->fld_timestamp = isset( $prop['timestamp'] );
102 $this->fld_comment = isset( $prop['comment'] );
103 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
104 $this->fld_details = isset( $prop['details'] );
105 $this->fld_tags = isset( $prop['tags'] );
106
107 $hideLogs = LogEventsList::getExcludeClause( $db, 'user', $this->getAuthority() );
108 if ( $hideLogs !== false ) {
109 $this->addWhere( $hideLogs );
110 }
111
112 $this->addTables( 'logging' );
113
114 $this->addFields( [
115 'log_id',
116 'log_type',
117 'log_action',
118 'log_timestamp',
119 'log_deleted',
120 ] );
121
122 $user = $params['user'];
123 if ( $this->fld_user || $this->fld_userid || $user !== null ) {
124 $this->addTables( 'actor' );
125 $this->addJoinConds( [
126 'actor' => [ 'JOIN', 'actor_id=log_actor' ],
127 ] );
128 $this->addFieldsIf( [ 'actor_name', 'actor_user' ], $this->fld_user );
129 $this->addFieldsIf( 'actor_user', $this->fld_userid );
130 if ( $user !== null ) {
131 $this->addWhereFld( 'actor_name', $user );
132 }
133 }
134
135 if ( $this->fld_ids ) {
136 $this->addTables( 'page' );
137 $this->addJoinConds( [
138 'page' => [ 'LEFT JOIN',
139 [ 'log_namespace=page_namespace',
140 'log_title=page_title' ] ]
141 ] );
142 // log_page is the page_id saved at log time, whereas page_id is from a
143 // join at query time. This leads to different results in various
144 // scenarios, e.g. deletion, recreation.
145 $this->addFields( [ 'page_id', 'log_page' ] );
146 }
147 $this->addFieldsIf(
148 [ 'log_namespace', 'log_title' ],
149 $this->fld_title || $this->fld_parsedcomment
150 );
151 $this->addFieldsIf( 'log_params', $this->fld_details || $this->fld_ids );
152
153 if ( $this->fld_comment || $this->fld_parsedcomment ) {
154 $commentQuery = $this->commentStore->getJoin( 'log_comment' );
155 $this->addTables( $commentQuery['tables'] );
156 $this->addFields( $commentQuery['fields'] );
157 $this->addJoinConds( $commentQuery['joins'] );
158 }
159
160 if ( $this->fld_tags ) {
161 $this->addFields( [
162 'ts_tags' => MediaWikiServices::getInstance()->getChangeTagsStore()
163 ->makeTagSummarySubquery( 'logging' )
164 ] );
165 }
166
167 if ( $params['tag'] !== null ) {
168 $this->addTables( 'change_tag' );
169 $this->addJoinConds( [ 'change_tag' => [ 'JOIN',
170 [ 'log_id=ct_log_id' ] ] ] );
171 try {
172 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
173 } catch ( NameTableAccessException $exception ) {
174 // Return nothing.
175 $this->addWhere( '1=0' );
176 }
177 }
178
179 if ( $params['action'] !== null ) {
180 // Do validation of action param, list of allowed actions can contains wildcards
181 // Allow the param, when the actions is in the list or a wildcard version is listed.
182 $logAction = $params['action'];
183 if ( !str_contains( $logAction, '/' ) ) {
184 // all items in the list have a slash
185 $valid = false;
186 } else {
187 $logActions = array_fill_keys( $this->getAllowedLogActions(), true );
188 [ $type, $action ] = explode( '/', $logAction, 2 );
189 $valid = isset( $logActions[$logAction] ) || isset( $logActions[$type . '/*'] );
190 }
191
192 if ( !$valid ) {
193 $encParamName = $this->encodeParamName( 'action' );
194 $this->dieWithError(
195 [ 'apierror-unrecognizedvalue', $encParamName, wfEscapeWikiText( $logAction ) ],
196 "unknown_$encParamName"
197 );
198 }
199
200 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable T240141
201 $this->addWhereFld( 'log_type', $type );
202 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable T240141
203 $this->addWhereFld( 'log_action', $action );
204 } elseif ( $params['type'] !== null ) {
205 $this->addWhereFld( 'log_type', $params['type'] );
206 }
207
209 'log_timestamp',
210 $params['dir'],
211 $params['start'],
212 $params['end']
213 );
214 // Include in ORDER BY for uniqueness
215 $this->addWhereRange( 'log_id', $params['dir'], null, null );
216
217 if ( $params['continue'] !== null ) {
218 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
219 $op = ( $params['dir'] === 'newer' ? '>=' : '<=' );
220 $this->addWhere( $db->buildComparison( $op, [
221 'log_timestamp' => $db->timestamp( $cont[0] ),
222 'log_id' => $cont[1],
223 ] ) );
224 }
225
226 $limit = $params['limit'];
227 $this->addOption( 'LIMIT', $limit + 1 );
228
229 $title = $params['title'];
230 if ( $title !== null ) {
231 $titleObj = Title::newFromText( $title );
232 if ( $titleObj === null || $titleObj->isExternal() ) {
233 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
234 }
235 $this->addWhereFld( 'log_namespace', $titleObj->getNamespace() );
236 $this->addWhereFld( 'log_title', $titleObj->getDBkey() );
237 }
238
239 if ( $params['namespace'] !== null ) {
240 $this->addWhereFld( 'log_namespace', $params['namespace'] );
241 }
242
243 $prefix = $params['prefix'];
244
245 if ( $prefix !== null ) {
246 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
247 $this->dieWithError( 'apierror-prefixsearchdisabled' );
248 }
249
250 $title = Title::newFromText( $prefix );
251 if ( $title === null || $title->isExternal() ) {
252 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $prefix ) ] );
253 }
254 $this->addWhereFld( 'log_namespace', $title->getNamespace() );
255 $this->addWhere(
256 $db->expr( 'log_title', IExpression::LIKE, new LikeValue( $title->getDBkey(), $db->anyString() ) )
257 );
258 }
259
260 // Paranoia: avoid brute force searches (T19342)
261 if ( $params['namespace'] !== null || $title !== null || $user !== null ) {
262 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
263 $titleBits = LogPage::DELETED_ACTION;
264 $userBits = LogPage::DELETED_USER;
265 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
266 $titleBits = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
267 $userBits = LogPage::DELETED_USER | LogPage::DELETED_RESTRICTED;
268 } else {
269 $titleBits = 0;
270 $userBits = 0;
271 }
272 if ( ( $params['namespace'] !== null || $title !== null ) && $titleBits ) {
273 $this->addWhere( $db->bitAnd( 'log_deleted', $titleBits ) . " != $titleBits" );
274 }
275 if ( $user !== null && $userBits ) {
276 $this->addWhere( $db->bitAnd( 'log_deleted', $userBits ) . " != $userBits" );
277 }
278 }
279
280 // T220999: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` before
281 // `logging` and filesorting is somehow better than querying $limit+1 rows from `logging`.
282 // Tell it not to reorder the query. But not when `letag` was used, as it seems as likely
283 // to be harmed as helped in that case.
284 // If "user" was specified, it's obviously correct to query actor first (T282122)
285 if ( $params['tag'] === null && $user === null ) {
286 $this->addOption( 'STRAIGHT_JOIN' );
287 }
288
289 $this->addOption(
290 'MAX_EXECUTION_TIME',
292 );
293
294 $count = 0;
295 $res = $this->select( __METHOD__ );
296
297 if ( $this->fld_title ) {
298 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__, 'log' );
299 }
300 if ( $this->fld_parsedcomment ) {
301 $this->formattedComments = $this->commentFormatter->formatItems(
302 $this->commentFormatter->rows( $res )
303 ->commentKey( 'log_comment' )
304 ->indexField( 'log_id' )
305 ->namespaceField( 'log_namespace' )
306 ->titleField( 'log_title' )
307 );
308 }
309
310 $result = $this->getResult();
311 foreach ( $res as $row ) {
312 if ( ++$count > $limit ) {
313 // We've reached the one extra which shows that there are
314 // additional pages to be had. Stop here...
315 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
316 break;
317 }
318
319 $vals = $this->extractRowInfo( $row );
320 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
321 if ( !$fit ) {
322 $this->setContinueEnumParameter( 'continue', "$row->log_timestamp|$row->log_id" );
323 break;
324 }
325 }
326 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
327 }
328
329 private function extractRowInfo( $row ) {
330 $logEntry = DatabaseLogEntry::newFromRow( $row );
331 $vals = [
332 ApiResult::META_TYPE => 'assoc',
333 ];
334 $anyHidden = false;
335
336 if ( $this->fld_ids ) {
337 $vals['logid'] = (int)$row->log_id;
338 }
339
340 if ( $this->fld_title ) {
341 $title = Title::makeTitle( $row->log_namespace, $row->log_title );
342 }
343
344 $authority = $this->getAuthority();
345 if ( $this->fld_title || $this->fld_ids || ( $this->fld_details && $row->log_params !== '' ) ) {
346 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
347 $vals['actionhidden'] = true;
348 $anyHidden = true;
349 }
350 if ( LogEventsList::userCan( $row, LogPage::DELETED_ACTION, $authority ) ) {
351 if ( $this->fld_title ) {
352 // @phan-suppress-next-next-line PhanTypeMismatchArgumentNullable,PhanPossiblyUndeclaredVariable
353 // title is set when used
354 ApiQueryBase::addTitleInfo( $vals, $title );
355 }
356 if ( $this->fld_ids ) {
357 $vals['pageid'] = (int)$row->page_id;
358 $vals['logpage'] = (int)$row->log_page;
359 $revId = $logEntry->getAssociatedRevId();
360 if ( $revId ) {
361 $vals['revid'] = (int)$revId;
362 }
363 }
364 if ( $this->fld_details ) {
365 $vals['params'] = $this->logFormatterFactory->newFromEntry( $logEntry )->formatParametersForApi();
366 }
367 }
368 }
369
370 if ( $this->fld_type ) {
371 $vals['type'] = $row->log_type;
372 $vals['action'] = $row->log_action;
373 }
374
375 if ( $this->fld_user || $this->fld_userid ) {
376 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
377 $vals['userhidden'] = true;
378 $anyHidden = true;
379 }
380 if ( LogEventsList::userCan( $row, LogPage::DELETED_USER, $authority ) ) {
381 if ( $this->fld_user ) {
382 $vals['user'] = $row->actor_name;
383 }
384 if ( $this->fld_userid ) {
385 $vals['userid'] = (int)$row->actor_user;
386 }
387
388 if ( isset( $vals['user'] ) && $this->userNameUtils->isTemp( $vals['user'] ) ) {
389 $vals['temp'] = true;
390 }
391
392 if ( !$row->actor_user ) {
393 $vals['anon'] = true;
394 }
395 }
396 }
397 if ( $this->fld_timestamp ) {
398 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->log_timestamp );
399 }
400
401 if ( $this->fld_comment || $this->fld_parsedcomment ) {
402 if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
403 $vals['commenthidden'] = true;
404 $anyHidden = true;
405 }
406 if ( LogEventsList::userCan( $row, LogPage::DELETED_COMMENT, $authority ) ) {
407 if ( $this->fld_comment ) {
408 $vals['comment'] = $this->commentStore->getComment( 'log_comment', $row )->text;
409 }
410
411 if ( $this->fld_parsedcomment ) {
412 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
413 $vals['parsedcomment'] = $this->formattedComments[$row->log_id];
414 }
415 }
416 }
417
418 if ( $this->fld_tags ) {
419 if ( $row->ts_tags ) {
420 $tags = explode( ',', $row->ts_tags );
421 ApiResult::setIndexedTagName( $tags, 'tag' );
422 $vals['tags'] = $tags;
423 } else {
424 $vals['tags'] = [];
425 }
426 }
427
428 if ( $anyHidden && LogEventsList::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) {
429 $vals['suppressed'] = true;
430 }
431
432 return $vals;
433 }
434
438 private function getAllowedLogActions() {
439 $config = $this->getConfig();
440 return array_keys( array_merge(
441 $config->get( MainConfigNames::LogActions ),
443 ) );
444 }
445
446 public function getCacheMode( $params ) {
447 if ( $this->userCanSeeRevDel() ) {
448 return 'private';
449 }
450 if ( $params['prop'] !== null && in_array( 'parsedcomment', $params['prop'] ) ) {
451 // MediaWiki\CommentFormatter\CommentFormatter::formatItems() calls wfMessage() among other things
452 return 'anon-public-user-private';
453 } elseif ( LogEventsList::getExcludeClause( $this->getDB(), 'user', $this->getAuthority() )
454 === LogEventsList::getExcludeClause( $this->getDB(), 'public' )
455 ) { // Output can only contain public data.
456 return 'public';
457 } else {
458 return 'anon-public-user-private';
459 }
460 }
461
462 public function getAllowedParams( $flags = 0 ) {
463 $config = $this->getConfig();
464 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
465 $logActions = $this->getAllowedLogActions();
466 sort( $logActions );
467 } else {
468 $logActions = null;
469 }
470 $ret = [
471 'prop' => [
472 ParamValidator::PARAM_ISMULTI => true,
473 ParamValidator::PARAM_DEFAULT => 'ids|title|type|user|timestamp|comment|details',
474 ParamValidator::PARAM_TYPE => [
475 'ids',
476 'title',
477 'type',
478 'user',
479 'userid',
480 'timestamp',
481 'comment',
482 'parsedcomment',
483 'details',
484 'tags'
485 ],
487 ],
488 'type' => [
489 ParamValidator::PARAM_TYPE => LogPage::validTypes(),
490 ],
491 'action' => [
492 // validation on request is done in execute()
493 ParamValidator::PARAM_TYPE => $logActions
494 ],
495 'start' => [
496 ParamValidator::PARAM_TYPE => 'timestamp'
497 ],
498 'end' => [
499 ParamValidator::PARAM_TYPE => 'timestamp'
500 ],
501 'dir' => [
502 ParamValidator::PARAM_DEFAULT => 'older',
503 ParamValidator::PARAM_TYPE => [
504 'newer',
505 'older'
506 ],
507 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
509 'newer' => 'api-help-paramvalue-direction-newer',
510 'older' => 'api-help-paramvalue-direction-older',
511 ],
512 ],
513 'user' => [
514 ParamValidator::PARAM_TYPE => 'user',
515 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
516 ],
517 'title' => null,
518 'namespace' => [
519 ParamValidator::PARAM_TYPE => 'namespace',
520 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ NS_MEDIA, NS_SPECIAL ],
521 ],
522 'prefix' => [],
523 'tag' => null,
524 'limit' => [
525 ParamValidator::PARAM_DEFAULT => 10,
526 ParamValidator::PARAM_TYPE => 'limit',
527 IntegerDef::PARAM_MIN => 1,
528 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
529 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
530 ],
531 'continue' => [
532 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
533 ],
534 ];
535
536 if ( $config->get( MainConfigNames::MiserMode ) ) {
537 $ret['prefix'][ApiBase::PARAM_HELP_MSG] = 'api-help-param-disabled-in-miser-mode';
538 }
539
540 return $ret;
541 }
542
543 protected function getExamplesMessages() {
544 return [
545 'action=query&list=logevents'
546 => 'apihelp-query+logevents-example-simple',
547 ];
548 }
549
550 public function getHelpUrls() {
551 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Logevents';
552 }
553}
554
556class_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:1577
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:1768
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, 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.
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()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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