to add fields to Special:Mute.
* (T100896) Skin authors can define custom OOUI themes using OOUIThemePaths.
See <https://www.mediawiki.org/wiki/OOUI/Themes> for details.
-* The HtmlCacheUpdater service was added to unify the logic of purging CDN cache
- and HTML file cache to simplify callers and make them more consistent.
=== External library changes in 1.34 ===
* SearchEngine::textAlreadyUpdatedForIndex() is deprecated, given the
deprecation above this method is no longer needed/called and should not be
implemented by SearchEngine implementation.
-* Title::purgeSquid is deprecated. Use MediaWikiServices::getHtmlCacheUpdater.
=== Other changes in 1.34 ===
* …
'Hooks' => __DIR__ . '/includes/Hooks.php',
'Html' => __DIR__ . '/includes/Html.php',
'HtmlArmor' => __DIR__ . '/includes/libs/HtmlArmor.php',
- 'HtmlCacheUpdater' => __DIR__ . '/includes/cache/HtmlCacheUpdater.php',
- 'HtmlFileCacheUpdate' => __DIR__ . '/includes/deferred/HtmlFileCacheUpdate.php',
'Http' => __DIR__ . '/includes/http/Http.php',
'HttpError' => __DIR__ . '/includes/exception/HttpError.php',
'HttpStatus' => __DIR__ . '/includes/libs/HttpStatus.php',
use MediaWiki\Interwiki\InterwikiLookup;
use MagicWordFactory;
use MediaWiki\Storage\PageEditStash;
-use HtmlCacheUpdater;
/**
* Service locator for MediaWiki core services.
return $this->getService( 'GenderCache' );
}
- /**
- * @return HtmlCacheUpdater
- * @since 1.34
- */
- public function getHtmlCacheUpdater() {
- return $this->getService( 'HtmlCacheUpdater' );
- }
-
/**
* @since 1.31
* @return HttpRequestFactory
return new GenderCache( $services->getNamespaceInfo() );
},
- 'HtmlCacheUpdater' => function ( MediaWikiServices $services ) : HtmlCacheUpdater {
- return new HtmlCacheUpdater();
- },
-
'HttpRequestFactory' =>
function ( MediaWikiServices $services ) : HttpRequestFactory {
return new HttpRequestFactory();
/**
* Purge all applicable CDN URLs
- * @deprecated 1.34 Use HtmlCacheUpdater
*/
public function purgeSquid() {
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getCdnUrls() );
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $this->getCdnUrls() ),
+ DeferredUpdates::PRESEND
+ );
}
/**
* on the number of links. Typically called on create and delete.
*/
public function touchLinks() {
- $jobs = [];
- $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
- $this,
- 'pagelinks',
- [ 'causeAction' => 'page-touch' ]
- );
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) );
if ( $this->mNamespace == NS_CATEGORY ) {
- $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
- $this,
- 'categorylinks',
- [ 'causeAction' => 'category-touch' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' )
);
}
-
- JobQueueGroup::singleton()->lazyPush( $jobs );
}
/**
return $text;
}
- /**
- * @param string[] $prefixedDbKeys List of prefixed DB keys for pages to purge
- * @since 1.34
- */
- public static function purge( array $prefixedDbKeys ) {
- foreach ( $prefixedDbKeys as $prefixedDbKey ) {
- foreach ( self::cacheablePageActions() as $type ) {
- $fc = new self( $prefixedDbKey, $type );
- $fc->clearCache();
- }
- }
- }
-
/**
* Clear the file caches for a page for all actions
- * @param Traversable|Title[]|Title $titles
+ * @param Title $title
* @return bool Whether $wgUseFileCache is enabled
*/
- public static function clearFileCache( $titles ) {
+ public static function clearFileCache( Title $title ) {
$config = MediaWikiServices::getInstance()->getMainConfig();
+
if ( !$config->get( 'UseFileCache' ) ) {
return false;
}
- $titleIterator = ( $titles instanceof Title ) ? [ $titles ] : $titles;
- foreach ( $titleIterator as $title ) {
- self::purge( [ $title->getPrefixedDBkey() ] );
+ foreach ( self::cacheablePageActions() as $type ) {
+ $fc = new self( $title, $type );
+ $fc->clearCache();
}
return true;
+++ /dev/null
-<?php
-/**
- * HTML/file cache invalidation of cacheable variant/action URLs for a page
- *
- * 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
- */
-
-/**
- * Class to invalidate the HTML/file cache of cacheable variant/action URLs for a page
- *
- * @ingroup Cache
- * @since 1.34
- */
-class HtmlCacheUpdater {
- /** @var int Purge after the main transaction round and respect $wgCdnReboundPurgeDelay */
- const ISOLATION_AND_LAG_AWARE = 1;
- /** @var int Purge immediately and only once (ignore $wgCdnReboundPurgeDelay) */
- const IMMEDIATE_WITHOUT_REBOUND = 2;
-
- /**
- * Purge CDN/HTMLFileCache for a URL, Title, or iteratable of URL or Title entries
- *
- * String entries will be treated as URLs to be purged from the CDN layer.
- * For Title entries, all cacheable canonical URLs associated with the page
- * will be purged from the CDN and HTMLFileCache.
- *
- * The cache purges are queued as PRESEND deferred updates so that they run after the
- * main database transaction round of LBFactory. This reduces the chance of race conditions
- * where a stale value is re-populated before commit. Depending on $wgCdnReboundPurgeDelay,
- * a secondary set of purges might be issued several seconds later through the use of a
- * delayed job. This is used to mitigate the effects of DB replication lag as well as
- * multiple layers of CDN proxies. All deferred CDN purges are combined and de-duplicated
- * into a single DeferrableUpdate instance. This improves HTTP PURGE request pipelining.
- *
- * Use the IMMEDIATE_WITHOUT_REBOUND class constant to instantly issue the purges instead
- * and skip the use of any secondary purges regardless of $wgCdnReboundPurgeDelay.
- *
- * @param Traversable|Title[]|Title|string[]|string $entries
- * @param int $mode ISOLATION_AND_LAG_AWARE or IMMEDIATE_WITHOUT_REBOUND class constant
- */
- public function purge( $entries, $mode = self::ISOLATION_AND_LAG_AWARE ) {
- $urls = [];
- $titles = [];
- if ( is_string( $entries ) ) {
- $urls = [ $entries ];
- } elseif ( $entries instanceof Title ) {
- $titles = [ $entries ];
- } elseif ( $entries instanceof TitleArray ) {
- $titles = $entries; // save memory
- } else {
- foreach ( $entries as $entry ) {
- if ( is_string( $entry ) ) {
- $urls[] = $entry;
- } else {
- $titles[] = $entry;
- }
- }
- }
-
- if ( $mode === self::IMMEDIATE_WITHOUT_REBOUND ) {
- HTMLFileCache::clearFileCache( $titles );
- foreach ( $titles as $title ) {
- /** @var Title $title */
- $urls = array_merge( $urls, $title->getCdnUrls() );
- }
- CdnCacheUpdate::purge( $urls ); // purge once (no "rebound" purges)
- } else {
- DeferredUpdates::addUpdate(
- HtmlFileCacheUpdate::newFromTitles( $titles ),
- DeferredUpdates::PRESEND
- );
- DeferredUpdates::addUpdate(
- CdnCacheUpdate::newFromTitles( $titles, $urls ),
- DeferredUpdates::PRESEND
- );
- }
- }
-}
use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\NameTableAccessException;
use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
class ChangeTags {
/**
);
}
- $prevTags = self::getPrevTags( $rc_id, $log_id, $rev_id );
+ $prevTags = self::getTags( $dbw, $rc_id, $rev_id, $log_id );
// add tags
$tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
return [ $tagsToAdd, $tagsToRemove, $prevTags ];
}
- private static function getPrevTags( $rc_id = null, $log_id = null, $rev_id = null ) {
+ /**
+ * Return all the tags associated with the given recent change ID,
+ * revision ID, and/or log entry ID.
+ *
+ * @param IDatabase $db the database to query
+ * @param int|null $rc_id
+ * @param int|null $rev_id
+ * @param int|null $log_id
+ * @return string[]
+ */
+ public static function getTags( IDatabase $db, $rc_id = null, $rev_id = null, $log_id = null ) {
$conds = array_filter(
[
'ct_rc_id' => $rc_id,
- 'ct_log_id' => $log_id,
'ct_rev_id' => $rev_id,
+ 'ct_log_id' => $log_id,
]
);
- $dbw = wfGetDB( DB_MASTER );
- $tagIds = $dbw->selectFieldValues( 'change_tag', 'ct_tag_id', $conds, __METHOD__ );
+ $tagIds = $db->selectFieldValues(
+ 'change_tag',
+ 'ct_tag_id',
+ $conds,
+ __METHOD__
+ );
$tags = [];
+ $changeTagDefStore = MediaWikiServices::getInstance()->getChangeTagDefStore();
foreach ( $tagIds as $tagId ) {
- $tags[] = MediaWikiServices::getInstance()->getChangeTagDefStore()->getName( (int)$tagId );
+ $tags[] = $changeTagDefStore->getName( (int)$tagId );
}
return $tags;
use MediaWiki\MediaWikiServices;
/**
- * Handles purging the appropriate CDN objects given a list of URLs or Title instances
+ * Handles purging appropriate CDN URLs given a title (or titles)
* @ingroup Cache
*/
class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
/** @var string[] Collection of URLs to purge */
- private $urls = [];
+ protected $urls = [];
/**
* @param string[] $urlArr Collection of URLs to purge
$urlArr = array_merge( $urlArr, $title->getCdnUrls() );
}
- return new self( $urlArr );
+ return new CdnCacheUpdate( $urlArr );
}
+ /**
+ * Purges the list of URLs passed to the constructor.
+ */
public function doUpdate() {
global $wgCdnReboundPurgeDelay;
wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
// Reliably broadcast the purge to all edge nodes
+ $relayer = MediaWikiServices::getInstance()->getEventRelayerGroup()
+ ->getRelayer( 'cdn-url-purges' );
$ts = microtime( true );
- $relayerGroup = MediaWikiServices::getInstance()->getEventRelayerGroup();
- $relayerGroup->getRelayer( 'cdn-url-purges' )->notifyMulti(
+ $relayer->notifyMulti(
'cdn-url-purges',
array_map(
function ( $url ) use ( $ts ) {
*/
/**
- * Class to invalidate the HTML/file cache of all the pages linking to a given title.
+ * Class to invalidate the HTML cache of all the pages linking to a given title.
*
* @ingroup Cache
- * @deprecated Since 1.34; Enqueue jobs from HTMLCacheUpdateJob::newForBacklinks instead
*/
class HTMLCacheUpdate extends DataUpdate {
/** @var Title */
- private $title;
+ public $mTitle;
+
/** @var string */
- private $table;
+ public $mTable;
/**
- * @param Title $title
+ * @param Title $titleTo
* @param string $table
+ * @param string $causeAction Triggering action
+ * @param string $causeAgent Triggering user
*/
- public function __construct( Title $title, $table ) {
- $this->title = $title;
- $this->table = $table;
+ function __construct(
+ Title $titleTo, $table, $causeAction = 'unknown', $causeAgent = 'unknown'
+ ) {
+ $this->mTitle = $titleTo;
+ $this->mTable = $table;
+ $this->causeAction = $causeAction;
+ $this->causeAgent = $causeAgent;
}
public function doUpdate() {
$job = HTMLCacheUpdateJob::newForBacklinks(
- $this->title,
- $this->table,
+ $this->mTitle,
+ $this->mTable,
[ 'causeAction' => $this->getCauseAction(), 'causeAgent' => $this->getCauseAgent() ]
);
+
JobQueueGroup::singleton()->lazyPush( $job );
}
}
+++ /dev/null
-<?php
-/**
- * HTMLFileCache cache purging
- *
- * 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
- */
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Handles purging the appropriate HTMLFileCache files given a list of titles
- * @ingroup Cache
- */
-class HtmlFileCacheUpdate implements DeferrableUpdate {
- /** @var string[] Collection of prefixed DB keys for the pages to purge */
- private $prefixedDbKeys = [];
-
- /**
- * @param string[] $prefixedDbKeys
- */
- public function __construct( array $prefixedDbKeys ) {
- $this->prefixedDbKeys = $prefixedDbKeys;
- }
-
- /**
- * Create an update object from an array of Title objects, or a TitleArray object
- *
- * @param Traversable|Title[] $titles
- * @return HtmlFileCacheUpdate
- */
- public static function newFromTitles( $titles ) {
- $prefixedDbKeys = [];
- foreach ( $titles as $title ) {
- $prefixedDbKeys[] = $title->getPrefixedDBkey();
- }
-
- return new self( $prefixedDbKeys );
- }
-
- public function doUpdate() {
- $config = MediaWikiServices::getInstance()->getMainConfig();
- if ( $config->get( 'UseFileCache' ) ) {
- HTMLFileCache::purge( $this->prefixedDbKeys );
- }
- }
-}
private function invalidateProperties( $changed ) {
global $wgPagePropLinkInvalidations;
- $jobs = [];
foreach ( $changed as $name => $value ) {
if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
$inv = $wgPagePropLinkInvalidations[$name];
$inv = [ $inv ];
}
foreach ( $inv as $table ) {
- $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
- $this->mTitle,
- $table,
- [ 'causeAction' => 'page-props' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $this->mTitle, $table, 'page-props' )
);
}
}
}
-
- JobQueueGroup::singleton()->lazyPush( $jobs );
}
/**
$title = $this->getTitle();
if ( $title ) {
$title->invalidateCache();
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+ $title->purgeSquid();
}
}
// Purge cache of all pages using this file
$title = $this->getTitle();
if ( $title ) {
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $title,
- 'imagelinks',
- [ 'causeAction' => 'file-purge' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'imagelinks', 'file-purge' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
}
}
$this->purgeThumbnails( $options );
// Purge CDN cache for this file
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->getUrl() );
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( [ $this->getUrl() ] ),
+ DeferredUpdates::PRESEND
+ );
}
/**
foreach ( $files as $file ) {
$urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
}
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
+ DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
}
/**
$this->purgeThumbList( $dir, $files );
// Purge the CDN
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $urls );
+ DeferredUpdates::addUpdate( new CdnCacheUpdate( $urls ), DeferredUpdates::PRESEND );
}
/**
}
} else {
# Existing file page: invalidate description page cache
- $title = $wikiPage->getTitle();
- $title->invalidateCache();
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+ $wikiPage->getTitle()->invalidateCache();
+ $wikiPage->getTitle()->purgeSquid();
# Allow the new file version to be patrolled from the page footer
Article::purgePatrolFooterCache( $descId );
}
# Delete old thumbnails
$this->purgeThumbnails();
# Remove the old file from the CDN cache
- MediaWikiServices::getInstance()
- ->getHtmlCacheUpdater()->purge( $this->getUrl() );
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( [ $this->getUrl() ] ),
+ DeferredUpdates::PRESEND
+ );
} else {
# Update backlink pages pointing to this title if created
LinksUpdate::queueRecursiveJobsForTable(
}
# Invalidate cache for all pages using this file
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $this->getTitle(),
- 'imagelinks',
- [ 'causeAction' => 'file-upload', 'causeAgent' => $user->getName() ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $this->getTitle(), 'imagelinks', 'file-upload' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
return Status::newGood();
}
foreach ( $archiveNames as $archiveName ) {
$purgeUrls[] = $this->getArchiveUrl( $archiveName );
}
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
+ DeferredUpdates::addUpdate( new CdnCacheUpdate( $purgeUrls ), DeferredUpdates::PRESEND );
return $status;
}
$this->purgeDescription();
}
- $url = $this->getArchiveUrl( $archiveName );
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $url );
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( [ $this->getArchiveUrl( $archiveName ) ] ),
+ DeferredUpdates::PRESEND
+ );
return $status;
}
use MediaWiki\MediaWikiServices;
/**
- * Job to purge the HTML/file cache for all pages that link to or use another page or file
+ * Job to purge the cache for all pages that link to or use another page or file
*
* This job comes in a few variants:
* - a) Recursive jobs to purge caches for backlink pages for a given title.
* @param array $pages Map of (page ID => (namespace, DB key)) entries
*/
protected function invalidateTitles( array $pages ) {
- global $wgUpdateRowsPerQuery, $wgPageLanguageUseDB;
+ global $wgUpdateRowsPerQuery, $wgUseFileCache, $wgPageLanguageUseDB;
// Get all page IDs in this query into an array
$pageIds = array_keys( $pages );
__METHOD__
) );
- // Update CDN and file caches (avoiding secondary purge overhead)
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge(
- $titleArray,
- HtmlCacheUpdater::IMMEDIATE_WITHOUT_REBOUND
- );
+ // Update CDN; call purge() directly so as to not bother with secondary purges
+ $urls = [];
+ foreach ( $titleArray as $title ) {
+ /** @var Title $title */
+ $urls = array_merge( $urls, $title->getCdnUrls() );
+ }
+ CdnCacheUpdate::purge( $urls );
+
+ // Update file cache
+ if ( $wgUseFileCache ) {
+ foreach ( $titleArray as $title ) {
+ HTMLFileCache::clearFileCache( $title );
+ }
+ }
}
public function getDeduplicationInfo() {
*/
/**
- * Redis-based caching module for redis server >= 2.6.12
+ * Redis-based caching module for redis server >= 2.6.12 and phpredis >= 2.2.4
*
+ * @see https://github.com/phpredis/phpredis/blob/d310ed7c8/Changelog.md
* @note Avoid use of Redis::MULTI transactions for twemproxy support
*
* @ingroup Cache
$e = null;
try {
// Note that redis does not return false if the key was not there
- $result = ( $conn->delete( $key ) !== false );
+ $result = ( $conn->del( $key ) !== false );
} catch ( RedisException $e ) {
$result = false;
$this->handleException( $conn, $e );
// Avoid delete() with array to reduce CPU hogging from a single request
$conn->multi( Redis::PIPELINE );
foreach ( $batchKeys as $key ) {
- $conn->delete( $key );
+ $conn->del( $key );
}
$batchResult = $conn->exec();
if ( $batchResult === false ) {
$this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
}
+ /**
+ * @param DatabaseDomain $domain
+ * @throws DBConnectionError
+ * @throws DBError
+ * @since 1.32
+ */
protected function doSelectDomain( DatabaseDomain $domain ) {
$this->currentDomain = $domain;
}
throw $this->newExceptionAfterConnectError( "DB path or directory required" );
}
- if ( !self::isProcessMemoryPath( $path ) && !is_readable( $path ) ) {
+ // Check if the database file already exists but is non-readable
+ if (
+ !self::isProcessMemoryPath( $path ) &&
+ file_exists( $path ) &&
+ !is_readable( $path )
+ ) {
throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
} elseif ( !in_array( $this->trxMode, self::$VALID_TRX_MODES, true ) ) {
throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
}
try {
+ // Open the database file, creating it if it does not yet exist
$this->conn = new PDO( "sqlite:$path", null, null, $attributes );
} catch ( PDOException $e ) {
throw $this->newExceptionAfterConnectError( $e->getMessage() );
return false;
}
+ protected function doSelectDomain( DatabaseDomain $domain ) {
+ if ( $domain->getSchema() !== null ) {
+ throw new DBExpectedError(
+ $this,
+ __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
+ );
+ }
+
+ $database = $domain->getDatabase();
+ // A null database means "don't care" so leave it as is and update the table prefix
+ if ( $database === null ) {
+ $this->currentDomain = new DatabaseDomain(
+ $this->currentDomain->getDatabase(),
+ null,
+ $domain->getTablePrefix()
+ );
+
+ return true;
+ }
+
+ if ( $database !== $this->getDBname() ) {
+ throw new DBExpectedError(
+ $this,
+ __CLASS__ . ": cannot change database (got '$database')"
+ );
+ }
+
+ return true;
+ }
+
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*
}
public function serverIsReadOnly() {
+ $this->assertHasConnectionHandle();
+
$path = $this->getDbFilePath();
return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
*
* @param string $db
* @return bool True unless an exception was thrown
- * @throws DBConnectionError If databasesAreIndependent() is true and an error occurs
- * @throws DBError
+ * @throws DBConnectionError If databasesAreIndependent() is true and connection change fails
+ * @throws DBError On query error or if database changes are disallowed
* @deprecated Since 1.32 Use selectDomain() instead
*/
public function selectDB( $db );
* This should only be called by a load balancer or if the handle is not attached to one
*
* @param string|DatabaseDomain $domain
+ * @throws DBConnectionError If databasesAreIndependent() is true and connection change fails
+ * @throws DBError On query error, if domain changes are disallowed, or the domain is invalid
* @since 1.32
- * @throws DBConnectionError
*/
public function selectDomain( $domain );
Hooks::run( 'ArticleUndelete',
[ &$this->title, $created, $comment, $oldPageId, $restoredPages ] );
-
if ( $this->title->getNamespace() == NS_FILE ) {
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $this->title,
- 'imagelinks',
- [ 'causeAction' => 'imagelinks', 'causeAgent' => 'file-restore' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $this->title, 'imagelinks', 'file-restore' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
}
}
if ( $this->mFile->exists() ) {
wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $this->mTitle,
- 'imagelinks',
- [ 'causeAction' => 'file-purge' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $this->mTitle, 'imagelinks', 'file-purge' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
} else {
wfDebug( 'ImagePage::doPurge no image for '
. $this->mFile->getName() . "; limiting purge to cache only\n" );
$this->mTitle->invalidateCache();
- // Clear file cache and send purge after above page_touched update was committed
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->mTitle );
+ // Clear file cache
+ HTMLFileCache::clearFileCache( $this->getTitle() );
+ // Send purge after above page_touched update was committed
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
+ DeferredUpdates::PRESEND
+ );
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$messageCache = MessageCache::singleton();
// Update existence markers on article/talk tabs...
$other = $title->getOtherPage();
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
+ $other->purgeSquid();
$title->touchLinks();
+ $title->purgeSquid();
$title->deleteTitleProtection();
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
// Invalidate caches of articles which include this page
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $title,
- 'templatelinks',
- [ 'causeAction' => 'page-create' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
if ( $title->getNamespace() == NS_CATEGORY ) {
// Load the Category object, which will schedule a job to create
// TODO: move this into a PageEventEmitter service
// Update existence markers on article/talk tabs...
+ // Clear Backlink cache first so that purge jobs use more up-to-date backlink information
+ BacklinkCache::get( $title )->clear();
$other = $title->getOtherPage();
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( [ $title, $other ] );
+ $other->purgeSquid();
$title->touchLinks();
+ $title->purgeSquid();
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
+ // File cache
+ HTMLFileCache::clearFileCache( $title );
InfoAction::invalidateCache( $title );
// Messages
// Images
if ( $title->getNamespace() == NS_FILE ) {
- $job = HTMLCacheUpdateJob::newForBacklinks(
- $title,
- 'imagelinks',
- [ 'causeAction' => 'page-delete' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
);
- JobQueueGroup::singleton()->lazyPush( $job );
}
// User talk pages
) {
// TODO: move this into a PageEventEmitter service
- $jobs = [];
- if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
+ if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
// Invalidate caches of articles which include this page.
// Only for the main slot, because only the main slot is transcluded.
// TODO: MCR: not true for TemplateStyles! [SlotHandler]
- $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
- $title,
- 'templatelinks',
- [ 'causeAction' => 'page-edit' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
);
}
+
// Invalidate the caches of all pages which redirect here
- $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
- $title,
- 'redirect',
- [ 'causeAction' => 'page-edit' ]
+ DeferredUpdates::addUpdate(
+ new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
);
- JobQueueGroup::singleton()->lazyPush( $jobs );
MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $title );
+ // Purge CDN for this page only
+ $title->purgeSquid();
+ // Clear file cache for this page only
+ HTMLFileCache::clearFileCache( $title );
// Purge ?action=info cache
$revid = $revision ? $revision->getId() : null;
$file->purgeOldThumbnails( $archiveName );
$purgeUrls[] = $file->getArchiveUrl( $archiveName );
}
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $purgeUrls );
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $purgeUrls ),
+ DeferredUpdates::PRESEND
+ );
return Status::newGood();
}
* @ingroup RevisionDelete
*/
-use MediaWiki\MediaWikiServices;
use MediaWiki\Storage\RevisionRecord;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\IDatabase;
}
public function doPostCommitUpdates( array $visibilityChangeMap ) {
- MediaWikiServices::getInstance()->getHtmlCacheUpdater()->purge( $this->title );
+ $this->title->purgeSquid();
// Extensions that require referencing previous revisions may need this
- Hooks::run( 'ArticleRevisionVisibilitySet',
- [ $this->title, $this->ids, $visibilityChangeMap ] );
+ Hooks::run( 'ArticleRevisionVisibilitySet', [ $this->title, $this->ids, $visibilityChangeMap ] );
return Status::newGood();
}
}
$opts->add( 'feed', '' );
$opts->add( 'tagfilter', '' );
$opts->add( 'invert', false );
+ $opts->add( 'associated', false );
$opts->add( 'size-mode', 'max' );
$opts->add( 'size', 0 );
$username = $this->opts->consumeValue( 'username' );
$tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
$nsinvert = $this->opts->consumeValue( 'invert' );
+ $nsassociated = $this->opts->consumeValue( 'associated' );
$size = $this->opts->consumeValue( 'size' );
$max = $this->opts->consumeValue( 'size-mode' ) === 'max';
'default' => $nsinvert,
'tooltip' => 'invert',
],
+ 'nsassociated' => [
+ 'type' => 'check',
+ 'name' => 'associated',
+ 'label-message' => 'namespace_association',
+ 'default' => $nsassociated,
+ 'tooltip' => 'namespace_association',
+ ],
'tagFilter' => [
'type' => 'tagfilter',
'name' => 'tagfilter',
/**
* @ingroup Pager
*/
+use MediaWiki\MediaWikiServices;
+
class NewPagesPager extends ReverseChronologicalPager {
/**
$conds = [];
$conds['rc_new'] = 1;
- $namespace = $this->opts->getValue( 'namespace' );
- $namespace = ( $namespace === 'all' ) ? false : intval( $namespace );
-
$username = $this->opts->getValue( 'username' );
$user = Title::makeTitleSafe( NS_USER, $username );
}
}
- if ( $namespace !== false ) {
- if ( $this->opts->getValue( 'invert' ) ) {
- $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace );
- } else {
- $conds['rc_namespace'] = $namespace;
- }
- }
-
if ( $user ) {
$conds[] = ActorMigration::newMigration()->getWhere(
$this->mDb, 'rc_user', User::newFromName( $user->getText(), false ), false
$conds[] = ActorMigration::newMigration()->isAnon( $rcQuery['fields']['rc_user'] );
}
+ $conds = array_merge( $conds, $this->getNamespaceCond() );
+
# If this user cannot see patrolled edits or they are off, don't do dumb queries!
if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
$conds['rc_patrolled'] = RecentChange::PRC_UNPATROLLED;
return $info;
}
+ // Based on ContribsPager.php
+ function getNamespaceCond() {
+ $namespace = $this->opts->getValue( 'namespace' );
+ if ( $namespace === 'all' || $namespace === '' ) {
+ return [];
+ }
+
+ $namespace = intval( $namespace );
+ $invert = $this->opts->getValue( 'invert' );
+ $associated = $this->opts->getValue( 'associated' );
+
+ $eq_op = $invert ? '!=' : '=';
+ $bool_op = $invert ? 'AND' : 'OR';
+
+ if ( !$associated ) {
+ return [ "rc_namespace $eq_op " . $this->mDb->addQuotes( $namespace ) ];
+ }
+
+ $associatedNS = MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociated( $namespace );
+ return [
+ "rc_namespace $eq_op " . $this->mDb->addQuotes( $namespace ) .
+ $bool_op .
+ " rc_namespace $eq_op " . $this->mDb->addQuotes( $associatedNS )
+ ];
+ }
+
function getIndexField() {
return 'rc_timestamp';
}
$this->tablesUsed[] = 'archive';
}
- // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
+ // TODO most methods are not tested
/** @dataProvider provideModifyDisplayQuery */
public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
$this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
}
+ public function provideTags() {
+ $tags = [ 'tag 1', 'tag 2', 'tag 3' ];
+ $rcId = 123;
+ $revId = 456;
+ $logId = 789;
+
+ yield [ $tags, $rcId, null, null ];
+ yield [ $tags, null, $revId, null ];
+ yield [ $tags, null, null, $logId ];
+ yield [ $tags, $rcId, $revId, null ];
+ yield [ $tags, $rcId, null, $logId ];
+ yield [ $tags, $rcId, $revId, $logId ];
+ }
+
+ /**
+ * @dataProvider provideTags
+ */
+ public function testGetTags( array $tags, $rcId, $revId, $logId ) {
+ ChangeTags::addTags( $tags, $rcId, $revId, $logId );
+
+ $actualTags = ChangeTags::getTags( $this->db, $rcId, $revId, $logId );
+
+ $this->assertSame( $tags, $actualTags );
+ }
+
+ public function testGetTags_multiple_arguments() {
+ $rcId = 123;
+ $revId = 456;
+ $logId = 789;
+
+ ChangeTags::addTags( [ 'tag 1' ], $rcId );
+ ChangeTags::addTags( [ 'tag 2' ], $rcId, $revId );
+ ChangeTags::addTags( [ 'tag 3' ], $rcId, $revId, $logId );
+
+ $tags3 = [ 'tag 3' ];
+ $tags2 = array_merge( $tags3, [ 'tag 2' ] );
+ $tags1 = array_merge( $tags2, [ 'tag 1' ] );
+ $this->assertArrayEquals( $tags3, ChangeTags::getTags( $this->db, $rcId, $revId, $logId ) );
+ $this->assertArrayEquals( $tags2, ChangeTags::getTags( $this->db, $rcId, $revId ) );
+ $this->assertArrayEquals( $tags1, ChangeTags::getTags( $this->db, $rcId ) );
+ }
+
public function testTagUsageStatistics() {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'change_tag', '*' );
* @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
* @expectedException \Wikimedia\Rdbms\DBConnectionError
*/
- public function testInvalidSelectDBIndependant() {
+ public function testInvalidSelectDBIndependent() {
$dbname = 'unittest-domain'; // explodes if DB is selected
$factory = $this->newLBFactoryMulti(
[ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
[
- 'dbname' => 'do_not_select_me' // explodes if DB is selected
+ // Explodes with SQLite and Postgres during open/USE
+ 'dbname' => 'bad_dir/do_not_select_me'
]
);
$lb = $factory->getMainLB();
- if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
$this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
}
/**
* @covers \Wikimedia\Rdbms\DatabaseSqlite::selectDB
* @covers \Wikimedia\Rdbms\DatabasePostgres::selectDB
- * @expectedException \Wikimedia\Rdbms\DBConnectionError
+ * @expectedException \Wikimedia\Rdbms\DBExpectedError
*/
- public function testInvalidSelectDBIndependant2() {
+ public function testInvalidSelectDBIndependent2() {
$dbname = 'unittest-domain'; // explodes if DB is selected
$factory = $this->newLBFactoryMulti(
[ 'localDomain' => ( new DatabaseDomain( $dbname, null, '' ) )->getId() ],
[
- 'dbname' => 'do_not_select_me' // explodes if DB is selected
+ // Explodes with SQLite and Postgres during open/USE
+ 'dbname' => 'bad_dir/do_not_select_me'
]
);
$lb = $factory->getMainLB();
- if ( !wfGetDB( DB_MASTER )->databasesAreIndependent() ) {
+ if ( !$lb->getConnection( DB_MASTER )->databasesAreIndependent() ) {
$this->markTestSkipped( "Not applicable per databasesAreIndependent()" );
}
$db = $lb->getConnection( DB_MASTER );
- \Wikimedia\suppressWarnings();
$db->selectDB( 'garbage-db' );
- \Wikimedia\restoreWarnings();
}
/**