MediaWiki master
ApiQueryAllPages.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
34
41
42 private NamespaceInfo $namespaceInfo;
43 private GenderCache $genderCache;
44 private RestrictionStore $restrictionStore;
45
46 public function __construct(
47 ApiQuery $query,
48 string $moduleName,
49 NamespaceInfo $namespaceInfo,
50 GenderCache $genderCache,
51 RestrictionStore $restrictionStore
52 ) {
53 parent::__construct( $query, $moduleName, 'ap' );
54 $this->namespaceInfo = $namespaceInfo;
55 $this->genderCache = $genderCache;
56 $this->restrictionStore = $restrictionStore;
57 }
58
59 public function execute() {
60 $this->run();
61 }
62
63 public function getCacheMode( $params ) {
64 return 'public';
65 }
66
71 public function executeGenerator( $resultPageSet ) {
72 if ( $resultPageSet->isResolvingRedirects() ) {
73 $this->dieWithError( 'apierror-allpages-generator-redirects', 'params' );
74 }
75
76 $this->run( $resultPageSet );
77 }
78
83 private function run( $resultPageSet = null ) {
84 $db = $this->getDB();
85
87
88 // Page filters
89 $this->addTables( 'page' );
90
91 if ( $params['continue'] !== null ) {
92 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'string' ] );
93 $op = $params['dir'] == 'descending' ? '<=' : '>=';
94 $this->addWhere( $db->expr( 'page_title', $op, $cont[0] ) );
95 }
96
97 $miserMode = $this->getConfig()->get( MainConfigNames::MiserMode );
98 if ( !$miserMode ) {
99 if ( $params['filterredir'] == 'redirects' ) {
100 $this->addWhereFld( 'page_is_redirect', 1 );
101 } elseif ( $params['filterredir'] == 'nonredirects' ) {
102 $this->addWhereFld( 'page_is_redirect', 0 );
103 }
104 }
105
106 $this->addWhereFld( 'page_namespace', $params['namespace'] );
107 $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
108 $from = ( $params['from'] === null
109 ? null
110 : $this->titlePartToKey( $params['from'], $params['namespace'] ) );
111 $to = ( $params['to'] === null
112 ? null
113 : $this->titlePartToKey( $params['to'], $params['namespace'] ) );
114 $this->addWhereRange( 'page_title', $dir, $from, $to );
115
116 if ( isset( $params['prefix'] ) ) {
117 $this->addWhere(
118 $db->expr(
119 'page_title',
120 IExpression::LIKE,
121 new LikeValue( $this->titlePartToKey( $params['prefix'], $params['namespace'] ), $db->anyString() )
122 )
123 );
124 }
125
126 if ( $resultPageSet === null ) {
127 $selectFields = [
128 'page_namespace',
129 'page_title',
130 'page_id'
131 ];
132 } else {
133 $selectFields = $resultPageSet->getPageTableFields();
134 }
135
136 $miserModeFilterRedirValue = null;
137 $miserModeFilterRedir = $miserMode && $params['filterredir'] !== 'all';
138 if ( $miserModeFilterRedir ) {
139 $selectFields[] = 'page_is_redirect';
140
141 if ( $params['filterredir'] == 'redirects' ) {
142 $miserModeFilterRedirValue = 1;
143 } elseif ( $params['filterredir'] == 'nonredirects' ) {
144 $miserModeFilterRedirValue = 0;
145 }
146 }
147
148 $this->addFields( $selectFields );
149 $forceNameTitleIndex = true;
150 if ( isset( $params['minsize'] ) ) {
151 $this->addWhere( 'page_len>=' . (int)$params['minsize'] );
152 $forceNameTitleIndex = false;
153 }
154
155 if ( isset( $params['maxsize'] ) ) {
156 $this->addWhere( 'page_len<=' . (int)$params['maxsize'] );
157 $forceNameTitleIndex = false;
158 }
159
160 // Page protection filtering
161 if ( $params['prtype'] || $params['prexpiry'] != 'all' ) {
162 $this->addTables( 'page_restrictions' );
163 $this->addWhere( 'page_id=pr_page' );
164 $this->addWhere(
165 $db->expr( 'pr_expiry', '>', $db->timestamp() )->or( 'pr_expiry', '=', null )
166 );
167
168 if ( $params['prtype'] ) {
169 $this->addWhereFld( 'pr_type', $params['prtype'] );
170
171 if ( isset( $params['prlevel'] ) ) {
172 // Remove the empty string and '*' from the prlevel array
173 $prlevel = array_diff( $params['prlevel'], [ '', '*' ] );
174
175 if ( count( $prlevel ) ) {
176 $this->addWhereFld( 'pr_level', $prlevel );
177 }
178 }
179 if ( $params['prfiltercascade'] == 'cascading' ) {
180 $this->addWhereFld( 'pr_cascade', 1 );
181 } elseif ( $params['prfiltercascade'] == 'noncascading' ) {
182 $this->addWhereFld( 'pr_cascade', 0 );
183 }
184 }
185 $forceNameTitleIndex = false;
186
187 if ( $params['prexpiry'] == 'indefinite' ) {
188 $this->addWhereFld( 'pr_expiry', [ $db->getInfinity(), null ] );
189 } elseif ( $params['prexpiry'] == 'definite' ) {
190 $this->addWhere( $db->expr( 'pr_expiry', '!=', $db->getInfinity() ) );
191 }
192
193 $this->addOption( 'DISTINCT' );
194 } elseif ( isset( $params['prlevel'] ) ) {
195 $this->dieWithError(
196 [ 'apierror-invalidparammix-mustusewith', 'prlevel', 'prtype' ], 'invalidparammix'
197 );
198 }
199
200 if ( $params['filterlanglinks'] == 'withoutlanglinks' ) {
201 $this->addTables( 'langlinks' );
202 $this->addJoinConds( [ 'langlinks' => [ 'LEFT JOIN', 'page_id=ll_from' ] ] );
203 $this->addWhere( [ 'll_from' => null ] );
204 $forceNameTitleIndex = false;
205 } elseif ( $params['filterlanglinks'] == 'withlanglinks' ) {
206 $this->addTables( 'langlinks' );
207 $this->addWhere( 'page_id=ll_from' );
208 $this->addOption( 'STRAIGHT_JOIN' );
209
210 // MySQL filesorts if we use a GROUP BY that works with the rules
211 // in the 1992 SQL standard (it doesn't like having the
212 // constant-in-WHERE page_namespace column in there). Using the
213 // 1999 rules works fine, but that breaks other DBs. Sigh.
214 // @todo Once we drop support for 1992-rule DBs, we can simplify this.
215 $dbType = $db->getType();
216 if ( $dbType === 'mysql' || $dbType === 'sqlite' ) {
217 // Ignore the rules, or 1999 rules if you count unique keys
218 // over non-NULL columns as satisfying the requirement for
219 // "functional dependency" and don't require including
220 // constant-in-WHERE columns in the GROUP BY.
221 $this->addOption( 'GROUP BY', [ 'page_title' ] );
222 } elseif ( $dbType === 'postgres' && $db->getServerVersion() >= 9.1 ) {
223 // 1999 rules only counting primary keys
224 $this->addOption( 'GROUP BY', [ 'page_title', 'page_id' ] );
225 } else {
226 // 1992 rules
227 $this->addOption( 'GROUP BY', $selectFields );
228 }
229
230 $forceNameTitleIndex = false;
231 }
232
233 if ( $forceNameTitleIndex ) {
234 $this->addOption( 'USE INDEX', 'page_name_title' );
235 }
236
237 $limit = $params['limit'];
238 $this->addOption( 'LIMIT', $limit + 1 );
239 $res = $this->select( __METHOD__ );
240
241 // Get gender information
242 if ( $this->namespaceInfo->hasGenderDistinction( $params['namespace'] ) ) {
243 $users = [];
244 foreach ( $res as $row ) {
245 $users[] = $row->page_title;
246 }
247 $this->genderCache->doQuery( $users, __METHOD__ );
248 $res->rewind(); // reset
249 }
250
251 $count = 0;
252 $result = $this->getResult();
253 foreach ( $res as $row ) {
254 if ( ++$count > $limit ) {
255 // We've reached the one extra which shows that there are
256 // additional pages to be had. Stop here...
257 $this->setContinueEnumParameter( 'continue', $row->page_title );
258 break;
259 }
260
261 if ( $miserModeFilterRedir && (int)$row->page_is_redirect !== $miserModeFilterRedirValue ) {
262 // Filter implemented in PHP due to being in Miser Mode
263 continue;
264 }
265
266 if ( $resultPageSet === null ) {
267 $title = Title::makeTitle( $row->page_namespace, $row->page_title );
268 $vals = [
269 'pageid' => (int)$row->page_id,
270 'ns' => $title->getNamespace(),
271 'title' => $title->getPrefixedText()
272 ];
273 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
274 if ( !$fit ) {
275 $this->setContinueEnumParameter( 'continue', $row->page_title );
276 break;
277 }
278 } else {
279 $resultPageSet->processDbRow( $row );
280 }
281 }
282
283 if ( $resultPageSet === null ) {
284 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'p' );
285 }
286 }
287
288 public function getAllowedParams() {
289 $ret = [
290 'from' => null,
291 'continue' => [
292 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
293 ],
294 'to' => null,
295 'prefix' => null,
296 'namespace' => [
297 ParamValidator::PARAM_DEFAULT => NS_MAIN,
298 ParamValidator::PARAM_TYPE => 'namespace',
299 ],
300 'filterredir' => [
301 ParamValidator::PARAM_DEFAULT => 'all',
302 ParamValidator::PARAM_TYPE => [
303 'all',
304 'redirects',
305 'nonredirects'
306 ]
307 ],
308 'filterlanglinks' => [
309 ParamValidator::PARAM_TYPE => [
310 'withlanglinks',
311 'withoutlanglinks',
312 'all'
313 ],
314 ParamValidator::PARAM_DEFAULT => 'all'
315 ],
316 'minsize' => [
317 ParamValidator::PARAM_TYPE => 'integer',
318 ],
319 'maxsize' => [
320 ParamValidator::PARAM_TYPE => 'integer',
321 ],
322 'prtype' => [
323 ParamValidator::PARAM_TYPE => $this->restrictionStore->listAllRestrictionTypes( true ),
324 ParamValidator::PARAM_ISMULTI => true
325 ],
326 'prlevel' => [
327 ParamValidator::PARAM_TYPE =>
329 ParamValidator::PARAM_ISMULTI => true
330 ],
331 'prfiltercascade' => [
332 ParamValidator::PARAM_DEFAULT => 'all',
333 ParamValidator::PARAM_TYPE => [
334 'cascading',
335 'noncascading',
336 'all'
337 ],
338 ],
339 'prexpiry' => [
340 ParamValidator::PARAM_TYPE => [
341 'indefinite',
342 'definite',
343 'all'
344 ],
345 ParamValidator::PARAM_DEFAULT => 'all',
347 ],
348 'limit' => [
349 ParamValidator::PARAM_DEFAULT => 10,
350 ParamValidator::PARAM_TYPE => 'limit',
351 IntegerDef::PARAM_MIN => 1,
352 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
353 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
354 ],
355 'dir' => [
356 ParamValidator::PARAM_DEFAULT => 'ascending',
357 ParamValidator::PARAM_TYPE => [
358 'ascending',
359 'descending'
360 ]
361 ],
362 ];
363
364 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
365 $ret['filterredir'][ApiBase::PARAM_HELP_MSG_APPEND] = [ 'api-help-param-limited-in-miser-mode' ];
366 }
367
368 return $ret;
369 }
370
371 protected function getExamplesMessages() {
372 return [
373 'action=query&list=allpages&apfrom=B'
374 => 'apihelp-query+allpages-example-b',
375 'action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info'
376 => 'apihelp-query+allpages-example-generator',
377 'action=query&generator=allpages&gaplimit=2&' .
378 'gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
379 => 'apihelp-query+allpages-example-generator-revisions',
380 ];
381 }
382
383 public function getHelpUrls() {
384 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Allpages';
385 }
386}
387
389class_alias( ApiQueryAllPages::class, 'ApiQueryAllPages' );
const NS_MAIN
Definition Defines.php:65
array $params
The job parameters.
run()
Run the job.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1565
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:1756
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
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:192
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
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:249
Query module to enumerate all available pages.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
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.
__construct(ApiQuery $query, string $moduleName, NamespaceInfo $namespaceInfo, GenderCache $genderCache, RestrictionStore $restrictionStore)
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.
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.
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.
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.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
This is the main query class.
Definition ApiQuery.php:48
Look up "gender" user preference.
A class containing constants representing the names of configuration variables.
const RestrictionLevels
Name constant for the RestrictionLevels setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:78
Service for formatting and validating API parameters.
Type definition for integer types.
Content of like value.
Definition LikeValue.php:14