Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
84.38% |
162 / 192 |
|
57.89% |
11 / 19 |
CRAP | |
0.00% |
0 / 1 |
LinkCache | |
84.82% |
162 / 191 |
|
57.89% |
11 / 19 |
99.30 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
setLogger | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCacheKey | |
54.84% |
17 / 31 |
|
0.00% |
0 / 1 |
25.26 | |||
getGoodLinkID | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
getGoodLinkFieldObj | |
92.31% |
24 / 26 |
|
0.00% |
0 / 1 |
13.08 | |||
isBadLink | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
addGoodLinkObjFromRow | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
addBadLinkObj | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
clearBadLink | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
4.59 | |||
clearLink | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getSelectFields | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
2.00 | |||
addLinkObj | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getGoodLinkRowInternal | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
10 | |||
getGoodLinkRow | |
66.67% |
14 / 21 |
|
0.00% |
0 / 1 |
7.33 | |||
getPersistentCacheKey | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
usePersistentCache | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
5.12 | |||
fetchPageRow | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
invalidateTitle | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
clear | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Page existence cache. |
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 | * @ingroup Cache |
22 | */ |
23 | |
24 | namespace MediaWiki\Cache; |
25 | |
26 | use DBAccessObjectUtils; |
27 | use IDBAccessObject; |
28 | use InvalidArgumentException; |
29 | use MapCacheLRU; |
30 | use MediaWiki\Linker\LinkTarget; |
31 | use MediaWiki\MainConfigNames; |
32 | use MediaWiki\MediaWikiServices; |
33 | use MediaWiki\Page\PageIdentity; |
34 | use MediaWiki\Page\PageReference; |
35 | use MediaWiki\Page\PageStoreRecord; |
36 | use MediaWiki\Title\NamespaceInfo; |
37 | use MediaWiki\Title\TitleFormatter; |
38 | use MediaWiki\Title\TitleValue; |
39 | use Psr\Log\LoggerAwareInterface; |
40 | use Psr\Log\LoggerInterface; |
41 | use Psr\Log\NullLogger; |
42 | use stdClass; |
43 | use WANObjectCache; |
44 | use Wikimedia\Rdbms\Database; |
45 | use Wikimedia\Rdbms\IDatabase; |
46 | use Wikimedia\Rdbms\ILoadBalancer; |
47 | |
48 | /** |
49 | * Cache for article titles (prefixed DB keys) and ids linked from one source |
50 | * |
51 | * @ingroup Cache |
52 | */ |
53 | class LinkCache implements LoggerAwareInterface { |
54 | /** @var MapCacheLRU */ |
55 | private $entries; |
56 | /** @var WANObjectCache */ |
57 | private $wanCache; |
58 | /** @var TitleFormatter */ |
59 | private $titleFormatter; |
60 | /** @var NamespaceInfo */ |
61 | private $nsInfo; |
62 | /** @var ILoadBalancer|null */ |
63 | private $loadBalancer; |
64 | /** @var LoggerInterface */ |
65 | private $logger; |
66 | |
67 | /** How many Titles to store */ |
68 | private const MAX_SIZE = 10000; |
69 | |
70 | /** Key to page row object or null */ |
71 | private const ROW = 0; |
72 | /** Key to query READ_* flags */ |
73 | private const FLAGS = 1; |
74 | |
75 | /** |
76 | * @param TitleFormatter $titleFormatter |
77 | * @param WANObjectCache $cache |
78 | * @param NamespaceInfo $nsInfo |
79 | * @param ILoadBalancer|null $loadBalancer Use null when no database is set up, for example on installation |
80 | */ |
81 | public function __construct( |
82 | TitleFormatter $titleFormatter, |
83 | WANObjectCache $cache, |
84 | NamespaceInfo $nsInfo, |
85 | ILoadBalancer $loadBalancer = null |
86 | ) { |
87 | $this->entries = new MapCacheLRU( self::MAX_SIZE ); |
88 | $this->wanCache = $cache; |
89 | $this->titleFormatter = $titleFormatter; |
90 | $this->nsInfo = $nsInfo; |
91 | $this->loadBalancer = $loadBalancer; |
92 | $this->logger = new NullLogger(); |
93 | } |
94 | |
95 | /** |
96 | * @param LoggerInterface $logger |
97 | */ |
98 | public function setLogger( LoggerInterface $logger ) { |
99 | $this->logger = $logger; |
100 | } |
101 | |
102 | /** |
103 | * @param LinkTarget|PageReference|array|string $page |
104 | * @param bool $passThrough Return $page if $page is a string |
105 | * @return ?string the cache key |
106 | */ |
107 | private function getCacheKey( $page, $passThrough = false ) { |
108 | if ( is_string( $page ) ) { |
109 | if ( $passThrough ) { |
110 | return $page; |
111 | } else { |
112 | throw new InvalidArgumentException( 'They key may not be given as a string here' ); |
113 | } |
114 | } |
115 | |
116 | if ( is_array( $page ) ) { |
117 | $namespace = $page['page_namespace']; |
118 | $dbkey = $page['page_title']; |
119 | return strtr( $this->titleFormatter->formatTitle( $namespace, $dbkey ), ' ', '_' ); |
120 | } |
121 | |
122 | if ( $page instanceof PageReference && $page->getWikiId() !== PageReference::LOCAL ) { |
123 | // No cross-wiki support yet. Perhaps LinkCache can become wiki-aware in the future. |
124 | $this->logger->info( |
125 | 'cross-wiki page reference', |
126 | [ |
127 | 'page-wiki' => $page->getWikiId(), |
128 | 'page-reference' => $this->titleFormatter->getFullText( $page ) |
129 | ] |
130 | ); |
131 | return null; |
132 | } |
133 | |
134 | if ( $page instanceof PageIdentity && !$page->canExist() ) { |
135 | // Non-proper page, perhaps a special page or interwiki link or relative section link. |
136 | $this->logger->warning( |
137 | 'non-proper page reference: {page-reference}', |
138 | [ 'page-reference' => $this->titleFormatter->getFullText( $page ) ] |
139 | ); |
140 | return null; |
141 | } |
142 | |
143 | if ( $page instanceof LinkTarget |
144 | && ( $page->isExternal() || $page->getText() === '' || $page->getNamespace() < 0 ) |
145 | ) { |
146 | // Interwiki link or relative section link. These do not have a page ID, so they |
147 | // can neither be "good" nor "bad" in the sense of this class. |
148 | $this->logger->warning( |
149 | 'link to non-proper page: {page-link}', |
150 | [ 'page-link' => $this->titleFormatter->getFullText( $page ) ] |
151 | ); |
152 | return null; |
153 | } |
154 | |
155 | return $this->titleFormatter->getPrefixedDBkey( $page ); |
156 | } |
157 | |
158 | /** |
159 | * Get the ID of a page known to the process cache |
160 | * |
161 | * @param LinkTarget|PageReference|array|string $page The page to get the ID for, |
162 | * as an object, an array containing the page_namespace and page_title fields, |
163 | * or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted. |
164 | * @return int Page ID, or zero if the page was not cached or does not exist or is not a |
165 | * proper page (e.g. a special page or an interwiki link). |
166 | */ |
167 | public function getGoodLinkID( $page ) { |
168 | $key = $this->getCacheKey( $page, true ); |
169 | if ( $key === null ) { |
170 | return 0; |
171 | } |
172 | |
173 | $entry = $this->entries->get( $key ); |
174 | if ( !$entry ) { |
175 | return 0; |
176 | } |
177 | |
178 | $row = $entry[self::ROW]; |
179 | |
180 | return $row ? (int)$row->page_id : 0; |
181 | } |
182 | |
183 | /** |
184 | * Get the field of a page known to the process cache |
185 | * |
186 | * If this link is not a cached good title, it will return NULL. |
187 | * @param LinkTarget|PageReference|array $page The page to get cached info for. |
188 | * Can be given as an object or an associative array containing the |
189 | * page_namespace and page_title fields. |
190 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
191 | * @param string $field ( 'id', 'length', 'redirect', 'revision', 'model', 'lang' ) |
192 | * @return string|int|null The field value, or null if the page was not cached or does not exist |
193 | * or is not a proper page (e.g. a special page or interwiki link). |
194 | */ |
195 | public function getGoodLinkFieldObj( $page, string $field ) { |
196 | $key = $this->getCacheKey( $page ); |
197 | if ( $key === null ) { |
198 | return null; |
199 | } |
200 | |
201 | $entry = $this->entries->get( $key ); |
202 | if ( !$entry ) { |
203 | return null; |
204 | } |
205 | |
206 | $row = $entry[self::ROW]; |
207 | if ( !$row ) { |
208 | return null; |
209 | } |
210 | |
211 | switch ( $field ) { |
212 | case 'id': |
213 | return (int)$row->page_id; |
214 | case 'length': |
215 | return (int)$row->page_len; |
216 | case 'redirect': |
217 | return (int)$row->page_is_redirect; |
218 | case 'revision': |
219 | return (int)$row->page_latest; |
220 | case 'model': |
221 | return !empty( $row->page_content_model ) |
222 | ? (string)$row->page_content_model |
223 | : null; |
224 | case 'lang': |
225 | return !empty( $row->page_lang ) |
226 | ? (string)$row->page_lang |
227 | : null; |
228 | default: |
229 | throw new InvalidArgumentException( "Unknown field: $field" ); |
230 | } |
231 | } |
232 | |
233 | /** |
234 | * Check if a page is known to be missing based on the process cache |
235 | * |
236 | * @param LinkTarget|PageReference|array|string $page The page to get cached info for, |
237 | * as an object, an array containing the page_namespace and page_title fields, |
238 | * or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted. |
239 | * In MediaWiki 1.36 and earlier, only a string was accepted. |
240 | * @return bool Whether the page is known to be missing based on the process cache |
241 | */ |
242 | public function isBadLink( $page ) { |
243 | $key = $this->getCacheKey( $page, true ); |
244 | if ( $key === null ) { |
245 | return false; |
246 | } |
247 | |
248 | $entry = $this->entries->get( $key ); |
249 | |
250 | return ( $entry && !$entry[self::ROW] ); |
251 | } |
252 | |
253 | /** |
254 | * Add information about an existing page to the process cache |
255 | * |
256 | * Callers must set the READ_LATEST flag if the row came from a DB_PRIMARY source. |
257 | * However, the use of such data is highly discouraged; most callers rely on seeing |
258 | * consistent DB_REPLICA data (e.g. REPEATABLE-READ point-in-time snapshots) and the |
259 | * accidental use of DB_PRIMARY data via LinkCache is prone to causing anomalies. |
260 | * |
261 | * @param LinkTarget|PageReference|array $page The page to set cached info for. |
262 | * Can be given as an object or an associative array containing the |
263 | * page_namespace and page_title fields. |
264 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
265 | * @param stdClass $row Object which has all fields returned by getSelectFields(). |
266 | * @param int $queryFlags The query flags used to retrieve the row, IDBAccessObject::READ_* |
267 | * @since 1.19 |
268 | */ |
269 | public function addGoodLinkObjFromRow( |
270 | $page, |
271 | stdClass $row, |
272 | int $queryFlags = IDBAccessObject::READ_NORMAL |
273 | ) { |
274 | $key = $this->getCacheKey( $page ); |
275 | if ( $key === null ) { |
276 | return; |
277 | } |
278 | |
279 | foreach ( self::getSelectFields() as $field ) { |
280 | if ( !property_exists( $row, $field ) ) { |
281 | throw new InvalidArgumentException( "Missing field: $field" ); |
282 | } |
283 | } |
284 | |
285 | $this->entries->set( $key, [ self::ROW => $row, self::FLAGS => $queryFlags ] ); |
286 | } |
287 | |
288 | /** |
289 | * Add information about a missing page to the process cache |
290 | * |
291 | * Callers must set the READ_LATEST flag if the row came from a DB_PRIMARY source. |
292 | * However, the use of such data is highly discouraged; most callers rely on seeing |
293 | * consistent DB_REPLICA data (e.g. REPEATABLE-READ point-in-time snapshots) and the |
294 | * accidental use of DB_PRIMARY data via LinkCache is prone to causing anomalies. |
295 | * |
296 | * @param LinkTarget|PageReference|array $page The page to set cached info for. |
297 | * Can be given as an object or an associative array containing the |
298 | * page_namespace and page_title fields. |
299 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
300 | * @param int $queryFlags The query flags used to retrieve the row, IDBAccessObject::READ_* |
301 | */ |
302 | public function addBadLinkObj( $page, int $queryFlags = IDBAccessObject::READ_NORMAL ) { |
303 | $key = $this->getCacheKey( $page ); |
304 | if ( $key === null ) { |
305 | return; |
306 | } |
307 | |
308 | $this->entries->set( $key, [ self::ROW => null, self::FLAGS => $queryFlags ] ); |
309 | } |
310 | |
311 | /** |
312 | * Clear information about a page being missing from the process cache |
313 | * |
314 | * @param LinkTarget|PageReference|array|string $page The page to clear cached info for, |
315 | * as an object, an array containing the page_namespace and page_title fields, |
316 | * or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted. |
317 | * In MediaWiki 1.36 and earlier, only a string was accepted. |
318 | */ |
319 | public function clearBadLink( $page ) { |
320 | $key = $this->getCacheKey( $page, true ); |
321 | if ( $key === null ) { |
322 | return; |
323 | } |
324 | |
325 | $entry = $this->entries->get( $key ); |
326 | if ( $entry && !$entry[self::ROW] ) { |
327 | $this->entries->clear( $key ); |
328 | } |
329 | } |
330 | |
331 | /** |
332 | * Clear information about a page from the process cache |
333 | * |
334 | * @param LinkTarget|PageReference|array $page The page to clear cached info for. |
335 | * Can be given as an object or an associative array containing the |
336 | * page_namespace and page_title fields. |
337 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
338 | */ |
339 | public function clearLink( $page ) { |
340 | $key = $this->getCacheKey( $page ); |
341 | if ( $key !== null ) { |
342 | $this->entries->clear( $key ); |
343 | } |
344 | } |
345 | |
346 | /** |
347 | * Fields that LinkCache needs to select |
348 | * |
349 | * @since 1.28 |
350 | * @return array |
351 | */ |
352 | public static function getSelectFields() { |
353 | $pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig() |
354 | ->get( MainConfigNames::PageLanguageUseDB ); |
355 | |
356 | $fields = array_merge( |
357 | PageStoreRecord::REQUIRED_FIELDS, |
358 | [ |
359 | 'page_len', |
360 | 'page_content_model', |
361 | ] |
362 | ); |
363 | |
364 | if ( $pageLanguageUseDB ) { |
365 | $fields[] = 'page_lang'; |
366 | } |
367 | |
368 | return $fields; |
369 | } |
370 | |
371 | /** |
372 | * Add a title to the link cache, return the page_id or zero if non-existent. |
373 | * This causes the link to be looked up in the database if it is not yet cached. |
374 | * |
375 | * @deprecated since 1.37, use PageStore::getPageForLink() instead. |
376 | * |
377 | * @param LinkTarget|PageReference|array $page The page to load. |
378 | * Can be given as an object or an associative array containing the |
379 | * page_namespace and page_title fields. |
380 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
381 | * @param int $queryFlags IDBAccessObject::READ_XXX |
382 | * |
383 | * @return int Page ID or zero |
384 | */ |
385 | public function addLinkObj( $page, int $queryFlags = IDBAccessObject::READ_NORMAL ) { |
386 | $row = $this->getGoodLinkRow( |
387 | $page->getNamespace(), |
388 | $page->getDBkey(), |
389 | [ $this, 'fetchPageRow' ], |
390 | $queryFlags |
391 | ); |
392 | |
393 | return $row ? (int)$row->page_id : 0; |
394 | } |
395 | |
396 | /** |
397 | * @param TitleValue $link |
398 | * @param callable|null $fetchCallback |
399 | * @param int $queryFlags |
400 | * @return array [ $shouldAddGoodLink, $row ], $shouldAddGoodLink is a bool indicating |
401 | * whether addGoodLinkObjFromRow should be called, and $row is the row the caller was looking |
402 | * for (or null, when it was not found). |
403 | */ |
404 | private function getGoodLinkRowInternal( |
405 | TitleValue $link, |
406 | callable $fetchCallback = null, |
407 | int $queryFlags = IDBAccessObject::READ_NORMAL |
408 | ): array { |
409 | $callerShouldAddGoodLink = false; |
410 | |
411 | $key = $this->getCacheKey( $link ); |
412 | if ( $key === null ) { |
413 | return [ $callerShouldAddGoodLink, null ]; |
414 | } |
415 | |
416 | $ns = $link->getNamespace(); |
417 | $dbkey = $link->getDBkey(); |
418 | |
419 | $entry = $this->entries->get( $key ); |
420 | if ( $entry && $entry[self::FLAGS] >= $queryFlags ) { |
421 | return [ $callerShouldAddGoodLink, $entry[self::ROW] ?: null ]; |
422 | } |
423 | |
424 | if ( !$fetchCallback ) { |
425 | return [ $callerShouldAddGoodLink, null ]; |
426 | } |
427 | |
428 | $callerShouldAddGoodLink = true; |
429 | |
430 | $wanCacheKey = $this->getPersistentCacheKey( $link ); |
431 | if ( $wanCacheKey !== null && !( $queryFlags & IDBAccessObject::READ_LATEST ) ) { |
432 | // Some pages are often transcluded heavily, so use persistent caching |
433 | $row = $this->wanCache->getWithSetCallback( |
434 | $wanCacheKey, |
435 | WANObjectCache::TTL_DAY, |
436 | function ( $curValue, &$ttl, array &$setOpts ) use ( $fetchCallback, $ns, $dbkey ) { |
437 | $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA ); |
438 | $setOpts += Database::getCacheSetOptions( $dbr ); |
439 | |
440 | $row = $fetchCallback( $dbr, $ns, $dbkey, [] ); |
441 | $mtime = $row ? (int)wfTimestamp( TS_UNIX, $row->page_touched ) : false; |
442 | $ttl = $this->wanCache->adaptiveTTL( $mtime, $ttl ); |
443 | |
444 | return $row; |
445 | } |
446 | ); |
447 | } else { |
448 | // No persistent caching needed, but we can still use the callback. |
449 | [ $mode, $options ] = DBAccessObjectUtils::getDBOptions( $queryFlags ); |
450 | $dbr = $this->loadBalancer->getConnectionRef( $mode ); |
451 | $row = $fetchCallback( $dbr, $ns, $dbkey, $options ); |
452 | } |
453 | |
454 | return [ $callerShouldAddGoodLink, $row ?: null ]; |
455 | } |
456 | |
457 | /** |
458 | * Returns the row for the page if the page exists (subject to race conditions). |
459 | * The row will be returned from local cache or WAN cache if possible, or it |
460 | * will be looked up using the callback provided. |
461 | * |
462 | * @param int $ns |
463 | * @param string $dbkey |
464 | * @param callable|null $fetchCallback A callback that will retrieve the link row with the |
465 | * signature ( IDatabase $db, int $ns, string $dbkey, array $queryOptions ): ?stdObj. |
466 | * @param int $queryFlags IDBAccessObject::READ_XXX |
467 | * |
468 | * @return stdClass|null |
469 | * @internal for use by PageStore. Other code should use a PageLookup instead. |
470 | */ |
471 | public function getGoodLinkRow( |
472 | int $ns, |
473 | string $dbkey, |
474 | callable $fetchCallback = null, |
475 | int $queryFlags = IDBAccessObject::READ_NORMAL |
476 | ): ?stdClass { |
477 | $link = TitleValue::tryNew( $ns, $dbkey ); |
478 | if ( $link === null ) { |
479 | return null; |
480 | } |
481 | |
482 | [ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal( |
483 | $link, |
484 | $fetchCallback, |
485 | $queryFlags |
486 | ); |
487 | |
488 | if ( $row ) { |
489 | if ( $shouldAddGoodLink ) { |
490 | try { |
491 | $this->addGoodLinkObjFromRow( $link, $row, $queryFlags ); |
492 | } catch ( InvalidArgumentException $e ) { |
493 | // a field is missing from $row; maybe we used a cache?; invalidate it and try again |
494 | $this->invalidateTitle( $link ); |
495 | [ , $row ] = $this->getGoodLinkRowInternal( |
496 | $link, |
497 | $fetchCallback, |
498 | $queryFlags |
499 | ); |
500 | $this->addGoodLinkObjFromRow( $link, $row, $queryFlags ); |
501 | } |
502 | } |
503 | } else { |
504 | $this->addBadLinkObj( $link ); |
505 | } |
506 | |
507 | return $row ?: null; |
508 | } |
509 | |
510 | /** |
511 | * @param LinkTarget|PageReference|TitleValue $page |
512 | * @return string|null |
513 | */ |
514 | private function getPersistentCacheKey( $page ) { |
515 | // if no key can be derived, the page isn't cacheable |
516 | if ( $this->getCacheKey( $page ) === null || !$this->usePersistentCache( $page ) ) { |
517 | return null; |
518 | } |
519 | |
520 | return $this->wanCache->makeKey( |
521 | 'page', |
522 | $page->getNamespace(), |
523 | sha1( $page->getDBkey() |
524 | ) ); |
525 | } |
526 | |
527 | /** |
528 | * @param LinkTarget|PageReference|int $pageOrNamespace |
529 | * @return bool |
530 | */ |
531 | private function usePersistentCache( $pageOrNamespace ) { |
532 | $ns = is_int( $pageOrNamespace ) ? $pageOrNamespace : $pageOrNamespace->getNamespace(); |
533 | if ( in_array( $ns, [ NS_TEMPLATE, NS_FILE, NS_CATEGORY, NS_MEDIAWIKI ] ) ) { |
534 | return true; |
535 | } |
536 | // Focus on transcluded pages more than the main content |
537 | if ( $this->nsInfo->isContent( $ns ) ) { |
538 | return false; |
539 | } |
540 | // Non-talk extension namespaces (e.g. NS_MODULE) |
541 | return ( $ns >= 100 && $this->nsInfo->isSubject( $ns ) ); |
542 | } |
543 | |
544 | /** |
545 | * @param IDatabase $db |
546 | * @param int $ns |
547 | * @param string $dbkey |
548 | * @param array $options Query options, see IDatabase::select() for details. |
549 | * @return stdClass|false |
550 | */ |
551 | private function fetchPageRow( IDatabase $db, int $ns, string $dbkey, $options = [] ) { |
552 | $queryBuilder = $db->newSelectQueryBuilder() |
553 | ->select( self::getSelectFields() ) |
554 | ->from( 'page' ) |
555 | ->where( [ 'page_namespace' => $ns, 'page_title' => $dbkey ] ) |
556 | ->options( $options ); |
557 | |
558 | return $queryBuilder->caller( __METHOD__ )->fetchRow(); |
559 | } |
560 | |
561 | /** |
562 | * Purge the persistent link cache for a title |
563 | * |
564 | * @param LinkTarget|PageReference $page |
565 | * In MediaWiki 1.36 and earlier, only LinkTarget was accepted. |
566 | * @since 1.28 |
567 | */ |
568 | public function invalidateTitle( $page ) { |
569 | $wanCacheKey = $this->getPersistentCacheKey( $page ); |
570 | if ( $wanCacheKey !== null ) { |
571 | $this->wanCache->delete( $wanCacheKey ); |
572 | } |
573 | |
574 | $this->clearLink( $page ); |
575 | } |
576 | |
577 | /** |
578 | * Clears cache |
579 | */ |
580 | public function clear() { |
581 | $this->entries->clear(); |
582 | } |
583 | } |
584 | |
585 | /** @deprecated class alias since 1.42 */ |
586 | class_alias( LinkCache::class, 'LinkCache' ); |