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