Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 137 |
|
0.00% |
0 / 32 |
CRAP | |
0.00% |
0 / 1 |
ApiQueryBase | |
0.00% |
0 / 137 |
|
0.00% |
0 / 32 |
5402 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getCacheMode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requestExtraData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getParent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDB | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getPageSet | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
resetQueryParams | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryBuilder | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
addTables | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
addJoinConds | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
addFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addFieldsIf | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
addWhere | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
addWhereIf | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
addWhereFld | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
20 | |||
addWhereIDsFld | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
addWhereRange | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
56 | |||
addTimestampWhereRange | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
addOption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
select | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
90 | |||
processRow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addTitleInfo | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
addPageSubItems | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
addPageSubItem | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
setContinueEnumParameter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
titlePartToKey | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
parsePrefixedTitlePart | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
42 | |||
validateSha1Hash | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateSha1Base36Hash | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
userCanSeeRevDel | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
executeGenderCacheFromResultWrapper | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | /** |
3 | * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use MediaWiki\MediaWikiServices; |
24 | use MediaWiki\Title\MalformedTitleException; |
25 | use MediaWiki\Title\Title; |
26 | use MediaWiki\Title\TitleValue; |
27 | use Wikimedia\Rdbms\IDatabase; |
28 | use Wikimedia\Rdbms\IExpression; |
29 | use Wikimedia\Rdbms\IReadableDatabase; |
30 | use Wikimedia\Rdbms\IResultWrapper; |
31 | use Wikimedia\Rdbms\SelectQueryBuilder; |
32 | |
33 | /** |
34 | * This is a base class for all Query modules. |
35 | * It provides some common functionality such as constructing various SQL |
36 | * queries. |
37 | * |
38 | * @stable to extend |
39 | * |
40 | * @ingroup API |
41 | */ |
42 | abstract class ApiQueryBase extends ApiBase { |
43 | use ApiQueryBlockInfoTrait; |
44 | |
45 | private ApiQuery $mQueryModule; |
46 | private ?IReadableDatabase $mDb; |
47 | |
48 | /** |
49 | * @var SelectQueryBuilder|null |
50 | */ |
51 | private $queryBuilder; |
52 | |
53 | /** |
54 | * @stable to call |
55 | * @param ApiQuery $queryModule |
56 | * @param string $moduleName |
57 | * @param string $paramPrefix |
58 | */ |
59 | public function __construct( ApiQuery $queryModule, $moduleName, $paramPrefix = '' ) { |
60 | parent::__construct( $queryModule->getMain(), $moduleName, $paramPrefix ); |
61 | $this->mQueryModule = $queryModule; |
62 | $this->mDb = null; |
63 | $this->resetQueryParams(); |
64 | } |
65 | |
66 | /***************************************************************************/ |
67 | // region Methods to implement |
68 | /** @name Methods to implement */ |
69 | |
70 | /** |
71 | * Get the cache mode for the data generated by this module. Override |
72 | * this in the module subclass. For possible return values and other |
73 | * details about cache modes, see ApiMain::setCacheMode() |
74 | * |
75 | * Public caching will only be allowed if *all* the modules that supply |
76 | * data for a given request return a cache mode of public. |
77 | * |
78 | * @stable to override |
79 | * @param array $params |
80 | * @return string |
81 | */ |
82 | public function getCacheMode( $params ) { |
83 | return 'private'; |
84 | } |
85 | |
86 | /** |
87 | * Override this method to request extra fields from the pageSet |
88 | * using $pageSet->requestField('fieldName') |
89 | * |
90 | * Note this only makes sense for 'prop' modules, as 'list' and 'meta' |
91 | * modules should not be using the pageset. |
92 | * |
93 | * @stable to override |
94 | * @param ApiPageSet $pageSet |
95 | */ |
96 | public function requestExtraData( $pageSet ) { |
97 | } |
98 | |
99 | // endregion -- end of methods to implement |
100 | |
101 | /***************************************************************************/ |
102 | // region Data access |
103 | /** @name Data access */ |
104 | |
105 | /** |
106 | * Get the main Query module |
107 | * @return ApiQuery |
108 | */ |
109 | public function getQuery() { |
110 | return $this->mQueryModule; |
111 | } |
112 | |
113 | /** @inheritDoc */ |
114 | public function getParent() { |
115 | return $this->getQuery(); |
116 | } |
117 | |
118 | /** |
119 | * Get the Query database connection (read-only) |
120 | * @stable to override |
121 | * @return IReadableDatabase |
122 | */ |
123 | protected function getDB() { |
124 | $this->mDb ??= $this->getQuery()->getDB(); |
125 | |
126 | return $this->mDb; |
127 | } |
128 | |
129 | /** |
130 | * Get the PageSet object to work on |
131 | * @stable to override |
132 | * @return ApiPageSet |
133 | */ |
134 | protected function getPageSet() { |
135 | return $this->getQuery()->getPageSet(); |
136 | } |
137 | |
138 | // endregion -- end of data access |
139 | |
140 | /***************************************************************************/ |
141 | // region Querying |
142 | /** @name Querying */ |
143 | |
144 | /** |
145 | * Blank the internal arrays with query parameters |
146 | */ |
147 | protected function resetQueryParams() { |
148 | $this->queryBuilder = null; |
149 | } |
150 | |
151 | /** |
152 | * Get the SelectQueryBuilder. |
153 | * |
154 | * This is lazy initialised since getDB() fails in ApiQueryAllImages if it |
155 | * is called before the constructor completes. |
156 | * |
157 | * @return SelectQueryBuilder |
158 | */ |
159 | protected function getQueryBuilder() { |
160 | $this->queryBuilder ??= $this->getDB()->newSelectQueryBuilder(); |
161 | return $this->queryBuilder; |
162 | } |
163 | |
164 | /** |
165 | * Add a set of tables to the internal array |
166 | * @param string|array $tables Table name or array of table names |
167 | * or nested arrays for joins using parentheses for grouping |
168 | * @param string|null $alias Table alias, or null for no alias. Cannot be |
169 | * used with multiple tables |
170 | */ |
171 | protected function addTables( $tables, $alias = null ) { |
172 | if ( is_array( $tables ) ) { |
173 | if ( $alias !== null ) { |
174 | ApiBase::dieDebug( __METHOD__, 'Multiple table aliases not supported' ); |
175 | } |
176 | $this->getQueryBuilder()->rawTables( $tables ); |
177 | } else { |
178 | $this->getQueryBuilder()->table( $tables, $alias ); |
179 | } |
180 | } |
181 | |
182 | /** |
183 | * Add a set of JOIN conditions to the internal array |
184 | * |
185 | * JOIN conditions are formatted as [ tablename => [ jointype, conditions ] ] |
186 | * e.g. [ 'page' => [ 'LEFT JOIN', 'page_id=rev_page' ] ]. |
187 | * Conditions may be a string or an addWhere()-style array. |
188 | * @param array $join_conds JOIN conditions |
189 | */ |
190 | protected function addJoinConds( $join_conds ) { |
191 | if ( !is_array( $join_conds ) ) { |
192 | ApiBase::dieDebug( __METHOD__, 'Join conditions have to be arrays' ); |
193 | } |
194 | $this->getQueryBuilder()->joinConds( $join_conds ); |
195 | } |
196 | |
197 | /** |
198 | * Add a set of fields to select to the internal array |
199 | * @param array|string $value Field name or array of field names |
200 | */ |
201 | protected function addFields( $value ) { |
202 | $this->getQueryBuilder()->fields( $value ); |
203 | } |
204 | |
205 | /** |
206 | * Same as addFields(), but add the fields only if a condition is met |
207 | * @param array|string $value See addFields() |
208 | * @param bool $condition If false, do nothing |
209 | * @return bool |
210 | */ |
211 | protected function addFieldsIf( $value, $condition ) { |
212 | if ( $condition ) { |
213 | $this->addFields( $value ); |
214 | |
215 | return true; |
216 | } |
217 | |
218 | return false; |
219 | } |
220 | |
221 | /** |
222 | * Add a set of WHERE clauses to the internal array. |
223 | * |
224 | * The array should be appropriate for passing as $conds to |
225 | * IDatabase::select(). Arrays from multiple calls are merged with |
226 | * array_merge(). A string is treated as a single-element array. |
227 | * |
228 | * When passing `'field' => $arrayOfIDs` where the IDs are taken from user |
229 | * input, consider using addWhereIDsFld() instead. |
230 | * |
231 | * @see IDatabase::select() |
232 | * @param string|array|IExpression $value |
233 | */ |
234 | protected function addWhere( $value ) { |
235 | if ( is_array( $value ) ) { |
236 | // Double check: don't insert empty arrays, |
237 | // Database::makeList() chokes on them |
238 | if ( count( $value ) ) { |
239 | $this->getQueryBuilder()->where( $value ); |
240 | } |
241 | } else { |
242 | $this->getQueryBuilder()->where( $value ); |
243 | } |
244 | } |
245 | |
246 | /** |
247 | * Same as addWhere(), but add the WHERE clauses only if a condition is met |
248 | * @param string|array|IExpression $value |
249 | * @param bool $condition If false, do nothing |
250 | * @return bool |
251 | */ |
252 | protected function addWhereIf( $value, $condition ) { |
253 | if ( $condition ) { |
254 | $this->addWhere( $value ); |
255 | |
256 | return true; |
257 | } |
258 | |
259 | return false; |
260 | } |
261 | |
262 | /** |
263 | * Equivalent to addWhere( [ $field => $value ] ) |
264 | * |
265 | * When $value is an array of integer IDs taken from user input, |
266 | * consider using addWhereIDsFld() instead. |
267 | * |
268 | * @param string $field Field name |
269 | * @param int|string|(string|int|null)[] $value Value; ignored if null or empty array |
270 | */ |
271 | protected function addWhereFld( $field, $value ) { |
272 | if ( $value !== null && !( is_array( $value ) && !$value ) ) { |
273 | $this->getQueryBuilder()->where( [ $field => $value ] ); |
274 | } |
275 | } |
276 | |
277 | /** |
278 | * Like addWhereFld for an integer list of IDs |
279 | * |
280 | * When passed wildly out-of-range values for integer comparison, |
281 | * the database may choose a poor query plan. This method validates the |
282 | * passed IDs against the range of values in the database to omit |
283 | * out-of-range values. |
284 | * |
285 | * This should be used when the IDs are derived from arbitrary user input; |
286 | * it is not necessary if the IDs are already known to be within a sensible |
287 | * range. |
288 | * |
289 | * This should not be used when there is not a suitable index on $field to |
290 | * quickly retrieve the minimum and maximum values. |
291 | * |
292 | * @since 1.33 |
293 | * @param string $table Table name |
294 | * @param string $field Field name |
295 | * @param int[] $ids |
296 | * @return int Count of IDs actually included |
297 | */ |
298 | protected function addWhereIDsFld( $table, $field, $ids ) { |
299 | // Use count() to its full documented capabilities to simultaneously |
300 | // test for null, empty array or empty countable object |
301 | if ( count( $ids ) ) { |
302 | $ids = $this->filterIDs( [ [ $table, $field ] ], $ids ); |
303 | |
304 | if ( $ids === [] ) { |
305 | // Return nothing, no IDs are valid |
306 | $this->getQueryBuilder()->where( '0 = 1' ); |
307 | } else { |
308 | $this->getQueryBuilder()->where( [ $field => $ids ] ); |
309 | } |
310 | } |
311 | return count( $ids ); |
312 | } |
313 | |
314 | /** |
315 | * Add a WHERE clause corresponding to a range, and an ORDER BY |
316 | * clause to sort in the right direction |
317 | * @param string $field Field name |
318 | * @param string $dir If 'newer', sort in ascending order, otherwise |
319 | * sort in descending order |
320 | * @param string|int|null $start Value to start the list at. If $dir == 'newer' |
321 | * this is the lower boundary, otherwise it's the upper boundary |
322 | * @param string|int|null $end Value to end the list at. If $dir == 'newer' this |
323 | * is the upper boundary, otherwise it's the lower boundary |
324 | * @param bool $sort If false, don't add an ORDER BY clause |
325 | */ |
326 | protected function addWhereRange( $field, $dir, $start, $end, $sort = true ) { |
327 | $isDirNewer = ( $dir === 'newer' ); |
328 | $after = ( $isDirNewer ? '>=' : '<=' ); |
329 | $before = ( $isDirNewer ? '<=' : '>=' ); |
330 | $db = $this->getDB(); |
331 | |
332 | if ( $start !== null ) { |
333 | $this->addWhere( $db->expr( $field, $after, $start ) ); |
334 | } |
335 | |
336 | if ( $end !== null ) { |
337 | $this->addWhere( $db->expr( $field, $before, $end ) ); |
338 | } |
339 | |
340 | if ( $sort ) { |
341 | $this->getQueryBuilder()->orderBy( $field, $isDirNewer ? null : 'DESC' ); |
342 | } |
343 | } |
344 | |
345 | /** |
346 | * Add a WHERE clause corresponding to a range, similar to addWhereRange, |
347 | * but converts $start and $end to database timestamps. |
348 | * @see addWhereRange |
349 | * @param string $field |
350 | * @param string $dir |
351 | * @param string|int|null $start |
352 | * @param string|int|null $end |
353 | * @param bool $sort |
354 | */ |
355 | protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) { |
356 | $db = $this->getDB(); |
357 | $this->addWhereRange( $field, $dir, |
358 | $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort ); |
359 | } |
360 | |
361 | /** |
362 | * Add an option such as LIMIT or USE INDEX. If an option was set |
363 | * before, the old value will be overwritten |
364 | * @param string $name Option name |
365 | * @param mixed $value The option value, or null for a boolean option |
366 | */ |
367 | protected function addOption( $name, $value = null ) { |
368 | $this->getQueryBuilder()->option( $name, $value ); |
369 | } |
370 | |
371 | /** |
372 | * Execute a SELECT query based on the values in the internal arrays |
373 | * @param string $method Function the query should be attributed to. |
374 | * You should usually use __METHOD__ here |
375 | * @param array $extraQuery Query data to add but not store in the object |
376 | * Format is [ |
377 | * 'tables' => ..., |
378 | * 'fields' => ..., |
379 | * 'where' => ..., |
380 | * 'options' => ..., |
381 | * 'join_conds' => ... |
382 | * ] |
383 | * @param array|null &$hookData If set, the ApiQueryBaseBeforeQuery and |
384 | * ApiQueryBaseAfterQuery hooks will be called, and the |
385 | * ApiQueryBaseProcessRow hook will be expected. |
386 | * @return IResultWrapper |
387 | */ |
388 | protected function select( $method, $extraQuery = [], array &$hookData = null ) { |
389 | $queryBuilder = clone $this->getQueryBuilder(); |
390 | if ( isset( $extraQuery['tables'] ) ) { |
391 | $queryBuilder->rawTables( (array)$extraQuery['tables'] ); |
392 | } |
393 | if ( isset( $extraQuery['fields'] ) ) { |
394 | $queryBuilder->fields( (array)$extraQuery['fields'] ); |
395 | } |
396 | if ( isset( $extraQuery['where'] ) ) { |
397 | $queryBuilder->where( (array)$extraQuery['where'] ); |
398 | } |
399 | if ( isset( $extraQuery['options'] ) ) { |
400 | $queryBuilder->options( (array)$extraQuery['options'] ); |
401 | } |
402 | if ( isset( $extraQuery['join_conds'] ) ) { |
403 | $queryBuilder->joinConds( (array)$extraQuery['join_conds'] ); |
404 | } |
405 | |
406 | if ( $hookData !== null && $this->getHookContainer()->isRegistered( 'ApiQueryBaseBeforeQuery' ) ) { |
407 | $info = $queryBuilder->getQueryInfo(); |
408 | $this->getHookRunner()->onApiQueryBaseBeforeQuery( |
409 | $this, $info['tables'], $info['fields'], $info['conds'], |
410 | $info['options'], $info['join_conds'], $hookData |
411 | ); |
412 | $queryBuilder = $this->getDB()->newSelectQueryBuilder()->queryInfo( $info ); |
413 | } |
414 | |
415 | $queryBuilder->caller( $method ); |
416 | $res = $queryBuilder->fetchResultSet(); |
417 | |
418 | if ( $hookData !== null ) { |
419 | $this->getHookRunner()->onApiQueryBaseAfterQuery( $this, $res, $hookData ); |
420 | } |
421 | |
422 | return $res; |
423 | } |
424 | |
425 | /** |
426 | * Call the ApiQueryBaseProcessRow hook |
427 | * |
428 | * Generally, a module that passed $hookData to self::select() will call |
429 | * this just before calling ApiResult::addValue(), and treat a false return |
430 | * here in the same way it treats a false return from addValue(). |
431 | * |
432 | * @since 1.28 |
433 | * @param stdClass $row Database row |
434 | * @param array &$data Data to be added to the result |
435 | * @param array &$hookData Hook data from ApiQueryBase::select() @phan-output-reference |
436 | * @return bool Return false if row processing should end with continuation |
437 | */ |
438 | protected function processRow( $row, array &$data, array &$hookData ) { |
439 | return $this->getHookRunner()->onApiQueryBaseProcessRow( $this, $row, $data, $hookData ); |
440 | } |
441 | |
442 | // endregion -- end of querying |
443 | |
444 | /***************************************************************************/ |
445 | // region Utility methods |
446 | /** @name Utility methods */ |
447 | |
448 | /** |
449 | * Add information (title and namespace) about a Title object to a |
450 | * result array |
451 | * @param array &$arr Result array à la ApiResult |
452 | * @param Title $title |
453 | * @param string $prefix Module prefix |
454 | */ |
455 | public static function addTitleInfo( &$arr, $title, $prefix = '' ) { |
456 | $arr[$prefix . 'ns'] = $title->getNamespace(); |
457 | $arr[$prefix . 'title'] = $title->getPrefixedText(); |
458 | } |
459 | |
460 | /** |
461 | * Add a sub-element under the page element with the given page ID |
462 | * @param int $pageId |
463 | * @param array $data Data array à la ApiResult |
464 | * @return bool Whether the element fit in the result |
465 | */ |
466 | protected function addPageSubItems( $pageId, $data ) { |
467 | $result = $this->getResult(); |
468 | ApiResult::setIndexedTagName( $data, $this->getModulePrefix() ); |
469 | |
470 | return $result->addValue( [ 'query', 'pages', (int)$pageId ], |
471 | $this->getModuleName(), |
472 | $data ); |
473 | } |
474 | |
475 | /** |
476 | * Same as addPageSubItems(), but one element of $data at a time |
477 | * @param int $pageId |
478 | * @param mixed $item Data à la ApiResult |
479 | * @param string|null $elemname XML element name. If null, getModuleName() |
480 | * is used |
481 | * @return bool Whether the element fit in the result |
482 | */ |
483 | protected function addPageSubItem( $pageId, $item, $elemname = null ) { |
484 | $result = $this->getResult(); |
485 | $fit = $result->addValue( [ 'query', 'pages', $pageId, |
486 | $this->getModuleName() ], null, $item ); |
487 | if ( !$fit ) { |
488 | return false; |
489 | } |
490 | $result->addIndexedTagName( |
491 | [ 'query', 'pages', $pageId, $this->getModuleName() ], |
492 | $elemname ?? $this->getModulePrefix() |
493 | ); |
494 | |
495 | return true; |
496 | } |
497 | |
498 | /** |
499 | * Set a query-continue value |
500 | * @param string $paramName Parameter name |
501 | * @param int|string|array $paramValue Parameter value |
502 | */ |
503 | protected function setContinueEnumParameter( $paramName, $paramValue ) { |
504 | $this->getContinuationManager()->addContinueParam( $this, $paramName, $paramValue ); |
505 | } |
506 | |
507 | /** |
508 | * Convert an input title or title prefix into a dbkey. |
509 | * |
510 | * $namespace should always be specified in order to handle per-namespace |
511 | * capitalization settings. |
512 | * |
513 | * @param string $titlePart |
514 | * @param int $namespace Namespace of the title |
515 | * @return string DBkey (no namespace prefix) |
516 | */ |
517 | public function titlePartToKey( $titlePart, $namespace = NS_MAIN ) { |
518 | $t = Title::makeTitleSafe( $namespace, $titlePart . 'x' ); |
519 | if ( !$t || $t->hasFragment() ) { |
520 | // Invalid title (e.g. bad chars) or contained a '#'. |
521 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] ); |
522 | } |
523 | if ( $namespace != $t->getNamespace() || $t->isExternal() ) { |
524 | // This can happen in two cases. First, if you call titlePartToKey with a title part |
525 | // that looks like a namespace, but with $defaultNamespace = NS_MAIN. It would be very |
526 | // difficult to handle such a case. Such cases cannot exist and are therefore treated |
527 | // as invalid user input. The second case is when somebody specifies a title interwiki |
528 | // prefix. |
529 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] ); |
530 | } |
531 | |
532 | return substr( $t->getDBkey(), 0, -1 ); |
533 | } |
534 | |
535 | /** |
536 | * Convert an input title or title prefix into a TitleValue. |
537 | * |
538 | * @since 1.35 |
539 | * @param string $titlePart |
540 | * @param int $defaultNamespace Default namespace if none is given |
541 | * @return TitleValue |
542 | */ |
543 | protected function parsePrefixedTitlePart( $titlePart, $defaultNamespace = NS_MAIN ) { |
544 | try { |
545 | $titleParser = MediaWikiServices::getInstance()->getTitleParser(); |
546 | $t = $titleParser->parseTitle( $titlePart . 'X', $defaultNamespace ); |
547 | } catch ( MalformedTitleException $e ) { |
548 | $t = null; |
549 | } |
550 | |
551 | if ( !$t || $t->hasFragment() || $t->isExternal() || $t->getDBkey() === 'X' ) { |
552 | // Invalid title (e.g. bad chars) or contained a '#'. |
553 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $titlePart ) ] ); |
554 | } |
555 | |
556 | return new TitleValue( $t->getNamespace(), substr( $t->getDBkey(), 0, -1 ) ); |
557 | } |
558 | |
559 | /** |
560 | * @param string $hash |
561 | * @return bool |
562 | */ |
563 | public function validateSha1Hash( $hash ) { |
564 | return (bool)preg_match( '/^[a-f0-9]{40}$/', $hash ); |
565 | } |
566 | |
567 | /** |
568 | * @param string $hash |
569 | * @return bool |
570 | */ |
571 | public function validateSha1Base36Hash( $hash ) { |
572 | return (bool)preg_match( '/^[a-z0-9]{31}$/', $hash ); |
573 | } |
574 | |
575 | /** |
576 | * Check whether the current user has permission to view revision-deleted |
577 | * fields. |
578 | * @return bool |
579 | */ |
580 | public function userCanSeeRevDel() { |
581 | return $this->getAuthority()->isAllowedAny( |
582 | 'deletedhistory', |
583 | 'deletedtext', |
584 | 'deleterevision', |
585 | 'suppressrevision', |
586 | 'viewsuppressed' |
587 | ); |
588 | } |
589 | |
590 | /** |
591 | * Preprocess the result set to fill the GenderCache with the necessary information |
592 | * before using self::addTitleInfo |
593 | * |
594 | * @param IResultWrapper $res Result set to work on. |
595 | * The result set must have _namespace and _title fields with the provided field prefix |
596 | * @param string $fname The caller function name, always use __METHOD__ |
597 | * @param string $fieldPrefix Prefix for fields to check gender for |
598 | */ |
599 | protected function executeGenderCacheFromResultWrapper( |
600 | IResultWrapper $res, $fname = __METHOD__, $fieldPrefix = 'page' |
601 | ) { |
602 | if ( !$res->numRows() ) { |
603 | return; |
604 | } |
605 | |
606 | $services = MediaWikiServices::getInstance(); |
607 | if ( !$services->getContentLanguage()->needsGenderDistinction() ) { |
608 | return; |
609 | } |
610 | |
611 | $nsInfo = $services->getNamespaceInfo(); |
612 | $namespaceField = $fieldPrefix . '_namespace'; |
613 | $titleField = $fieldPrefix . '_title'; |
614 | |
615 | $usernames = []; |
616 | foreach ( $res as $row ) { |
617 | if ( $nsInfo->hasGenderDistinction( $row->$namespaceField ) ) { |
618 | $usernames[] = $row->$titleField; |
619 | } |
620 | } |
621 | |
622 | if ( $usernames === [] ) { |
623 | return; |
624 | } |
625 | |
626 | $genderCache = $services->getGenderCache(); |
627 | $genderCache->doQuery( $usernames, $fname ); |
628 | } |
629 | |
630 | // endregion -- end of utility methods |
631 | } |