MediaWiki REL1_35
ApiQueryDeletedrevs.php
Go to the documentation of this file.
1<?php
30
38
39 public function __construct( ApiQuery $query, $moduleName ) {
40 parent::__construct( $query, $moduleName, 'dr' );
41 }
42
43 public function execute() {
44 // Before doing anything at all, let's check permissions
45 $this->checkUserRightsAny( 'deletedhistory' );
46
47 $this->addDeprecation( 'apiwarn-deprecation-deletedrevs', 'action=query&list=deletedrevs' );
48
49 $user = $this->getUser();
50 $db = $this->getDB();
51 $commentStore = CommentStore::getStore();
52 $params = $this->extractRequestParams( false );
53 $prop = array_flip( $params['prop'] );
54 $fld_parentid = isset( $prop['parentid'] );
55 $fld_revid = isset( $prop['revid'] );
56 $fld_user = isset( $prop['user'] );
57 $fld_userid = isset( $prop['userid'] );
58 $fld_comment = isset( $prop['comment'] );
59 $fld_parsedcomment = isset( $prop['parsedcomment'] );
60 $fld_minor = isset( $prop['minor'] );
61 $fld_len = isset( $prop['len'] );
62 $fld_sha1 = isset( $prop['sha1'] );
63 $fld_content = isset( $prop['content'] );
64 $fld_token = isset( $prop['token'] );
65 $fld_tags = isset( $prop['tags'] );
66
67 // If we're in a mode that breaks the same-origin policy, no tokens can
68 // be obtained
69 if ( $this->lacksSameOriginSecurity() ) {
70 $fld_token = false;
71 }
72
73 // If user can't undelete, no tokens
74 if ( !$this->getPermissionManager()->userHasRight( $user, 'undelete' ) ) {
75 $fld_token = false;
76 }
77
78 $result = $this->getResult();
79 $pageSet = $this->getPageSet();
80 $titles = $pageSet->getTitles();
81
82 // This module operates in three modes:
83 // 'revs': List deleted revs for certain titles (1)
84 // 'user': List deleted revs by a certain user (2)
85 // 'all': List all deleted revs in NS (3)
86 $mode = 'all';
87 if ( count( $titles ) > 0 ) {
88 $mode = 'revs';
89 } elseif ( $params['user'] !== null ) {
90 $mode = 'user';
91 }
92
93 if ( $mode == 'revs' || $mode == 'user' ) {
94 // Ignore namespace and unique due to inability to know whether they were purposely set
95 foreach ( [ 'from', 'to', 'prefix', /*'namespace', 'unique'*/ ] as $p ) {
96 if ( $params[$p] !== null ) {
97 $this->dieWithError( [ 'apierror-deletedrevs-param-not-1-2', $p ], 'badparams' );
98 }
99 }
100 } else {
101 foreach ( [ 'start', 'end' ] as $p ) {
102 if ( $params[$p] !== null ) {
103 $this->dieWithError( [ 'apierror-deletedrevs-param-not-3', $p ], 'badparams' );
104 }
105 }
106 }
107
108 if ( $params['user'] !== null && $params['excludeuser'] !== null ) {
109 $this->dieWithError( 'user and excludeuser cannot be used together', 'badparams' );
110 }
111
112 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
113 $arQuery = $revisionStore->getArchiveQueryInfo();
114 $this->addTables( $arQuery['tables'] );
115 $this->addFields( $arQuery['fields'] );
116 $this->addJoinConds( $arQuery['joins'] );
117 $this->addFields( [ 'ar_title', 'ar_namespace' ] );
118
119 if ( $fld_tags ) {
120 $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'archive' ) ] );
121 }
122
123 if ( $params['tag'] !== null ) {
124 $this->addTables( 'change_tag' );
125 $this->addJoinConds(
126 [ 'change_tag' => [ 'JOIN', [ 'ar_rev_id=ct_rev_id' ] ] ]
127 );
128 $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
129 try {
130 $this->addWhereFld( 'ct_tag_id', $changeTagDefStore->getId( $params['tag'] ) );
131 } catch ( NameTableAccessException $exception ) {
132 // Return nothing.
133 $this->addWhere( '1=0' );
134 }
135 }
136
137 // This means stricter restrictions
138 if ( $fld_content ) {
139 $this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
140 }
141 // Check limits
142 $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1;
143 $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2;
144
145 $limit = $params['limit'];
146
147 if ( $limit == 'max' ) {
148 $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
149 $this->getResult()->addParsedLimit( $this->getModuleName(), $limit );
150 }
151
152 $limit = $this->getMain()->getParamValidator()->validateValue(
153 $this, 'limit', $limit, [
154 ParamValidator::PARAM_TYPE => 'limit',
155 IntegerDef::PARAM_MIN => 1,
156 IntegerDef::PARAM_MAX => $userMax,
157 IntegerDef::PARAM_MAX2 => $botMax,
158 IntegerDef::PARAM_IGNORE_RANGE => true,
159 ]
160 );
161
162 if ( $fld_token ) {
163 // Undelete tokens are identical for all pages, so we cache one here
164 $token = $user->getEditToken( '', $this->getMain()->getRequest() );
165 }
166
167 $dir = $params['dir'];
168
169 // We need a custom WHERE clause that matches all titles.
170 if ( $mode == 'revs' ) {
171 $lb = new LinkBatch( $titles );
172 $where = $lb->constructSet( 'ar', $db );
173 $this->addWhere( $where );
174 } elseif ( $mode == 'all' ) {
175 $this->addWhereFld( 'ar_namespace', $params['namespace'] );
176
177 $from = $params['from'] === null
178 ? null
179 : $this->titlePartToKey( $params['from'], $params['namespace'] );
180 $to = $params['to'] === null
181 ? null
182 : $this->titlePartToKey( $params['to'], $params['namespace'] );
183 $this->addWhereRange( 'ar_title', $dir, $from, $to );
184
185 if ( isset( $params['prefix'] ) ) {
186 $this->addWhere( 'ar_title' . $db->buildLike(
187 $this->titlePartToKey( $params['prefix'], $params['namespace'] ),
188 $db->anyString() ) );
189 }
190 }
191
192 if ( $params['user'] !== null ) {
193 // Don't query by user ID here, it might be able to use the ar_usertext_timestamp index.
194 $actorQuery = ActorMigration::newMigration()
195 ->getWhere( $db, 'ar_user', $params['user'], false );
196 $this->addTables( $actorQuery['tables'] );
197 $this->addJoinConds( $actorQuery['joins'] );
198 $this->addWhere( $actorQuery['conds'] );
199 } elseif ( $params['excludeuser'] !== null ) {
200 // Here there's no chance of using ar_usertext_timestamp.
201 $actorQuery = ActorMigration::newMigration()
202 ->getWhere( $db, 'ar_user', $params['excludeuser'] );
203 $this->addTables( $actorQuery['tables'] );
204 $this->addJoinConds( $actorQuery['joins'] );
205 $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
206 }
207
208 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
209 // Paranoia: avoid brute force searches (T19342)
210 // (shouldn't be able to get here without 'deletedhistory', but
211 // check it again just in case)
212 if ( !$this->getPermissionManager()->userHasRight( $user, 'deletedhistory' ) ) {
213 $bitmask = RevisionRecord::DELETED_USER;
214 } elseif ( !$this->getPermissionManager()
215 ->userHasAnyRight( $user, 'suppressrevision', 'viewsuppressed' )
216 ) {
217 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
218 } else {
219 $bitmask = 0;
220 }
221 if ( $bitmask ) {
222 $this->addWhere( $db->bitAnd( 'ar_deleted', $bitmask ) . " != $bitmask" );
223 }
224 }
225
226 if ( $params['continue'] !== null ) {
227 $cont = explode( '|', $params['continue'] );
228 $op = ( $dir == 'newer' ? '>' : '<' );
229 if ( $mode == 'all' || $mode == 'revs' ) {
230 $this->dieContinueUsageIf( count( $cont ) != 4 );
231 $ns = (int)$cont[0];
232 $this->dieContinueUsageIf( strval( $ns ) !== $cont[0] );
233 $title = $db->addQuotes( $cont[1] );
234 $ts = $db->addQuotes( $db->timestamp( $cont[2] ) );
235 $ar_id = (int)$cont[3];
236 $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[3] );
237 $this->addWhere( "ar_namespace $op $ns OR " .
238 "(ar_namespace = $ns AND " .
239 "(ar_title $op $title OR " .
240 "(ar_title = $title AND " .
241 "(ar_timestamp $op $ts OR " .
242 "(ar_timestamp = $ts AND " .
243 "ar_id $op= $ar_id)))))" );
244 } else {
245 $this->dieContinueUsageIf( count( $cont ) != 2 );
246 $ts = $db->addQuotes( $db->timestamp( $cont[0] ) );
247 $ar_id = (int)$cont[1];
248 $this->dieContinueUsageIf( strval( $ar_id ) !== $cont[1] );
249 $this->addWhere( "ar_timestamp $op $ts OR " .
250 "(ar_timestamp = $ts AND " .
251 "ar_id $op= $ar_id)" );
252 }
253 }
254
255 $this->addOption( 'LIMIT', $limit + 1 );
256 if ( $mode == 'all' ) {
257 if ( $params['unique'] ) {
258 // @todo Does this work on non-MySQL?
259 $this->addOption( 'GROUP BY', 'ar_title' );
260 } else {
261 $sort = ( $dir == 'newer' ? '' : ' DESC' );
262 $this->addOption( 'ORDER BY', [
263 'ar_title' . $sort,
264 'ar_timestamp' . $sort,
265 'ar_id' . $sort,
266 ] );
267 }
268 } else {
269 if ( $mode == 'revs' ) {
270 // Sort by ns and title in the same order as timestamp for efficiency
271 $this->addWhereRange( 'ar_namespace', $dir, null, null );
272 $this->addWhereRange( 'ar_title', $dir, null, null );
273 }
274 $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
275 // Include in ORDER BY for uniqueness
276 $this->addWhereRange( 'ar_id', $dir, null, null );
277 }
278 $res = $this->select( __METHOD__ );
279 $pageMap = []; // Maps ns&title to (fake) pageid
280 $count = 0;
281 $newPageID = 0;
282 foreach ( $res as $row ) {
283 if ( ++$count > $limit ) {
284 // We've had enough
285 if ( $mode == 'all' || $mode == 'revs' ) {
286 $this->setContinueEnumParameter( 'continue',
287 "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
288 );
289 } else {
290 $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
291 }
292 break;
293 }
294
295 $rev = [];
296 $anyHidden = false;
297
298 $rev['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ar_timestamp );
299 if ( $fld_revid ) {
300 $rev['revid'] = (int)$row->ar_rev_id;
301 }
302 if ( $fld_parentid && $row->ar_parent_id !== null ) {
303 $rev['parentid'] = (int)$row->ar_parent_id;
304 }
305 if ( $fld_user || $fld_userid ) {
306 if ( $row->ar_deleted & RevisionRecord::DELETED_USER ) {
307 $rev['userhidden'] = true;
308 $anyHidden = true;
309 }
310 if ( RevisionRecord::userCanBitfield(
311 $row->ar_deleted,
312 RevisionRecord::DELETED_USER,
313 $user
314 ) ) {
315 if ( $fld_user ) {
316 $rev['user'] = $row->ar_user_text;
317 }
318 if ( $fld_userid ) {
319 $rev['userid'] = (int)$row->ar_user;
320 }
321 }
322 }
323
324 if ( $fld_comment || $fld_parsedcomment ) {
325 if ( $row->ar_deleted & RevisionRecord::DELETED_COMMENT ) {
326 $rev['commenthidden'] = true;
327 $anyHidden = true;
328 }
329 if ( RevisionRecord::userCanBitfield(
330 $row->ar_deleted,
331 RevisionRecord::DELETED_COMMENT,
332 $user
333 ) ) {
334 $comment = $commentStore->getComment( 'ar_comment', $row )->text;
335 if ( $fld_comment ) {
336 $rev['comment'] = $comment;
337 }
338 if ( $fld_parsedcomment ) {
339 $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
340 $rev['parsedcomment'] = Linker::formatComment( $comment, $title );
341 }
342 }
343 }
344
345 if ( $fld_minor ) {
346 $rev['minor'] = $row->ar_minor_edit == 1;
347 }
348 if ( $fld_len ) {
349 $rev['len'] = $row->ar_len;
350 }
351 if ( $fld_sha1 ) {
352 if ( $row->ar_deleted & RevisionRecord::DELETED_TEXT ) {
353 $rev['sha1hidden'] = true;
354 $anyHidden = true;
355 }
356 if ( RevisionRecord::userCanBitfield(
357 $row->ar_deleted,
358 RevisionRecord::DELETED_TEXT,
359 $user
360 ) ) {
361 if ( $row->ar_sha1 != '' ) {
362 $rev['sha1'] = Wikimedia\base_convert( $row->ar_sha1, 36, 16, 40 );
363 } else {
364 $rev['sha1'] = '';
365 }
366 }
367 }
368 if ( $fld_content ) {
369 if ( $row->ar_deleted & RevisionRecord::DELETED_TEXT ) {
370 $rev['texthidden'] = true;
371 $anyHidden = true;
372 }
373 if ( RevisionRecord::userCanBitfield(
374 $row->ar_deleted,
375 RevisionRecord::DELETED_TEXT,
376 $user
377 ) ) {
378 ApiResult::setContentValue( $rev, 'text',
379 $revisionStore->newRevisionFromArchiveRow( $row )
380 ->getContent( SlotRecord::MAIN )->serialize() );
381 }
382 }
383
384 if ( $fld_tags ) {
385 if ( $row->ts_tags ) {
386 $tags = explode( ',', $row->ts_tags );
387 ApiResult::setIndexedTagName( $tags, 'tag' );
388 $rev['tags'] = $tags;
389 } else {
390 $rev['tags'] = [];
391 }
392 }
393
394 if ( $anyHidden && ( $row->ar_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
395 $rev['suppressed'] = true;
396 }
397
398 if ( !isset( $pageMap[$row->ar_namespace][$row->ar_title] ) ) {
399 $pageID = $newPageID++;
400 $pageMap[$row->ar_namespace][$row->ar_title] = $pageID;
401 $a = [ 'revisions' => [ $rev ] ];
402 ApiResult::setIndexedTagName( $a['revisions'], 'rev' );
403 $title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
405 if ( $fld_token ) {
406 $a['token'] = $token;
407 }
408 $fit = $result->addValue( [ 'query', $this->getModuleName() ], $pageID, $a );
409 } else {
410 $pageID = $pageMap[$row->ar_namespace][$row->ar_title];
411 $fit = $result->addValue(
412 [ 'query', $this->getModuleName(), $pageID, 'revisions' ],
413 null, $rev );
414 }
415 if ( !$fit ) {
416 if ( $mode == 'all' || $mode == 'revs' ) {
417 $this->setContinueEnumParameter( 'continue',
418 "$row->ar_namespace|$row->ar_title|$row->ar_timestamp|$row->ar_id"
419 );
420 } else {
421 $this->setContinueEnumParameter( 'continue', "$row->ar_timestamp|$row->ar_id" );
422 }
423 break;
424 }
425 }
426 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'page' );
427 }
428
429 public function isDeprecated() {
430 return true;
431 }
432
433 public function getAllowedParams() {
434 return [
435 'start' => [
436 ApiBase::PARAM_TYPE => 'timestamp',
437 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 2 ] ],
438 ],
439 'end' => [
440 ApiBase::PARAM_TYPE => 'timestamp',
441 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 2 ] ],
442 ],
443 'dir' => [
445 'newer',
446 'older'
447 ],
448 ApiBase::PARAM_DFLT => 'older',
449 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
450 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 1, 3 ] ],
451 ],
452 'from' => [
453 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
454 ],
455 'to' => [
456 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
457 ],
458 'prefix' => [
459 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
460 ],
461 'unique' => [
462 ApiBase::PARAM_DFLT => false,
463 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
464 ],
465 'namespace' => [
466 ApiBase::PARAM_TYPE => 'namespace',
468 ApiBase::PARAM_HELP_MSG_INFO => [ [ 'modes', 3 ] ],
469 ],
470 'tag' => null,
471 'user' => [
472 ApiBase::PARAM_TYPE => 'user',
473 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
474 UserDef::PARAM_RETURN_OBJECT => true,
475 ],
476 'excludeuser' => [
477 ApiBase::PARAM_TYPE => 'user',
478 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
479 UserDef::PARAM_RETURN_OBJECT => true,
480 ],
481 'prop' => [
482 ApiBase::PARAM_DFLT => 'user|comment',
484 'revid',
485 'parentid',
486 'user',
487 'userid',
488 'comment',
489 'parsedcomment',
490 'minor',
491 'len',
492 'sha1',
493 'content',
494 'token',
495 'tags'
496 ],
498 ],
499 'limit' => [
501 ApiBase::PARAM_TYPE => 'limit',
505 ],
506 'continue' => [
507 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
508 ],
509 ];
510 }
511
512 protected function getExamplesMessages() {
513 return [
514 'action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&' .
515 'drprop=user|comment|content'
516 => 'apihelp-query+deletedrevs-example-mode1',
517 'action=query&list=deletedrevs&druser=Bob&drlimit=50'
518 => 'apihelp-query+deletedrevs-example-mode2',
519 'action=query&list=deletedrevs&drdir=newer&drlimit=50'
520 => 'apihelp-query+deletedrevs-example-mode3-main',
521 'action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique='
522 => 'apihelp-query+deletedrevs-example-mode3-talk',
523 ];
524 }
525
526 public function getHelpUrls() {
527 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Deletedrevs';
528 }
529}
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:1437
const PARAM_MAX2
Definition ApiBase.php:86
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition ApiBase.php:1539
const PARAM_MAX
Definition ApiBase.php:82
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1617
getMain()
Get the main module.
Definition ApiBase.php:515
const PARAM_TYPE
Definition ApiBase.php:78
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition ApiBase.php:179
const PARAM_DFLT
Definition ApiBase.php:70
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition ApiBase.php:1370
const PARAM_MIN
Definition ApiBase.php:90
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:220
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition ApiBase.php:226
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
const LIMIT_SML1
Slow query, standard limit.
Definition ApiBase.php:224
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:222
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:499
const PARAM_ISMULTI
Definition ApiBase.php:74
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:548
This is a base class for all Query modules.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
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.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only) Stable to override.
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
getPageSet()
Get the PageSet object to work on Stable to override.
titlePartToKey( $titlePart, $namespace=NS_MAIN)
Convert an input title or title prefix into a dbkey.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Query module to enumerate all deleted revisions.
getExamplesMessages()
Returns usage examples for this module.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getHelpUrls()
Return links to more detailed help pages about the module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
isDeprecated()
Indicates whether this module is deprecated.
__construct(ApiQuery $query, $moduleName)
This is the main query class.
Definition ApiQuery.php:37
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
getUser()
Stable to override.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition Linker.php:1209
MediaWikiServices is the service locator for the application scope of MediaWiki.
Type definition for user types.
Definition UserDef.php:23
Page revision base class.
Value object representing a content slot associated with a page revision.
Exception representing a failure to look up a row from a name table.
Service for formatting and validating API parameters.
Type definition for integer types.
const NS_MAIN
Definition Defines.php:70
return true
Definition router.php:92