29use Psr\Log\LoggerAwareInterface;
30use Psr\Log\LoggerInterface;
31use Psr\Log\NullLogger;
50 private $mForUpdate =
false;
83 wfDeprecated( __METHOD__ .
' with no NamespaceInfo argument',
'1.34' );
84 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
86 $this->goodLinks =
new MapCacheLRU( self::MAX_SIZE );
87 $this->badLinks =
new MapCacheLRU( self::MAX_SIZE );
89 $this->titleFormatter = $titleFormatter;
90 $this->nsInfo = $nsInfo;
92 $this->logger =
new NullLogger();
98 public function setLogger( LoggerInterface $logger ) {
99 $this->logger = $logger;
111 return MediaWikiServices::getInstance()->getLinkCache();
127 return wfSetVar( $this->mForUpdate, $update );
137 if ( is_string( $page ) ) {
138 if ( $passThrough ) {
141 throw new InvalidArgumentException(
'They key may not be given as a string here' );
145 if ( is_array( $page ) ) {
146 $namespace = $page[
'page_namespace'];
147 $dbkey = $page[
'page_title'];
148 return strtr( $this->titleFormatter->formatTitle( $namespace, $dbkey ),
' ',
'_' );
154 'cross-wiki page reference',
156 'page-wiki' => $page->getWikiId(),
157 'page-reference' => $this->titleFormatter->getFullText( $page )
165 $this->logger->warning(
166 'non-proper page reference: {page-reference}',
167 [
'page-reference' => $this->titleFormatter->getFullText( $page ) ]
173 && ( $page->isExternal() || $page->getText() ===
'' || $page->getNamespace() < 0 )
177 $this->logger->warning(
178 'link to non-proper page: {page-link}',
179 [
'page-link' => $this->titleFormatter->getFullText( $page ) ]
184 return $this->titleFormatter->getPrefixedDBkey( $page );
199 if ( $key ===
null ) {
203 [ $row ] = $this->goodLinks->get( $key );
205 return $row ? (int)$row->page_id : 0;
222 if ( $key ===
null ) {
230 [ $row ] = $this->goodLinks->get( $key );
238 return intval( $row->page_id );
240 return intval( $row->page_len );
242 return intval( $row->page_is_redirect );
244 return intval( $row->page_latest );
246 return !empty( $row->page_content_model )
247 ? strval( $row->page_content_model )
250 return !empty( $row->page_lang )
251 ? strval( $row->page_lang )
254 return !empty( $row->page_restrictions )
255 ? strval( $row->page_restrictions )
258 throw new InvalidArgumentException(
"Unknown field: $field" );
273 if ( $key ===
null ) {
277 return $this->badLinks->has( $key );
296 $revision = 0, $model =
null,
$lang =
null
299 'page_id' => (
int)$id,
300 'page_namespace' => $page->getNamespace(),
301 'page_title' => $page->getDBkey(),
302 'page_len' => (
int)$len,
303 'page_is_redirect' => (
int)$redir,
304 'page_latest' => (
int)$revision,
305 'page_content_model' => $model ? (
string)$model :
null,
307 'page_restrictions' =>
null,
309 'page_touched' =>
'',
329 int $queryFlags = IDBAccessObject::READ_NORMAL
331 foreach ( self::getSelectFields() as $field ) {
332 if ( !property_exists( $row, $field ) ) {
333 throw new InvalidArgumentException(
"Missing field: $field" );
338 if ( $key ===
null ) {
342 $this->goodLinks->set( $key, [ $row, $queryFlags ] );
343 $this->badLinks->clear( $key );
354 if ( $key !==
null && !$this->
isBadLink( $key ) ) {
355 $this->badLinks->set( $key, 1 );
356 $this->goodLinks->clear( $key );
369 if ( $key !==
null ) {
370 $this->badLinks->clear( $key );
383 if ( $key !==
null ) {
384 $this->badLinks->clear( $key );
385 $this->goodLinks->clear( $key );
398 $fields = array_merge(
399 PageStoreRecord::REQUIRED_FIELDS,
403 'page_content_model',
408 $fields[] =
'page_lang';
428 public function addLinkObj( $page,
int $queryFlags = IDBAccessObject::READ_NORMAL ) {
430 $page->getNamespace(),
432 [ $this,
'fetchPageRow' ],
436 return $row ? (int)$row->page_id : 0;
449 callable $fetchCallback =
null,
450 int $queryFlags = IDBAccessObject::READ_NORMAL
453 if ( $key ===
null ) {
454 return [
false, false ];
459 $callerShouldAddGoodLink =
false;
461 if ( $this->mForUpdate ) {
462 $queryFlags |= IDBAccessObject::READ_LATEST;
464 $forUpdate = $queryFlags & IDBAccessObject::READ_LATEST;
466 if ( !$forUpdate && $this->
isBadLink( $key ) ) {
467 return [ $callerShouldAddGoodLink, false ];
470 [ $row, $rowFlags ] = $this->goodLinks->get( $key );
471 if ( $row && $rowFlags >= $queryFlags ) {
472 return [ $callerShouldAddGoodLink, $row ];
475 if ( !$fetchCallback ) {
476 return [ $callerShouldAddGoodLink, false ];
479 $callerShouldAddGoodLink =
true;
482 $wanCacheKey = $this->wanCache->makeKey(
'page', $ns, sha1( $dbkey ) );
484 $row = $this->wanCache->getWithSetCallback(
486 WANObjectCache::TTL_DAY,
487 function ( $curValue, &$ttl, array &$setOpts ) use ( $fetchCallback, $ns, $dbkey ) {
488 $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
489 $setOpts += Database::getCacheSetOptions(
$dbr );
491 $row = $fetchCallback(
$dbr, $ns, $dbkey, [] );
492 $mtime = $row ?
wfTimestamp( TS_UNIX, $row->page_touched ) : false;
493 $ttl = $this->wanCache->adaptiveTTL( $mtime, $ttl );
501 $dbr = $this->loadBalancer->getConnectionRef( $mode );
502 $row = $fetchCallback(
$dbr, $ns, $dbkey, $options );
505 return [ $callerShouldAddGoodLink, $row ];
525 callable $fetchCallback =
null,
526 int $queryFlags = IDBAccessObject::READ_NORMAL
529 if ( $link ===
null ) {
532 [ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
539 if ( $shouldAddGoodLink ) {
541 $this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
542 }
catch ( InvalidArgumentException $e ) {
544 $this->invalidateTitle( $link );
545 [ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
550 $this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
554 $this->addBadLinkObj( $link );
568 $key = $this->getCacheKey( $page );
570 if ( $key ===
null ) {
574 if ( $this->usePersistentCache( $page ) ) {
575 return [
$cache->makeKey(
'page', $page->getNamespace(), sha1( $page->getDBkey() ) ) ];
587 $ns = is_int( $pageOrNamespace ) ? $pageOrNamespace : $pageOrNamespace->getNamespace();
592 if ( $this->nsInfo->isContent( $ns ) ) {
596 return ( $ns >= 100 && $this->nsInfo->isSubject( $ns ) );
608 $fields = self::getSelectFields();
609 if ( $this->usePersistentCache( $ns ) ) {
610 $fields[] =
'page_touched';
616 [
'page_namespace' => $ns,
'page_title' => $dbkey ],
630 if ( $this->usePersistentCache( $page ) ) {
633 $cache->makeKey(
'page', $page->getNamespace(), sha1( $page->getDBkey() ) )
637 $this->clearLink( $page );
644 $this->goodLinks->clear();
645 $this->badLinks->clear();
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Cache for article titles (prefixed DB keys) and ids linked from one source.
TitleFormatter $titleFormatter
isBadLink( $page)
Returns true if the fact that this page does not exist had been added to the cache.
getCacheKey( $page, $passThrough=false)
fetchPageRow(IDatabase $db, int $ns, string $dbkey, $options=[])
invalidateTitle( $page)
Purge the persistent link cache for a title.
addLinkObj( $page, int $queryFlags=IDBAccessObject::READ_NORMAL)
Add a title to the link cache, return the page_id or zero if non-existent.
forUpdate( $update=null)
General accessor to get/set whether the primary DB should be used.
addGoodLinkObjFromRow( $page, stdClass $row, int $queryFlags=IDBAccessObject::READ_NORMAL)
Same as above with better interface.
usePersistentCache( $pageOrNamespace)
addGoodLinkObj( $id, $page, $len=-1, $redir=null, $revision=0, $model=null, $lang=null)
Add information about an existing page to the cache.
getGoodLinkID( $page)
Returns the ID of the given page, if information about this page has been cached.
setLogger(LoggerInterface $logger)
getGoodLinkRow(int $ns, string $dbkey, callable $fetchCallback=null, int $queryFlags=IDBAccessObject::READ_NORMAL)
Returns the row for the page if the page exists (subject to race conditions).
getGoodLinkRowInternal(?TitleValue $link, callable $fetchCallback=null, int $queryFlags=IDBAccessObject::READ_NORMAL)
static singleton()
Get an instance of this class.
const MAX_SIZE
How many Titles to store.
static getSelectFields()
Fields that LinkCache needs to select.
ILoadBalancer null $loadBalancer
getGoodLinkFieldObj( $page, string $field)
Get a field of a page from the cache.
getMutableCacheKeys(WANObjectCache $cache, $page)
__construct(TitleFormatter $titleFormatter, WANObjectCache $cache, NamespaceInfo $nsInfo=null, ILoadBalancer $loadBalancer=null)
Handles a simple LRU key/value map with a maximum number of entries.
Immutable data record representing an editable page on a wiki.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a page (or page fragment) title within MediaWiki.
getDBkey()
Returns the title's DB key, as supplied to the constructor, without namespace prefix or fragment.
Multi-datacenter aware caching interface.
Interface for objects (potentially) representing an editable wiki page.
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...
if(!isset( $args[0])) $lang