2004-02-18 02:15:00 +00:00
|
|
|
<?php
|
2012-05-08 12:51:21 +00:00
|
|
|
/**
|
|
|
|
|
* Page existence cache.
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Cache
|
|
|
|
|
*/
|
2017-02-07 04:49:57 +00:00
|
|
|
|
2016-04-27 21:52:50 +00:00
|
|
|
use MediaWiki\Linker\LinkTarget;
|
2022-04-26 15:48:03 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2016-04-27 21:52:50 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2021-06-02 15:49:19 +00:00
|
|
|
use MediaWiki\Page\PageIdentity;
|
|
|
|
|
use MediaWiki\Page\PageReference;
|
2021-12-14 08:14:06 +00:00
|
|
|
use MediaWiki\Page\PageStoreRecord;
|
2021-06-02 15:49:19 +00:00
|
|
|
use Psr\Log\LoggerAwareInterface;
|
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
use Psr\Log\NullLogger;
|
2020-01-10 00:00:51 +00:00
|
|
|
use Wikimedia\Rdbms\Database;
|
|
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2020-08-13 20:10:35 +00:00
|
|
|
use Wikimedia\Rdbms\ILoadBalancer;
|
2012-05-08 12:51:21 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
|
|
|
|
* Cache for article titles (prefixed DB keys) and ids linked from one source
|
2008-04-14 07:45:50 +00:00
|
|
|
*
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Cache
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
class LinkCache implements LoggerAwareInterface {
|
2018-07-11 13:11:12 +00:00
|
|
|
/** @var MapCacheLRU */
|
|
|
|
|
private $goodLinks;
|
|
|
|
|
/** @var MapCacheLRU */
|
|
|
|
|
private $badLinks;
|
2016-09-02 14:19:13 +00:00
|
|
|
/** @var WANObjectCache */
|
|
|
|
|
private $wanCache;
|
|
|
|
|
|
|
|
|
|
/** @var TitleFormatter */
|
2016-04-27 21:52:50 +00:00
|
|
|
private $titleFormatter;
|
|
|
|
|
|
2019-04-09 09:30:58 +00:00
|
|
|
/** @var NamespaceInfo */
|
|
|
|
|
private $nsInfo;
|
|
|
|
|
|
2020-08-13 20:10:35 +00:00
|
|
|
/** @var ILoadBalancer|null */
|
|
|
|
|
private $loadBalancer;
|
|
|
|
|
|
2021-06-02 15:49:19 +00:00
|
|
|
/** @var LoggerInterface */
|
|
|
|
|
private $logger;
|
|
|
|
|
|
2015-07-26 23:07:59 +00:00
|
|
|
/**
|
|
|
|
|
* How many Titles to store. There are two caches, so the amount actually
|
|
|
|
|
* stored in memory can be up to twice this.
|
|
|
|
|
*/
|
2020-05-15 22:16:46 +00:00
|
|
|
private const MAX_SIZE = 10000;
|
2015-07-26 23:07:59 +00:00
|
|
|
|
2020-08-13 20:10:35 +00:00
|
|
|
/**
|
|
|
|
|
* @param TitleFormatter $titleFormatter
|
|
|
|
|
* @param WANObjectCache $cache
|
2022-06-16 18:14:42 +00:00
|
|
|
* @param NamespaceInfo $nsInfo
|
2020-08-13 20:10:35 +00:00
|
|
|
* @param ILoadBalancer|null $loadBalancer Use null when no database is set up, for example on installation
|
|
|
|
|
*/
|
2019-04-09 09:30:58 +00:00
|
|
|
public function __construct(
|
|
|
|
|
TitleFormatter $titleFormatter,
|
|
|
|
|
WANObjectCache $cache,
|
2022-06-16 18:14:42 +00:00
|
|
|
NamespaceInfo $nsInfo,
|
2020-08-13 20:10:35 +00:00
|
|
|
ILoadBalancer $loadBalancer = null
|
2019-04-09 09:30:58 +00:00
|
|
|
) {
|
2018-07-11 13:11:12 +00:00
|
|
|
$this->goodLinks = new MapCacheLRU( self::MAX_SIZE );
|
|
|
|
|
$this->badLinks = new MapCacheLRU( self::MAX_SIZE );
|
2016-09-02 14:19:13 +00:00
|
|
|
$this->wanCache = $cache;
|
2016-04-27 21:52:50 +00:00
|
|
|
$this->titleFormatter = $titleFormatter;
|
2019-04-09 09:30:58 +00:00
|
|
|
$this->nsInfo = $nsInfo;
|
2020-08-13 20:10:35 +00:00
|
|
|
$this->loadBalancer = $loadBalancer;
|
2021-06-02 15:49:19 +00:00
|
|
|
$this->logger = new NullLogger();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param LoggerInterface $logger
|
|
|
|
|
*/
|
|
|
|
|
public function setLogger( LoggerInterface $logger ) {
|
|
|
|
|
$this->logger = $logger;
|
2015-07-26 23:07:59 +00:00
|
|
|
}
|
|
|
|
|
|
2011-05-28 18:58:51 +00:00
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array|string $page
|
2021-06-02 15:49:19 +00:00
|
|
|
* @param bool $passThrough Return $page if $page is a string
|
|
|
|
|
*
|
|
|
|
|
* @return ?string the cache key
|
2011-05-28 18:58:51 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
private function getCacheKey( $page, $passThrough = false ) {
|
|
|
|
|
if ( is_string( $page ) ) {
|
|
|
|
|
if ( $passThrough ) {
|
|
|
|
|
return $page;
|
|
|
|
|
} else {
|
|
|
|
|
throw new InvalidArgumentException( 'They key may not be given as a string here' );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( is_array( $page ) ) {
|
|
|
|
|
$namespace = $page['page_namespace'];
|
|
|
|
|
$dbkey = $page['page_title'];
|
|
|
|
|
return strtr( $this->titleFormatter->formatTitle( $namespace, $dbkey ), ' ', '_' );
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-02 15:49:19 +00:00
|
|
|
if ( $page instanceof PageReference && $page->getWikiId() !== PageReference::LOCAL ) {
|
|
|
|
|
// No cross-wiki support yet. Perhaps LinkCache can become wiki-aware in the future.
|
|
|
|
|
$this->logger->info(
|
|
|
|
|
'cross-wiki page reference',
|
2021-07-01 17:21:19 +00:00
|
|
|
[
|
|
|
|
|
'page-wiki' => $page->getWikiId(),
|
|
|
|
|
'page-reference' => $this->titleFormatter->getFullText( $page )
|
|
|
|
|
]
|
2021-06-02 15:49:19 +00:00
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $page instanceof PageIdentity && !$page->canExist() ) {
|
|
|
|
|
// Non-proper page, perhaps a special page or interwiki link or relative section link.
|
|
|
|
|
$this->logger->warning(
|
2021-07-27 06:51:19 +00:00
|
|
|
'non-proper page reference: {page-reference}',
|
2021-07-01 17:21:19 +00:00
|
|
|
[ 'page-reference' => $this->titleFormatter->getFullText( $page ) ]
|
2021-06-02 15:49:19 +00:00
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $page instanceof LinkTarget
|
|
|
|
|
&& ( $page->isExternal() || $page->getText() === '' || $page->getNamespace() < 0 )
|
|
|
|
|
) {
|
|
|
|
|
// Interwiki link or relative section link. These do not have a page ID, so they
|
|
|
|
|
// can neither be "good" nor "bad" in the sense of this class.
|
|
|
|
|
$this->logger->warning(
|
2021-07-27 06:51:19 +00:00
|
|
|
'link to non-proper page: {page-link}',
|
2021-07-01 17:21:19 +00:00
|
|
|
[ 'page-link' => $this->titleFormatter->getFullText( $page ) ]
|
2021-06-02 15:49:19 +00:00
|
|
|
);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->titleFormatter->getPrefixedDBkey( $page );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the ID of the given page, if information about this page has been cached.
|
|
|
|
|
*
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array|string $page The page to get the ID for,
|
|
|
|
|
* as an object, an array containing the page_namespace and page_title fields,
|
|
|
|
|
* or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted.
|
2021-06-02 15:49:19 +00:00
|
|
|
* @return int Page ID, or zero if the page was not cached or does not exist or is not a
|
|
|
|
|
* proper page (e.g. a special page or an interwiki link).
|
|
|
|
|
*/
|
|
|
|
|
public function getGoodLinkID( $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page, true );
|
|
|
|
|
|
|
|
|
|
if ( $key === null ) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
[ $row ] = $this->goodLinks->get( $key );
|
2021-06-02 15:49:19 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
return $row ? (int)$row->page_id : 0;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-04-09 05:21:00 +00:00
|
|
|
/**
|
2021-06-02 15:49:19 +00:00
|
|
|
* Get a field of a page from the cache.
|
|
|
|
|
*
|
2015-11-09 23:14:42 +00:00
|
|
|
* If this link is not a cached good title, it will return NULL.
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array $page The page to get cached info for.
|
|
|
|
|
* Can be given as an object or an associative array containing the
|
|
|
|
|
* page_namespace and page_title fields.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2022-05-09 07:00:17 +00:00
|
|
|
* @param string $field ( 'id', 'length', 'redirect', 'revision', 'model', 'lang' )
|
2021-06-02 15:49:19 +00:00
|
|
|
* @return string|int|null The field value, or null if the page was not cached or does not exist
|
|
|
|
|
* or is not a proper page (e.g. a special page or interwiki link).
|
2008-04-09 05:21:00 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function getGoodLinkFieldObj( $page, string $field ) {
|
|
|
|
|
$key = $this->getCacheKey( $page );
|
|
|
|
|
if ( $key === null ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $this->isBadLink( $key ) ) {
|
2009-12-11 21:07:27 +00:00
|
|
|
return null;
|
2008-04-09 05:21:00 +00:00
|
|
|
}
|
2021-06-01 20:14:36 +00:00
|
|
|
|
|
|
|
|
[ $row ] = $this->goodLinks->get( $key );
|
|
|
|
|
|
|
|
|
|
if ( !$row ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch ( $field ) {
|
|
|
|
|
case 'id':
|
|
|
|
|
return intval( $row->page_id );
|
|
|
|
|
case 'length':
|
|
|
|
|
return intval( $row->page_len );
|
|
|
|
|
case 'redirect':
|
|
|
|
|
return intval( $row->page_is_redirect );
|
|
|
|
|
case 'revision':
|
|
|
|
|
return intval( $row->page_latest );
|
|
|
|
|
case 'model':
|
|
|
|
|
return !empty( $row->page_content_model )
|
|
|
|
|
? strval( $row->page_content_model )
|
|
|
|
|
: null;
|
|
|
|
|
case 'lang':
|
|
|
|
|
return !empty( $row->page_lang )
|
|
|
|
|
? strval( $row->page_lang )
|
|
|
|
|
: null;
|
|
|
|
|
default:
|
|
|
|
|
throw new InvalidArgumentException( "Unknown field: $field" );
|
|
|
|
|
}
|
2008-04-09 05:21:00 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
|
2011-05-28 18:58:51 +00:00
|
|
|
/**
|
2021-06-02 15:49:19 +00:00
|
|
|
* Returns true if the fact that this page does not exist had been added to the cache.
|
|
|
|
|
*
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array|string $page The page to get cached info for,
|
|
|
|
|
* as an object, an array containing the page_namespace and page_title fields,
|
|
|
|
|
* or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only a string was accepted.
|
|
|
|
|
* @return bool True if the page is known to not exist.
|
2011-05-28 18:58:51 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function isBadLink( $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page, true );
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $key === null ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-06-02 15:49:19 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
return $this->badLinks->has( $key );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2008-04-09 05:21:00 +00:00
|
|
|
/**
|
2021-06-02 15:49:19 +00:00
|
|
|
* Add information about an existing page to the cache.
|
|
|
|
|
*
|
2021-06-09 21:17:21 +00:00
|
|
|
* @deprecated since 1.37, use addGoodLinkObjFromRow() instead. PHPUnit tests
|
|
|
|
|
* must use LinkCacheTestTrait::addGoodLinkObject().
|
2010-07-17 20:13:49 +00:00
|
|
|
*
|
2013-11-17 20:42:23 +00:00
|
|
|
* @param int $id Page's ID
|
2021-06-02 15:49:19 +00:00
|
|
|
* @param LinkTarget|PageReference $page The page to set cached info for.
|
|
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2013-11-17 20:42:23 +00:00
|
|
|
* @param int $len Text's length
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param int|null $redir Whether the page is a redirect
|
2013-11-17 20:42:23 +00:00
|
|
|
* @param int $revision Latest revision's ID
|
2014-08-20 21:46:11 +00:00
|
|
|
* @param string|null $model Latest revision's content model ID
|
2015-12-19 15:25:45 +00:00
|
|
|
* @param string|null $lang Language code of the page, if not the content language
|
2008-04-09 05:21:00 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function addGoodLinkObj( $id, $page, $len = -1, $redir = null,
|
2015-12-19 15:25:45 +00:00
|
|
|
$revision = 0, $model = null, $lang = null
|
2013-11-17 20:42:23 +00:00
|
|
|
) {
|
2021-10-26 15:21:32 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.38' );
|
2021-06-01 20:14:36 +00:00
|
|
|
$this->addGoodLinkObjFromRow( $page, (object)[
|
|
|
|
|
'page_id' => (int)$id,
|
|
|
|
|
'page_namespace' => $page->getNamespace(),
|
|
|
|
|
'page_title' => $page->getDBkey(),
|
|
|
|
|
'page_len' => (int)$len,
|
|
|
|
|
'page_is_redirect' => (int)$redir,
|
|
|
|
|
'page_latest' => (int)$revision,
|
|
|
|
|
'page_content_model' => $model ? (string)$model : null,
|
|
|
|
|
'page_lang' => $lang ? (string)$lang : null,
|
|
|
|
|
'page_is_new' => 0,
|
|
|
|
|
'page_touched' => '',
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2011-09-20 15:19:18 +00:00
|
|
|
/**
|
|
|
|
|
* Same as above with better interface.
|
2021-06-02 15:49:19 +00:00
|
|
|
*
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array $page The page to set cached info for.
|
|
|
|
|
* Can be given as an object or an associative array containing the
|
|
|
|
|
* page_namespace and page_title fields.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
|
|
|
|
* @param stdClass $row Object which has all fields returned by getSelectFields().
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param int $queryFlags The query flags used to retrieve the row, IDBAccessObject::READ_XXX
|
|
|
|
|
*
|
|
|
|
|
* @since 1.19
|
2021-06-02 15:49:19 +00:00
|
|
|
*
|
2011-09-20 15:19:18 +00:00
|
|
|
*/
|
2021-06-01 20:14:36 +00:00
|
|
|
public function addGoodLinkObjFromRow(
|
|
|
|
|
$page,
|
|
|
|
|
stdClass $row,
|
|
|
|
|
int $queryFlags = IDBAccessObject::READ_NORMAL
|
|
|
|
|
) {
|
|
|
|
|
foreach ( self::getSelectFields() as $field ) {
|
|
|
|
|
if ( !property_exists( $row, $field ) ) {
|
|
|
|
|
throw new InvalidArgumentException( "Missing field: $field" );
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-02 15:49:19 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
$key = $this->getCacheKey( $page );
|
2021-06-02 15:49:19 +00:00
|
|
|
if ( $key === null ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
$this->goodLinks->set( $key, [ $row, $queryFlags ] );
|
2021-06-02 15:49:19 +00:00
|
|
|
$this->badLinks->clear( $key );
|
2011-09-20 15:19:18 +00:00
|
|
|
}
|
|
|
|
|
|
2011-05-26 19:21:50 +00:00
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array $page The page to set cached info for.
|
|
|
|
|
* Can be given as an object or an associative array containing the
|
|
|
|
|
* page_namespace and page_title fields.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2011-05-26 19:21:50 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function addBadLinkObj( $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page );
|
|
|
|
|
if ( $key !== null && !$this->isBadLink( $key ) ) {
|
|
|
|
|
$this->badLinks->set( $key, 1 );
|
|
|
|
|
$this->goodLinks->clear( $key );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 21:36:24 +00:00
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array|string $page The page to clear cached info for,
|
|
|
|
|
* as an object, an array containing the page_namespace and page_title fields,
|
|
|
|
|
* or a prefixed DB key. In MediaWiki 1.36 and earlier, only a string was accepted.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only a string was accepted.
|
2016-04-26 21:36:24 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function clearBadLink( $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page, true );
|
|
|
|
|
|
|
|
|
|
if ( $key !== null ) {
|
|
|
|
|
$this->badLinks->clear( $key );
|
|
|
|
|
}
|
2003-11-04 08:59:28 +00:00
|
|
|
}
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2011-05-26 19:21:50 +00:00
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|array $page The page to clear cached info for.
|
|
|
|
|
* Can be given as an object or an associative array containing the
|
|
|
|
|
* page_namespace and page_title fields.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2011-05-26 19:21:50 +00:00
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function clearLink( $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page );
|
|
|
|
|
|
|
|
|
|
if ( $key !== null ) {
|
|
|
|
|
$this->badLinks->clear( $key );
|
|
|
|
|
$this->goodLinks->clear( $key );
|
|
|
|
|
}
|
2008-09-07 08:24:06 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
|
2016-05-13 07:00:39 +00:00
|
|
|
/**
|
|
|
|
|
* Fields that LinkCache needs to select
|
|
|
|
|
*
|
|
|
|
|
* @since 1.28
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public static function getSelectFields() {
|
2022-04-26 15:48:03 +00:00
|
|
|
$pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig()
|
|
|
|
|
->get( MainConfigNames::PageLanguageUseDB );
|
2016-05-13 07:00:39 +00:00
|
|
|
|
2021-12-14 08:14:06 +00:00
|
|
|
$fields = array_merge(
|
|
|
|
|
PageStoreRecord::REQUIRED_FIELDS,
|
|
|
|
|
[
|
|
|
|
|
'page_len',
|
|
|
|
|
'page_content_model',
|
|
|
|
|
]
|
|
|
|
|
);
|
2019-12-17 16:20:32 +00:00
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $pageLanguageUseDB ) {
|
2016-05-13 07:00:39 +00:00
|
|
|
$fields[] = 'page_lang';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
|
}
|
|
|
|
|
|
2005-12-30 09:33:11 +00:00
|
|
|
/**
|
2021-06-02 15:49:19 +00:00
|
|
|
* Add a title to the link cache, return the page_id or zero if non-existent.
|
|
|
|
|
* This causes the link to be looked up in the database if it is not yet cached.
|
|
|
|
|
*
|
2021-06-01 20:14:36 +00:00
|
|
|
* @deprecated since 1.37, use PageStore::getPageForLink() instead.
|
|
|
|
|
*
|
|
|
|
|
* @param LinkTarget|PageReference|array $page The page to load.
|
|
|
|
|
* Can be given as an object or an associative array containing the
|
|
|
|
|
* page_namespace and page_title fields.
|
2021-06-02 15:49:19 +00:00
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param int $queryFlags IDBAccessObject::READ_XXX
|
2010-07-17 20:13:49 +00:00
|
|
|
*
|
2015-11-09 23:14:42 +00:00
|
|
|
* @return int Page ID or zero
|
2005-12-30 09:33:11 +00:00
|
|
|
*/
|
2021-06-01 20:14:36 +00:00
|
|
|
public function addLinkObj( $page, int $queryFlags = IDBAccessObject::READ_NORMAL ) {
|
|
|
|
|
$row = $this->getGoodLinkRow(
|
|
|
|
|
$page->getNamespace(),
|
|
|
|
|
$page->getDBkey(),
|
|
|
|
|
[ $this, 'fetchPageRow' ],
|
|
|
|
|
$queryFlags
|
|
|
|
|
);
|
2005-10-22 20:52:30 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
return $row ? (int)$row->page_id : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-11-01 13:11:33 +00:00
|
|
|
* @param TitleValue|null $link
|
|
|
|
|
* @param callable|null $fetchCallback
|
|
|
|
|
* @param int $queryFlags
|
|
|
|
|
* @return array [ $shouldAddGoodLink, $row ], $shouldAddGoodLink is a bool indicating
|
|
|
|
|
* whether addGoodLinkObjFromRow should be called, and $row is the row the caller was looking
|
|
|
|
|
* for (or false, when it was not found).
|
2021-06-01 20:14:36 +00:00
|
|
|
*/
|
2021-11-01 13:11:33 +00:00
|
|
|
private function getGoodLinkRowInternal(
|
|
|
|
|
?TitleValue $link,
|
2021-06-01 20:14:36 +00:00
|
|
|
callable $fetchCallback = null,
|
|
|
|
|
int $queryFlags = IDBAccessObject::READ_NORMAL
|
2021-11-01 13:11:33 +00:00
|
|
|
): array {
|
2021-06-01 20:14:36 +00:00
|
|
|
$key = $link ? $this->getCacheKey( $link ) : null;
|
2021-06-02 15:49:19 +00:00
|
|
|
if ( $key === null ) {
|
2021-11-01 13:11:33 +00:00
|
|
|
return [ false, false ];
|
2003-10-16 13:30:45 +00:00
|
|
|
}
|
2011-02-12 04:06:22 +00:00
|
|
|
|
2021-11-01 13:11:33 +00:00
|
|
|
$ns = $link->getNamespace();
|
|
|
|
|
$dbkey = $link->getDBkey();
|
|
|
|
|
$callerShouldAddGoodLink = false;
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
$forUpdate = $queryFlags & IDBAccessObject::READ_LATEST;
|
2021-06-02 15:49:19 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( !$forUpdate && $this->isBadLink( $key ) ) {
|
2021-11-01 13:11:33 +00:00
|
|
|
return [ $callerShouldAddGoodLink, false ];
|
2021-06-02 15:49:19 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
[ $row, $rowFlags ] = $this->goodLinks->get( $key );
|
|
|
|
|
if ( $row && $rowFlags >= $queryFlags ) {
|
2021-11-01 13:11:33 +00:00
|
|
|
return [ $callerShouldAddGoodLink, $row ];
|
2020-08-13 20:10:35 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( !$fetchCallback ) {
|
2021-11-01 13:11:33 +00:00
|
|
|
return [ $callerShouldAddGoodLink, false ];
|
2021-06-01 20:14:36 +00:00
|
|
|
}
|
|
|
|
|
|
2021-11-01 13:11:33 +00:00
|
|
|
$callerShouldAddGoodLink = true;
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $this->usePersistentCache( $ns ) && !$forUpdate ) {
|
|
|
|
|
// Some pages are often transcluded heavily, so use persistent caching
|
|
|
|
|
$wanCacheKey = $this->wanCache->makeKey( 'page', $ns, sha1( $dbkey ) );
|
|
|
|
|
|
|
|
|
|
$row = $this->wanCache->getWithSetCallback(
|
|
|
|
|
$wanCacheKey,
|
|
|
|
|
WANObjectCache::TTL_DAY,
|
|
|
|
|
function ( $curValue, &$ttl, array &$setOpts ) use ( $fetchCallback, $ns, $dbkey ) {
|
2020-08-13 20:10:35 +00:00
|
|
|
$dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
|
2016-09-02 14:19:13 +00:00
|
|
|
$setOpts += Database::getCacheSetOptions( $dbr );
|
2005-08-02 13:35:19 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
$row = $fetchCallback( $dbr, $ns, $dbkey, [] );
|
2021-10-16 21:47:01 +00:00
|
|
|
$mtime = $row ? (int)wfTimestamp( TS_UNIX, $row->page_touched ) : false;
|
2021-06-01 20:14:36 +00:00
|
|
|
$ttl = $this->wanCache->adaptiveTTL( $mtime, $ttl );
|
2015-11-09 23:14:42 +00:00
|
|
|
|
2016-09-02 14:19:13 +00:00
|
|
|
return $row;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2021-06-01 20:14:36 +00:00
|
|
|
// No persistent caching needed, but we can still use the callback.
|
|
|
|
|
[ $mode, $options ] = DBAccessObjectUtils::getDBOptions( $queryFlags );
|
|
|
|
|
$dbr = $this->loadBalancer->getConnectionRef( $mode );
|
|
|
|
|
$row = $fetchCallback( $dbr, $ns, $dbkey, $options );
|
2016-09-02 14:19:13 +00:00
|
|
|
}
|
|
|
|
|
|
2021-11-01 13:11:33 +00:00
|
|
|
return [ $callerShouldAddGoodLink, $row ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the row for the page if the page exists (subject to race conditions).
|
|
|
|
|
* The row will be returned from local cache or WAN cache if possible, or it
|
|
|
|
|
* will be looked up using the callback provided.
|
|
|
|
|
*
|
|
|
|
|
* @param int $ns
|
|
|
|
|
* @param string $dbkey
|
|
|
|
|
* @param callable|null $fetchCallback A callback that will retrieve the link row with the
|
|
|
|
|
* signature ( IDatabase $db, int $ns, string $dbkey, array $queryOptions ): ?stdObj.
|
|
|
|
|
* @param int $queryFlags IDBAccessObject::READ_XXX
|
|
|
|
|
*
|
|
|
|
|
* @return stdClass|null
|
|
|
|
|
* @internal for use by PageStore. Other code should use a PageLookup instead.
|
|
|
|
|
*/
|
|
|
|
|
public function getGoodLinkRow(
|
|
|
|
|
int $ns,
|
|
|
|
|
string $dbkey,
|
|
|
|
|
callable $fetchCallback = null,
|
|
|
|
|
int $queryFlags = IDBAccessObject::READ_NORMAL
|
|
|
|
|
): ?stdClass {
|
|
|
|
|
$link = TitleValue::tryNew( $ns, $dbkey );
|
|
|
|
|
if ( $link === null ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
[ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
|
|
|
|
|
$link,
|
|
|
|
|
$fetchCallback,
|
|
|
|
|
$queryFlags
|
|
|
|
|
);
|
|
|
|
|
|
2016-09-02 14:19:13 +00:00
|
|
|
if ( $row ) {
|
2021-11-01 13:11:33 +00:00
|
|
|
if ( $shouldAddGoodLink ) {
|
|
|
|
|
try {
|
|
|
|
|
$this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
|
|
|
|
|
} catch ( InvalidArgumentException $e ) {
|
|
|
|
|
// a field is missing from $row; maybe we used a cache?; invalidate it and try again
|
|
|
|
|
$this->invalidateTitle( $link );
|
|
|
|
|
[ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
|
|
|
|
|
$link,
|
|
|
|
|
$fetchCallback,
|
|
|
|
|
$queryFlags
|
|
|
|
|
);
|
|
|
|
|
$this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-08-29 19:12:56 +00:00
|
|
|
} else {
|
2021-06-01 20:14:36 +00:00
|
|
|
$this->addBadLinkObj( $link );
|
2005-05-26 10:23:36 +00:00
|
|
|
}
|
2011-09-20 15:19:18 +00:00
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
return $row ?: null;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-03 04:43:16 +00:00
|
|
|
/**
|
|
|
|
|
* @param WANObjectCache $cache
|
2021-06-02 15:49:19 +00:00
|
|
|
* @param LinkTarget|Pagereference $page
|
|
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2016-09-03 04:43:16 +00:00
|
|
|
* @return string[]
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function getMutableCacheKeys( WANObjectCache $cache, $page ) {
|
|
|
|
|
$key = $this->getCacheKey( $page );
|
|
|
|
|
// if no key can be derived, the page isn't cacheable
|
|
|
|
|
if ( $key === null ) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $this->usePersistentCache( $page ) ) {
|
2021-06-02 15:49:19 +00:00
|
|
|
return [ $cache->makeKey( 'page', $page->getNamespace(), sha1( $page->getDBkey() ) ) ];
|
2016-09-03 04:43:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-02 15:49:19 +00:00
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param LinkTarget|PageReference|int $pageOrNamespace
|
2021-06-02 15:49:19 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2021-06-01 20:14:36 +00:00
|
|
|
private function usePersistentCache( $pageOrNamespace ) {
|
|
|
|
|
$ns = is_int( $pageOrNamespace ) ? $pageOrNamespace : $pageOrNamespace->getNamespace();
|
2019-07-10 22:18:48 +00:00
|
|
|
if ( in_array( $ns, [ NS_TEMPLATE, NS_FILE, NS_CATEGORY, NS_MEDIAWIKI ] ) ) {
|
2019-03-14 06:44:38 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// Focus on transcluded pages more than the main content
|
2019-04-09 09:30:58 +00:00
|
|
|
if ( $this->nsInfo->isContent( $ns ) ) {
|
2019-03-14 06:44:38 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Non-talk extension namespaces (e.g. NS_MODULE)
|
2019-04-09 09:30:58 +00:00
|
|
|
return ( $ns >= 100 && $this->nsInfo->isSubject( $ns ) );
|
2016-09-02 14:19:13 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-02 15:49:19 +00:00
|
|
|
/**
|
|
|
|
|
* @param IDatabase $db
|
2021-06-01 20:14:36 +00:00
|
|
|
* @param int $ns
|
|
|
|
|
* @param string $dbkey
|
|
|
|
|
* @param array $options Query options, see IDatabase::select() for details.
|
2021-06-02 15:49:19 +00:00
|
|
|
*
|
|
|
|
|
* @return stdClass|false
|
|
|
|
|
*/
|
2021-06-01 20:14:36 +00:00
|
|
|
private function fetchPageRow( IDatabase $db, int $ns, string $dbkey, $options = [] ) {
|
2016-09-02 14:19:13 +00:00
|
|
|
$fields = self::getSelectFields();
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $this->usePersistentCache( $ns ) ) {
|
2016-09-02 14:19:13 +00:00
|
|
|
$fields[] = 'page_touched';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $db->selectRow(
|
|
|
|
|
'page',
|
|
|
|
|
$fields,
|
2021-06-01 20:14:36 +00:00
|
|
|
[ 'page_namespace' => $ns, 'page_title' => $dbkey ],
|
|
|
|
|
__METHOD__,
|
|
|
|
|
$options
|
2016-09-02 14:19:13 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-06-01 20:14:36 +00:00
|
|
|
* Purge the persistent link cache for a title
|
2016-09-02 14:19:13 +00:00
|
|
|
*
|
2021-06-02 15:49:19 +00:00
|
|
|
* @param LinkTarget|PageReference $page
|
|
|
|
|
* In MediaWiki 1.36 and earlier, only LinkTarget was accepted.
|
2016-09-02 14:19:13 +00:00
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
2021-06-02 15:49:19 +00:00
|
|
|
public function invalidateTitle( $page ) {
|
2021-06-01 20:14:36 +00:00
|
|
|
if ( $this->usePersistentCache( $page ) ) {
|
2018-06-15 04:00:38 +00:00
|
|
|
$cache = $this->wanCache;
|
2016-09-02 14:19:13 +00:00
|
|
|
$cache->delete(
|
2021-06-02 15:49:19 +00:00
|
|
|
$cache->makeKey( 'page', $page->getNamespace(), sha1( $page->getDBkey() ) )
|
2016-09-02 14:19:13 +00:00
|
|
|
);
|
|
|
|
|
}
|
2021-06-02 15:49:19 +00:00
|
|
|
|
|
|
|
|
$this->clearLink( $page );
|
2016-09-02 14:19:13 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2005-08-02 13:35:19 +00:00
|
|
|
* Clears cache
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2008-04-09 13:02:34 +00:00
|
|
|
public function clear() {
|
2018-07-11 13:11:12 +00:00
|
|
|
$this->goodLinks->clear();
|
|
|
|
|
$this->badLinks->clear();
|
2005-05-29 10:17:44 +00:00
|
|
|
}
|
2021-06-02 15:49:19 +00:00
|
|
|
|
2003-07-06 11:42:42 +00:00
|
|
|
}
|