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