Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
65.33% |
147 / 225 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
ApiQueryAllPages | |
65.62% |
147 / 224 |
|
37.50% |
3 / 8 |
161.83 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCacheMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
executeGenerator | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
run | |
50.78% |
65 / 128 |
|
0.00% |
0 / 1 |
263.46 | |||
getAllowedParams | |
98.70% |
76 / 77 |
|
0.00% |
0 / 1 |
2 | |||
getExamplesMessages | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | namespace MediaWiki\Api; |
24 | |
25 | use MediaWiki\Cache\GenderCache; |
26 | use MediaWiki\MainConfigNames; |
27 | use MediaWiki\Permissions\RestrictionStore; |
28 | use MediaWiki\Title\NamespaceInfo; |
29 | use MediaWiki\Title\Title; |
30 | use Wikimedia\ParamValidator\ParamValidator; |
31 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
32 | use Wikimedia\Rdbms\IExpression; |
33 | use Wikimedia\Rdbms\LikeValue; |
34 | |
35 | /** |
36 | * Query module to enumerate all available pages. |
37 | * |
38 | * @ingroup API |
39 | */ |
40 | class ApiQueryAllPages extends ApiQueryGeneratorBase { |
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 | |
67 | /** |
68 | * @param ApiPageSet $resultPageSet |
69 | * @return void |
70 | */ |
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 | |
79 | /** |
80 | * @param ApiPageSet|null $resultPageSet |
81 | * @return void |
82 | */ |
83 | private function run( $resultPageSet = null ) { |
84 | $db = $this->getDB(); |
85 | |
86 | $params = $this->extractRequestParams(); |
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 => |
328 | $this->getConfig()->get( MainConfigNames::RestrictionLevels ), |
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', |
346 | ApiBase::PARAM_HELP_MSG_PER_VALUE => [], |
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 | |
388 | /** @deprecated class alias since 1.43 */ |
389 | class_alias( ApiQueryAllPages::class, 'ApiQueryAllPages' ); |