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