* $wgDebugDumpSqlLength was removed (deprecated in 1.24).
* $wgDebugDBTransactions was removed (deprecated in 1.20).
* $wgRemoteUploadTarget (added in 1.26) removed, replaced by $wgForeignUploadTargets
+* $wgUseXVO has been removed, as it provides functionality only used by
+ custom Wikimedia patches against Squid 2.x that probably noone uses in
+ production anymore. There is now $wgUseKeyHeader that provides similar
+ functionality but instead of the MediaWiki-specific X-Vary-Options header,
+ uses the draft Key header standard.
=== New features in 1.27 ===
* $wgDataCenterId and $wgDataCenterRoles where added, which will serve as
$wgUseESI = false;
/**
- * Send X-Vary-Options header for better caching (requires patched Squid)
+ * Send the Key HTTP header for better caching.
+ * See https://datatracker.ietf.org/doc/draft-fielding-http-key/ for details.
+ * @since 1.27
*/
-$wgUseXVO = false;
+$wgUseKeyHeader = false;
/**
- * Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API
- * requests and RSS/Atom feeds. Use this if you have an SSL termination setup
+ * Add X-Forwarded-Proto to the Vary and Key headers for API requests and
+ * RSS/Atom feeds. Use this if you have an SSL termination setup
* and need to split the cache between HTTP and HTTPS for API requests,
* feed requests and HTTP redirect responses in order to prevent cache
* pollution. This does not affect 'normal' requests to index.php other than
}
if ( !$foundVary ) {
header( 'Vary: Accept-Encoding' );
- global $wgUseXVO;
- if ( $wgUseXVO ) {
- header( 'X-Vary-Options: Accept-Encoding;list-contains=gzip' );
+ global $wgUseKeyHeader;
+ if ( $wgUseKeyHeader ) {
+ header( 'Key: Accept-Encoding;match=gzip' );
}
}
return $s;
private $mIndexPolicy = 'index';
private $mFollowPolicy = 'follow';
private $mVaryHeader = array(
- 'Accept-Encoding' => array( 'list-contains=gzip' ),
+ 'Accept-Encoding' => array( 'match=gzip' ),
);
/**
* @return bool
*/
function haveCacheVaryCookies() {
- $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
- if ( $cookieHeader === false ) {
- return false;
- }
- $cvCookies = $this->getCacheVaryCookies();
- foreach ( $cvCookies as $cookieName ) {
- # Check for a simple string match, like the way squid does it
- if ( strpos( $cookieHeader, $cookieName ) !== false ) {
+ $request = $this->getRequest();
+ foreach ( $this->getCacheVaryCookies() as $cookieName ) {
+ if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
wfDebug( __METHOD__ . ": found $cookieName\n" );
return true;
}
* Add an HTTP header that will influence on the cache
*
* @param string $header Header name
- * @param string[]|null $option Options for X-Vary-Options. Possible options are:
- * - "string-contains=$XXX" varies on whether the header value as a string
- * contains $XXX as a substring.
- * - "list-contains=$XXX" varies on whether the header value as a
- * comma-separated list contains $XXX as one of the list items.
+ * @param string[]|null $option Options for the Key header. See
+ * https://datatracker.ietf.org/doc/draft-fielding-http-key/
+ * for the list of valid options.
*/
public function addVaryHeader( $header, array $option = null ) {
if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
}
/**
- * Get a complete X-Vary-Options header
+ * Get a complete Key header
*
* @return string
*/
- public function getXVO() {
+ public function getKeyHeader() {
$cvCookies = $this->getCacheVaryCookies();
$cookiesOption = array();
foreach ( $cvCookies as $cookieName ) {
- $cookiesOption[] = 'string-contains=' . $cookieName;
+ $cookiesOption[] = 'param=' . $cookieName;
}
$this->addVaryHeader( 'Cookie', $cookiesOption );
}
$headers[] = $newheader;
}
- $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
+ $key = 'Key: ' . implode( ',', $headers );
- return $xvo;
+ return $key;
}
/**
- * bug 21672: Add Accept-Language to Vary and XVO headers
+ * T23672: Add Accept-Language to Vary and Key headers
* if there's no 'variant' parameter existed in GET.
*
* For example:
if ( $variant === $lang->getCode() ) {
continue;
} else {
- $aloption[] = 'string-contains=' . $variant;
+ $aloption[] = 'substr=' . $variant;
// IE and some other browsers use BCP 47 standards in
// their Accept-Language header, like "zh-CN" or "zh-Hant".
// We should handle these too.
$variantBCP47 = wfBCP47( $variant );
if ( $variantBCP47 !== $variant ) {
- $aloption[] = 'string-contains=' . $variantBCP47;
+ $aloption[] = 'substr=' . $variantBCP47;
}
}
}
# maintain different caches for logged-in users and non-logged in ones
$response->header( $this->getVaryHeader() );
- if ( $config->get( 'UseXVO' ) ) {
- # Add an X-Vary-Options header for Squid with Wikimedia patches
- $response->header( $this->getXVO() );
+ if ( $config->get( 'UseKeyHeader' ) ) {
+ $response->header( $this->getKeyHeader() );
}
if ( $this->mEnableClientCache ) {
* @return bool
*/
public function userCanPreview() {
- if ( $this->getRequest()->getVal( 'action' ) != 'submit'
- || !$this->getRequest()->wasPosted()
- || !$this->getUser()->matchEditToken(
- $this->getRequest()->getVal( 'wpEditToken' ) )
- ) {
+ $request = $this->getRequest();
+ if ( $request->getVal( 'action' ) !== 'submit' || !$request->wasPosted() ) {
return false;
}
- if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
+
+ $user = $this->getUser();
+ if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
return false;
}
- if ( !$this->getTitle()->isSubpageOf( $this->getUser()->getUserPage() ) ) {
+
+ $title = $this->getTitle();
+ if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
+ return false;
+ }
+ if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
// Don't execute another user's CSS or JS on preview (T85855)
return false;
}
- return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
+ $errors = $title->getUserPermissionsErrors( 'edit', $user );
+ if ( count( $errors ) !== 0 ) {
+ return false;
+ }
+
+ return true;
}
/**
return;
}
- $useXVO = $config->get( 'UseXVO' );
+ $useKeyHeader = $config->get( 'UseKeyHeader' );
if ( $this->mCacheMode == 'anon-public-user-private' ) {
$out->addVaryHeader( 'Cookie' );
$response->header( $out->getVaryHeader() );
- if ( $useXVO ) {
- $response->header( $out->getXVO() );
+ if ( $useKeyHeader ) {
+ $response->header( $out->getKeyHeader() );
if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
$response->header( "Cache-Control: $privateCache" );
$response->header( "Cache-Control: $privateCache" );
return;
- } // else no XVO and anonymous, send public headers below
+ } // else no Key and anonymous, send public headers below
}
// Send public headers
$response->header( $out->getVaryHeader() );
- if ( $useXVO ) {
- $response->header( $out->getXVO() );
+ if ( $useKeyHeader ) {
+ $response->header( $out->getKeyHeader() );
}
// If nobody called setCacheMaxAge(), use the (s)maxage parameters
* @since 1.19
*/
class DeferredUpdates {
- /** @var array Updates to be deferred until the end of the request */
+ /** @var DeferrableUpdate[] Updates to be deferred until the end of the request */
private static $updates = array();
/** @var bool Defer updates fully even in CLI mode */
private static $forceDeferral = false;
while ( count( $updates ) ) {
self::clearPendingUpdates();
-
- /** @var DeferrableUpdate $update */
+ /** @var DataUpdate[] $dataUpdates */
+ $dataUpdates = array();
+ /** @var DeferrableUpdate[] $otherUpdates */
+ $otherUpdates = array();
foreach ( $updates as $update ) {
+ if ( $update instanceof DataUpdate ) {
+ $dataUpdates[] = $update;
+ } else {
+ $otherUpdates[] = $update;
+ }
+ }
+
+ // Delegate DataUpdate execution to the DataUpdate class
+ DataUpdate::runUpdates( $dataUpdates, 'run' );
+ // Execute the non-DataUpdate tasks
+ foreach ( $otherUpdates as $update ) {
try {
$update->doUpdate();
* @return Closure
*/
protected function getDBFactory() {
- return function( $index ) {
- return DatabaseBase::factory( $this->dbType,
- array(
- 'host' => $this->dbServer,
- 'user' => $this->dbUser,
- 'password' => $this->dbPassword,
- 'dbname' => $this->dbName,
- 'flags' => $this->dbFlags,
- 'tablePrefix' => $this->tablePrefix,
- 'foreign' => true,
- )
- );
+ $type = $this->dbType;
+ $params = array(
+ 'host' => $this->dbServer,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'dbname' => $this->dbName,
+ 'flags' => $this->dbFlags,
+ 'tablePrefix' => $this->tablePrefix,
+ 'foreign' => true,
+ );
+
+ return function ( $index ) use ( $type, $params ) {
+ return DatabaseBase::factory( $type, $params );
};
}
* @return mixed Value to use for the key
*/
final public function getWithSetCallback(
- $key, $ttl, $callback, array $opts = array(), $oldOpts = null
+ $key, $ttl, $callback, array $opts = array(), $oldOpts = array()
) {
// Back-compat with 1.26: Swap $ttl and $callback
if ( is_int( $callback ) ) {
}
if ( $title instanceof Title ) {
- $output .= "<li>"
- . Linker::link( $title )
- . ' (' . Linker::link( $title->getTalkPage(), $talk )
- . ")</li>\n";
+ $output .= '<li>' .
+ Linker::link( $title ) . ' ' .
+ $this->msg( 'parentheses' )->rawParams(
+ Linker::link( $title->getTalkPage(), $talk )
+ )->escaped() .
+ "</li>\n";
}
}
$link = '<span class="watchlistredir">' . $link . '</span>';
}
- return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
+ return $link . ' ' .
+ $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped();
}
/**
* @dataProvider provideVaryHeaders
* @covers OutputPage::addVaryHeader
* @covers OutputPage::getVaryHeader
- * @covers OutputPage::getXVO
+ * @covers OutputPage::getKeyHeader
*/
- public function testVaryHeaders( $calls, $vary, $xvo ) {
+ public function testVaryHeaders( $calls, $vary, $key ) {
// get rid of default Vary fields
$outputPage = $this->getMockBuilder( 'OutputPage' )
->setConstructorArgs( array( new RequestContext() ) )
call_user_func_array( array( $outputPage, 'addVaryHeader' ), $call );
}
$this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' );
- $this->assertEquals( $xvo, $outputPage->getXVO(), 'X-Vary-Options:' );
+ $this->assertEquals( $key, $outputPage->getKeyHeader(), 'Key:' );
}
public function provideVaryHeaders() {
- // note: getXVO() automatically adds Vary: Cookie
+ // note: getKeyHeader() automatically adds Vary: Cookie
return array(
array( // single header
array(
array( 'Cookie' ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie',
+ 'Key: Cookie',
),
array( // non-unique headers
array(
array( 'Cookie' ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie,Accept-Language',
+ 'Key: Cookie,Accept-Language',
),
array( // two headers with single options
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Accept-Language', array( 'string-contains=en' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Accept-Language', array( 'substr=en' ) ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en',
+ 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
),
array( // one header with multiple options
array(
- array( 'Cookie', array( 'string-contains=phpsessid', 'string-contains=userId' ) ),
+ array( 'Cookie', array( 'param=phpsessid', 'param=userId' ) ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId',
+ 'Key: Cookie;param=phpsessid;param=userId',
),
array( // Duplicate option
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Accept-Language', array( 'string-contains=en', 'string-contains=en' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Accept-Language', array( 'substr=en', 'substr=en' ) ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en',
+ 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
),
array( // Same header, different options
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Cookie', array( 'string-contains=userId' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Cookie', array( 'param=userId' ) ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId',
+ 'Key: Cookie;param=phpsessid;param=userId',
),
);
}
+
+ /**
+ * @covers OutputPage::haveCacheVaryCookies
+ */
+ function testHaveCacheVaryCookies() {
+ $request = new FauxRequest();
+ $context = new RequestContext();
+ $context->setRequest( $request );
+ $outputPage = new OutputPage( $context );
+
+ // No cookies are set.
+ $this->assertFalse( $outputPage->haveCacheVaryCookies() );
+
+ // 'Token' is present but empty, so it shouldn't count.
+ $request->setCookie( 'Token', '' );
+ $this->assertFalse( $outputPage->haveCacheVaryCookies() );
+
+ // 'Token' present and nonempty.
+ $request->setCookie( 'Token', '123' );
+ $this->assertTrue( $outputPage->haveCacheVaryCookies() );
+ }
}
/**