MediaWiki master
ApiQueryAllLinks.php
Go to the documentation of this file.
1<?php
32
39
40 private string $table;
41 private string $tablePrefix;
42 private string $indexTag;
43 private $fieldTitle = 'title';
44 private $dfltNamespace = NS_MAIN;
45 private $hasNamespace = true;
46 private $useIndex = null;
47 private $props = [];
48
49 private NamespaceInfo $namespaceInfo;
50 private GenderCache $genderCache;
51 private LinksMigration $linksMigration;
52
60 public function __construct(
61 ApiQuery $query,
62 $moduleName,
63 NamespaceInfo $namespaceInfo,
64 GenderCache $genderCache,
65 LinksMigration $linksMigration
66 ) {
67 switch ( $moduleName ) {
68 case 'alllinks':
69 $prefix = 'al';
70 $this->table = 'pagelinks';
71 $this->tablePrefix = 'pl_';
72 $this->useIndex = 'pl_namespace';
73 $this->indexTag = 'l';
74 break;
75 case 'alltransclusions':
76 $prefix = 'at';
77 $this->table = 'templatelinks';
78 $this->tablePrefix = 'tl_';
79 $this->dfltNamespace = NS_TEMPLATE;
80 $this->useIndex = 'tl_namespace';
81 $this->indexTag = 't';
82 break;
83 case 'allfileusages':
84 $prefix = 'af';
85 $this->table = 'imagelinks';
86 $this->tablePrefix = 'il_';
87 $this->fieldTitle = 'to';
88 $this->dfltNamespace = NS_FILE;
89 $this->hasNamespace = false;
90 $this->indexTag = 'f';
91 break;
92 case 'allredirects':
93 $prefix = 'ar';
94 $this->table = 'redirect';
95 $this->tablePrefix = 'rd_';
96 $this->indexTag = 'r';
97 $this->props = [
98 'fragment' => 'rd_fragment',
99 'interwiki' => 'rd_interwiki',
100 ];
101 break;
102 default:
103 ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
104 }
105
106 parent::__construct( $query, $moduleName, $prefix );
107 $this->namespaceInfo = $namespaceInfo;
108 $this->genderCache = $genderCache;
109 $this->linksMigration = $linksMigration;
110 }
111
112 public function execute() {
113 $this->run();
114 }
115
116 public function getCacheMode( $params ) {
117 return 'public';
118 }
119
120 public function executeGenerator( $resultPageSet ) {
121 $this->run( $resultPageSet );
122 }
123
128 private function run( $resultPageSet = null ) {
129 $db = $this->getDB();
130 $params = $this->extractRequestParams();
131
132 $pfx = $this->tablePrefix;
133
134 $nsField = $pfx . 'namespace';
135 $titleField = $pfx . $this->fieldTitle;
136 $linktargetReadNew = false;
137 $targetIdColumn = '';
138 if ( isset( $this->linksMigration::$mapping[$this->table] ) ) {
139 [ $nsField, $titleField ] = $this->linksMigration->getTitleFields( $this->table );
140 $queryInfo = $this->linksMigration->getQueryInfo( $this->table, 'linktarget', 'STRAIGHT_JOIN' );
141 $this->addTables( $queryInfo['tables'] );
142 $this->addJoinConds( $queryInfo['joins'] );
143 if ( in_array( 'linktarget', $queryInfo['tables'] ) ) {
144 $linktargetReadNew = true;
145 $targetIdColumn = "{$pfx}target_id";
146 $this->addFields( [ $targetIdColumn ] );
147 }
148 } else {
149 if ( $this->useIndex ) {
150 $this->addOption( 'USE INDEX', $this->useIndex );
151 }
152 $this->addTables( $this->table );
153 }
154
155 $prop = array_fill_keys( $params['prop'], true );
156 $fld_ids = isset( $prop['ids'] );
157 $fld_title = isset( $prop['title'] );
158 if ( $this->hasNamespace ) {
159 $namespace = $params['namespace'];
160 } else {
161 $namespace = $this->dfltNamespace;
162 }
163
164 if ( $params['unique'] ) {
165 $matches = array_intersect_key( $prop, $this->props + [ 'ids' => 1 ] );
166 if ( $matches ) {
167 $p = $this->getModulePrefix();
168 $this->dieWithError(
169 [
170 'apierror-invalidparammix-cannotusewith',
171 "{$p}prop=" . implode( '|', array_keys( $matches ) ),
172 "{$p}unique"
173 ],
174 'invalidparammix'
175 );
176 }
177 $this->addOption( 'DISTINCT' );
178 }
179
180 if ( $this->hasNamespace ) {
181 $this->addWhereFld( $nsField, $namespace );
182 }
183
184 $continue = $params['continue'] !== null;
185 if ( $continue ) {
186 $op = $params['dir'] == 'descending' ? '<=' : '>=';
187 if ( $params['unique'] ) {
188 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'string' ] );
189 $this->addWhere( $db->expr( $titleField, $op, $cont[0] ) );
190 } elseif ( !$linktargetReadNew ) {
191 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'string', 'int' ] );
192 $this->addWhere( $db->buildComparison( $op, [
193 $titleField => $cont[0],
194 "{$pfx}from" => $cont[1],
195 ] ) );
196 } else {
197 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'int' ] );
198 $this->addWhere( $db->buildComparison( $op, [
199 $targetIdColumn => $cont[0],
200 "{$pfx}from" => $cont[1],
201 ] ) );
202 }
203 }
204
205 // 'continue' always overrides 'from'
206 $from = $continue || $params['from'] === null ? null :
207 $this->titlePartToKey( $params['from'], $namespace );
208 $to = $params['to'] === null ? null :
209 $this->titlePartToKey( $params['to'], $namespace );
210 $this->addWhereRange( $titleField, 'newer', $from, $to );
211
212 if ( isset( $params['prefix'] ) ) {
213 $this->addWhere(
214 $db->expr(
215 $titleField,
216 IExpression::LIKE,
217 new LikeValue( $this->titlePartToKey( $params['prefix'], $namespace ), $db->anyString() )
218 )
219 );
220 }
221
222 $this->addFields( [ 'pl_title' => $titleField ] );
223 $this->addFieldsIf( [ 'pl_from' => $pfx . 'from' ], !$params['unique'] );
224 foreach ( $this->props as $name => $field ) {
225 $this->addFieldsIf( $field, isset( $prop[$name] ) );
226 }
227
228 $limit = $params['limit'];
229 $this->addOption( 'LIMIT', $limit + 1 );
230
231 $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
232 $orderBy = [];
233 if ( $linktargetReadNew ) {
234 $orderBy[] = $targetIdColumn;
235 } else {
236 $orderBy[] = $titleField . $sort;
237 }
238 if ( !$params['unique'] ) {
239 $orderBy[] = $pfx . 'from' . $sort;
240 }
241 $this->addOption( 'ORDER BY', $orderBy );
242
243 $res = $this->select( __METHOD__ );
244
245 // Get gender information
246 if ( $resultPageSet === null && $res->numRows() && $this->namespaceInfo->hasGenderDistinction( $namespace ) ) {
247 $users = [];
248 foreach ( $res as $row ) {
249 $users[] = $row->pl_title;
250 }
251 if ( $users !== [] ) {
252 $this->genderCache->doQuery( $users, __METHOD__ );
253 }
254 }
255
256 $pageids = [];
257 $titles = [];
258 $count = 0;
259 $result = $this->getResult();
260
261 foreach ( $res as $row ) {
262 if ( ++$count > $limit ) {
263 // We've reached the one extra which shows that there are
264 // additional pages to be had. Stop here...
265 if ( $params['unique'] ) {
266 $this->setContinueEnumParameter( 'continue', $row->pl_title );
267 } elseif ( $linktargetReadNew ) {
268 $this->setContinueEnumParameter( 'continue', $row->{$targetIdColumn} . '|' . $row->pl_from );
269 } else {
270 $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
271 }
272 break;
273 }
274
275 if ( $resultPageSet === null ) {
276 $vals = [
277 ApiResult::META_TYPE => 'assoc',
278 ];
279 if ( $fld_ids ) {
280 $vals['fromid'] = (int)$row->pl_from;
281 }
282 if ( $fld_title ) {
283 $title = Title::makeTitle( $namespace, $row->pl_title );
284 ApiQueryBase::addTitleInfo( $vals, $title );
285 }
286 foreach ( $this->props as $name => $field ) {
287 if ( isset( $prop[$name] ) && $row->$field !== null && $row->$field !== '' ) {
288 $vals[$name] = $row->$field;
289 }
290 }
291 $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
292 if ( !$fit ) {
293 if ( $params['unique'] ) {
294 $this->setContinueEnumParameter( 'continue', $row->pl_title );
295 } elseif ( $linktargetReadNew ) {
296 $this->setContinueEnumParameter( 'continue', $row->{$targetIdColumn} . '|' . $row->pl_from );
297 } else {
298 $this->setContinueEnumParameter( 'continue', $row->pl_title . '|' . $row->pl_from );
299 }
300 break;
301 }
302 } elseif ( $params['unique'] ) {
303 $titles[] = Title::makeTitle( $namespace, $row->pl_title );
304 } else {
305 $pageids[] = $row->pl_from;
306 }
307 }
308
309 if ( $resultPageSet === null ) {
310 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], $this->indexTag );
311 } elseif ( $params['unique'] ) {
312 $resultPageSet->populateFromTitles( $titles );
313 } else {
314 $resultPageSet->populateFromPageIDs( $pageids );
315 }
316 }
317
318 public function getAllowedParams() {
319 $allowedParams = [
320 'continue' => [
321 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
322 ],
323 'from' => null,
324 'to' => null,
325 'prefix' => null,
326 'unique' => false,
327 'prop' => [
328 ParamValidator::PARAM_ISMULTI => true,
329 ParamValidator::PARAM_DEFAULT => 'title',
330 ParamValidator::PARAM_TYPE => array_merge(
331 [ 'ids', 'title' ], array_keys( $this->props )
332 ),
334 ],
335 'namespace' => [
336 ParamValidator::PARAM_DEFAULT => $this->dfltNamespace,
337 ParamValidator::PARAM_TYPE => 'namespace',
338 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ NS_MEDIA, NS_SPECIAL ],
339 ],
340 'limit' => [
341 ParamValidator::PARAM_DEFAULT => 10,
342 ParamValidator::PARAM_TYPE => 'limit',
343 IntegerDef::PARAM_MIN => 1,
344 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
345 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
346 ],
347 'dir' => [
348 ParamValidator::PARAM_DEFAULT => 'ascending',
349 ParamValidator::PARAM_TYPE => [
350 'ascending',
351 'descending'
352 ]
353 ],
354 ];
355 if ( !$this->hasNamespace ) {
356 unset( $allowedParams['namespace'] );
357 }
358
359 return $allowedParams;
360 }
361
362 protected function getExamplesMessages() {
363 $p = $this->getModulePrefix();
364 $name = $this->getModuleName();
365 $path = $this->getModulePath();
366
367 return [
368 "action=query&list={$name}&{$p}from=B&{$p}prop=ids|title"
369 => "apihelp-$path-example-b",
370 "action=query&list={$name}&{$p}unique=&{$p}from=B"
371 => "apihelp-$path-example-unique",
372 "action=query&generator={$name}&g{$p}unique=&g{$p}from=B"
373 => "apihelp-$path-example-unique-generator",
374 "action=query&generator={$name}&g{$p}from=B"
375 => "apihelp-$path-example-generator",
376 ];
377 }
378
379 public function getHelpUrls() {
380 $name = ucfirst( $this->getModuleName() );
381
382 return "https://www.mediawiki.org/wiki/Special:MyLanguage/API:{$name}";
383 }
384}
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
const NS_TEMPLATE
Definition Defines.php:74
const NS_SPECIAL
Definition Defines.php:53
const NS_MEDIA
Definition Defines.php:52
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:1542
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:550
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1786
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1734
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:211
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:236
getResult()
Get the result object.
Definition ApiBase.php:680
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:820
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:238
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:541
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result 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.
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.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
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
const META_TYPE
Key for the 'type' metadata item.
Look up "gender" user preference.
Service for compat reading of links tables.
Type definition for namespace types.
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