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