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