MediaWiki master
ApiQueryCategories.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
15use Wikimedia\Timestamp\TimestampFormat as TS;
16
23
24 public function __construct( ApiQuery $query, string $moduleName ) {
25 parent::__construct( $query, $moduleName, 'cl' );
26 }
27
28 public function execute() {
29 $this->run();
30 }
31
33 public function getCacheMode( $params ) {
34 return 'public';
35 }
36
38 public function executeGenerator( $resultPageSet ) {
39 $this->run( $resultPageSet );
40 }
41
45 private function run( $resultPageSet = null ) {
46 $pages = $this->getPageSet()->getGoodPages();
47 if ( $pages === [] ) {
48 return; // nothing to do
49 }
50
51 $params = $this->extractRequestParams();
52 $prop = array_fill_keys( (array)$params['prop'], true );
53 $show = array_fill_keys( (array)$params['show'], true );
54
55 $cats = [];
56 if ( $params['categories'] ) {
57 foreach ( $params['categories'] as $cat ) {
58 $title = Title::newFromText( $cat );
59 if ( !$title || $title->getNamespace() !== NS_CATEGORY ) {
60 $this->addWarning( [ 'apiwarn-invalidcategory', wfEscapeWikiText( $cat ) ] );
61 } else {
62 $cats[] = $title->getDBkey();
63 }
64 }
65
66 if ( !$cats ) {
67 // No titles so no results
68 return;
69 }
70 }
71
72 $filteredRows = [];
73 $hiddenCategories = [];
74 $needHidden = isset( $prop['hidden'] ) || isset( $show['!hidden'] ) || isset( $show['hidden'] );
75 $needFiltering = isset( $show['hidden'] ) || isset( $show['!hidden'] );
76
77 $continueFrom = null;
78 if ( $params['continue'] !== null ) {
79 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'string' ] );
80 $continueFrom = [ $cont[0], $cont[1] ];
81 }
82
83 $db = $this->getDB();
84
85 $this->setVirtualDomain( CategoryLinksTable::VIRTUAL_DOMAIN );
86 $categoryLinksDb = $this->getDB();
87
88 $fields = [ 'cl_from', 'lt_title' ];
89 if ( isset( $prop['sortkey'] ) ) {
90 $fields[] = 'cl_sortkey';
91 $fields[] = 'cl_sortkey_prefix';
92 }
93 if ( isset( $prop['timestamp'] ) ) {
94 $fields[] = 'cl_timestamp';
95 }
96
97 $isFirstBatch = true;
98 $loopCount = 0;
99 $maxLoops = 20;
100 while ( count( $filteredRows ) < $params['limit'] + 1 ) {
101 if ( $loopCount > $maxLoops ) {
102 // Safety limit to prevent excessive iterations
103 break;
104 }
105
106 $queryBuilder = $categoryLinksDb->newSelectQueryBuilder()
107 ->select( $fields )
108 ->from( 'categorylinks' )
109 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
110 ->where( [ 'cl_from' => array_keys( $pages ), 'lt_namespace' => NS_CATEGORY ] )
111 ->caller( __METHOD__ );
112
113 if ( $cats !== [] ) {
114 $queryBuilder->andWhere( [ 'lt_title' => $cats ] );
115 }
116
117 if ( $continueFrom !== null ) {
118 // Use strict comparison for subsequent batches to skip the continue row
119 if ( $isFirstBatch ) {
120 $op = $params['dir'] == 'descending' ? '<=' : '>=';
121 } else {
122 $op = $params['dir'] == 'descending' ? '<' : '>';
123 }
124 $queryBuilder->andWhere( $categoryLinksDb->buildComparison( $op, [
125 'cl_from' => $continueFrom[0],
126 'lt_title' => $continueFrom[1],
127 ] ) );
128 }
129
130 $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
131 if ( count( $pages ) === 1 ) {
132 $queryBuilder->orderBy( 'lt_title' . $sort );
133 } else {
134 $queryBuilder->orderBy( [ 'cl_from' . $sort, 'lt_title' . $sort ] );
135 }
136 $queryBuilder->limit( $params['limit'] + 1 );
137
138 $res = $queryBuilder->fetchResultSet();
139
140 $isFirstBatch = false;
141
142 $batchRows = [];
143 $categories = [];
144
145 foreach ( $res as $row ) {
146 $batchRows[] = $row;
147 $categories[] = $row->lt_title;
148 }
149
150 if ( $categories === [] ) {
151 // No more rows available
152 break;
153 }
154
155 if ( $needHidden ) {
156 $hiddenQueryBuilder = $db->newSelectQueryBuilder()
157 ->select( [ 'pp_page', 'pp_propname', 'page_title' ] )
158 ->from( 'page_props' )
159 ->join( 'page', null, 'page_id = pp_page' )
160 ->where( [
161 'pp_propname' => 'hiddencat',
162 'page_namespace' => NS_CATEGORY,
163 'page_title' => $categories
164 ] )
165 ->caller( __METHOD__ );
166
167 $hiddenRes = $hiddenQueryBuilder->fetchResultSet();
168
169 foreach ( $hiddenRes as $hiddenRow ) {
170 $hiddenCategories[$hiddenRow->page_title] = true;
171 }
172 }
173
174 if ( $needFiltering ) {
175 foreach ( $batchRows as $row ) {
176 if ( isset( $show['hidden'] ) === isset( $hiddenCategories[$row->lt_title] ) ) {
177 $filteredRows[] = $row;
178 }
179 }
180 } else {
181 $filteredRows = array_merge( $filteredRows, $batchRows );
182 }
183
184 if ( count( $batchRows ) < $params['limit'] + 1 ) {
185 break;
186 }
187
188 $loopCount++;
189 $lastRow = end( $batchRows );
190 $continueFrom = [ $lastRow->cl_from, $lastRow->lt_title ];
191 }
192
193 $this->resetVirtualDomain();
194
195 $count = 0;
196 if ( $resultPageSet === null ) {
197 foreach ( $filteredRows as $row ) {
198 if ( ++$count > $params['limit'] ) {
199 // We've reached the one extra which shows that
200 // there are additional pages to be had. Stop here...
201 $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->lt_title );
202 break;
203 }
204
205 $title = Title::makeTitle( NS_CATEGORY, $row->lt_title );
206 $vals = [];
207 ApiQueryBase::addTitleInfo( $vals, $title );
208 if ( isset( $prop['sortkey'] ) ) {
209 $vals['sortkey'] = bin2hex( $row->cl_sortkey );
210 $vals['sortkeyprefix'] = $row->cl_sortkey_prefix;
211 }
212 if ( isset( $prop['timestamp'] ) ) {
213 $vals['timestamp'] = wfTimestamp( TS::ISO_8601, $row->cl_timestamp );
214 }
215 if ( isset( $prop['hidden'] ) ) {
216 $vals['hidden'] = isset( $hiddenCategories[$row->lt_title] );
217 }
218
219 $fit = $this->addPageSubItem( $row->cl_from, $vals );
220 if ( !$fit ) {
221 $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->lt_title );
222 break;
223 }
224 }
225 } else {
226 $titles = [];
227 foreach ( $filteredRows as $row ) {
228 if ( ++$count > $params['limit'] ) {
229 // We've reached the one extra which shows that
230 // there are additional pages to be had. Stop here...
231 $this->setContinueEnumParameter( 'continue', $row->cl_from . '|' . $row->lt_title );
232 break;
233 }
234
235 $titles[] = Title::makeTitle( NS_CATEGORY, $row->lt_title );
236 }
237 $resultPageSet->populateFromTitles( $titles );
238 }
239 }
240
242 public function getAllowedParams() {
243 return [
244 'prop' => [
245 ParamValidator::PARAM_ISMULTI => true,
246 ParamValidator::PARAM_TYPE => [
247 'sortkey',
248 'timestamp',
249 'hidden',
250 ],
252 ],
253 'show' => [
254 ParamValidator::PARAM_ISMULTI => true,
255 ParamValidator::PARAM_TYPE => [
256 'hidden',
257 '!hidden',
258 ]
259 ],
260 'limit' => [
261 ParamValidator::PARAM_DEFAULT => 10,
262 ParamValidator::PARAM_TYPE => 'limit',
263 IntegerDef::PARAM_MIN => 1,
264 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
265 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
266 ],
267 'continue' => [
268 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
269 ],
270 'categories' => [
271 ParamValidator::PARAM_ISMULTI => true,
272 ],
273 'dir' => [
274 ParamValidator::PARAM_DEFAULT => 'ascending',
275 ParamValidator::PARAM_TYPE => [
276 'ascending',
277 'descending'
278 ]
279 ],
280 ];
281 }
282
284 protected function getExamplesMessages() {
285 return [
286 'action=query&prop=categories&titles=Albert%20Einstein'
287 => 'apihelp-query+categories-example-simple',
288 'action=query&generator=categories&titles=Albert%20Einstein&prop=info'
289 => 'apihelp-query+categories-example-generator',
290 ];
291 }
292
294 public function getHelpUrls() {
295 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Categories';
296 }
297}
298
300class_alias( ApiQueryCategories::class, 'ApiQueryCategories' );
const NS_CATEGORY
Definition Defines.php:65
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1707
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:206
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1439
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:166
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:233
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:837
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:231
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
addPageSubItem( $pageId, $item, $elemname=null)
Same as addPageSubItems(), but one element of $data at a time.
resetVirtualDomain()
Reset the virtual domain to the main database.
setVirtualDomain(string|false $virtualDomain)
Set the Query database connection (read-only)
getDB()
Get the Query database connection (read-only).
A query module to enumerate categories the set of pages belong to.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
executeGenerator( $resultPageSet)
Execute this module as a generator.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
__construct(ApiQuery $query, string $moduleName)
getCacheMode( $params)
Get the cache mode for the data generated by this module.Override this in the module subclass....
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
getPageSet()
Get the PageSet object to work on.
This is the main query class.
Definition ApiQuery.php:36
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Represents a title within MediaWiki.
Definition Title.php:69
Service for formatting and validating API parameters.
Type definition for integer types.
array $params
The job parameters.