[[iw:User:Example|iw>Example]].
* (T111605) The 'ImportHandleUnknownUser' hook allows extensions to auto-create
users during an import.
+* Added a hook, ParserOutputPostCacheTransform, to allow extensions to affect
+ the ParserOutput::getText() post-cache transformations.
=== External library changes in 1.31 ===
* The Block class will no longer accept usable-but-missing usernames for
'byText' or ->setBlocker(). Callers should either ensure the blocker exists
locally or use a new interwiki-format username like "iw>Example".
+* The following methods that get and set ParserOutput state are deprecated.
+ Callers should use the new stateless $options parameter to
+ ParserOutput::getText() instead.
+ * ParserOptions::getEditSection()
+ * ParserOptions::setEditSection()
+ * ParserOutput::getEditSectionTokens()
+ * ParserOutput::setEditSectionTokens()
+ * ParserOutput::getTOCEnabled()
+ * ParserOutput::setTOCEnabled()
+ * OutputPage::enableSectionEditLinks()
+ * OutputPage::sectionEditLinksEnabled()
+ * The public ParserOutput state fields $mTOCEnabled and $mEditSectionTokens are also deprecated.
== Compatibility ==
MediaWiki 1.31 requires PHP 5.5.9 or later. There is experimental support for
callable here. The callable is passed the ParserOptions object and the option
name.
+'ParserOutputPostCacheTransform': Called from ParserOutput::getText() to do
+post-cache transforms.
+$parserOutput: The ParserOutput object.
+&$text: The text being transformed, before core transformations are done.
+&$options: The options array being used for the transformation.
+
'ParserSectionCreate': Called each time the parser creates a document section
from wikitext. Use this to apply per-section modifications to HTML (like
wrapping the section in a DIV). Caveat: DIVs are valid wikitext, and a DIV
* @file
*/
-/**
- * @defgroup Globalsettings Global settings
- */
-
/**
* @cond file_level_code
* This is not a valid entry point, perform no further processing unless
$parserOutput->setEditSectionTokens( false ); // no section edit links
return [
'parserOutput' => $parserOutput,
- 'html' => $parserOutput->getText() ];
+ 'html' => $parserOutput->getText( [
+ 'enableSectionEditLinks' => false
+ ] )
+ ];
}
/**
$this->getLanguage()
);
- return $out instanceof ParserOutput ? $out->getText() : $out;
+ return $out instanceof ParserOutput
+ ? $out->getText( [ 'enableSectionEditLinks' => false ] )
+ : $out;
}
/**
$popts->setTidy( $oldTidy );
- $this->addParserOutput( $parserOutput );
+ $this->addParserOutput( $parserOutput, [
+ 'enableSectionEditLinks' => false,
+ ] );
}
/**
*
* @since 1.24
* @param ParserOutput $parserOutput
+ * @param array $poOptions Options to ParserOutput::getText()
*/
- public function addParserOutputContent( $parserOutput ) {
- $this->addParserOutputText( $parserOutput );
+ public function addParserOutputContent( $parserOutput, $poOptions = [] ) {
+ $this->addParserOutputText( $parserOutput, $poOptions );
$this->addModules( $parserOutput->getModules() );
$this->addModuleScripts( $parserOutput->getModuleScripts() );
*
* @since 1.24
* @param ParserOutput $parserOutput
+ * @param array $poOptions Options to ParserOutput::getText()
*/
- public function addParserOutputText( $parserOutput ) {
- $text = $parserOutput->getText();
+ public function addParserOutputText( $parserOutput, $poOptions = [] ) {
+ $text = $parserOutput->getText( $poOptions );
// Avoid PHP 7.1 warning of passing $this by reference
$outputPage = $this;
Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
* Add everything from a ParserOutput object.
*
* @param ParserOutput $parserOutput
+ * @param array $poOptions Options to ParserOutput::getText()
*/
- function addParserOutput( $parserOutput ) {
+ function addParserOutput( $parserOutput, $poOptions = [] ) {
$this->addParserOutputMetadata( $parserOutput );
// Touch section edit links only if not previously disabled
if ( $parserOutput->getEditSectionTokens() ) {
$parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
}
+ if ( !$this->mEnableSectionEditLinks
+ && !array_key_exists( 'enableSectionEditLinks', $poOptions )
+ ) {
+ $poOptions['enableSectionEditLinks'] = false;
+ }
- $this->addParserOutputText( $parserOutput );
+ $this->addParserOutputText( $parserOutput, $poOptions );
}
/**
$popts->setTargetLanguage( $oldLang );
}
- return $parserOutput->getText();
+ return $parserOutput->getText( [
+ 'enableSectionEditLinks' => false,
+ ] );
}
/**
* Enables/disables section edit links, doesn't override __NOEDITSECTION__
* @param bool $flag
* @since 1.23
+ * @deprecated since 1.31, use $poOptions to addParserOutput() instead.
*/
public function enableSectionEditLinks( $flag = true ) {
$this->mEnableSectionEditLinks = $flag;
/**
* @return bool
* @since 1.23
+ * @deprecated since 1.31, use $poOptions to addParserOutput() instead.
*/
public function sectionEditLinksEnabled() {
return $this->mEnableSectionEditLinks;
// Initialize the request object in $wgRequest
$wgRequest = RequestContext::getMain()->getRequest(); // BackCompat
-// Set user IP/agent information for causal consistency purposes
+// Set user IP/agent information for causal consistency purposes.
+// The cpPosTime cookie has no prefix and is set by MediaWiki::preOutputCommit().
+$cpPosTime = $wgRequest->getFloat( 'cpPosTime', $wgRequest->getCookie( 'cpPosTime', '' ) );
MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->setRequestInfo( [
'IPAddress' => $wgRequest->getIP(),
'UserAgent' => $wgRequest->getHeader( 'User-Agent' ),
'ChronologyProtection' => $wgRequest->getHeader( 'ChronologyProtection' ),
- // The cpPosTime cookie has no prefix and is set by MediaWiki::preOutputCommit()
- 'ChronologyPositionTime' => $wgRequest->getFloat(
- 'cpPosTime',
- $wgRequest->getCookie( 'cpPosTime', '' )
- )
+ 'ChronologyPositionTime' => $cpPosTime
] );
+// Make sure that caching does not compromise the consistency improvements
+if ( $cpPosTime ) {
+ MediaWikiServices::getInstance()->getMainWANObjectCache()->useInterimHoldOffCaching( false );
+}
+unset( $cpPosTime );
// Useful debug output
if ( $wgCommandLineMode ) {
$lang = $this->languageFromParam( $lang );
$text = $this->getWikiText( $shortContext, $longContext, $lang );
$out = MessageCache::singleton()->parse( $text, null, true, true, $lang );
- return $out instanceof ParserOutput ? $out->getText() : $out;
+ return $out instanceof ParserOutput
+ ? $out->getText( [ 'enableSectionEditLinks' => false ] )
+ : $out;
}
/**
public function getFragmentForURL() {
if ( !$this->hasFragment() ) {
return '';
- } elseif ( $this->isExternal() && !$this->getTransWikiID() ) {
+ } elseif ( $this->isExternal()
+ && !self::getInterwikiLookup()->fetch( $this->mInterwiki )->isLocal()
+ ) {
return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->getFragment() );
}
return '#' . Sanitizer::escapeIdForLink( $this->getFragment() );
$result_array['textsuppressed'] = true;
}
- if ( $params['disabletoc'] ) {
- $p_result->setTOCEnabled( false );
- }
-
if ( isset( $params['useskin'] ) ) {
$factory = MediaWikiServices::getInstance()->getSkinFactory();
$skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
}
if ( isset( $prop['text'] ) ) {
- $result_array['text'] = $p_result->getText();
+ $result_array['text'] = $p_result->getText( [
+ 'allowTOC' => !$params['disabletoc'],
+ 'enableSectionEditLinks' => !$params['disableeditsection'],
+ ] );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
<?php
/**
- * API for MediaWiki 1.14+
- *
- * Created on Sep 2, 2008
- *
- * Copyright © 2008 Chad Horohoe
- *
* 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
if ( !is_null( $this->allText ) ) {
return;
}
- $this->parserOutput->setEditSectionTokens( false );
- $this->parserOutput->setTOCEnabled( false );
- $text = $this->parserOutput->getText();
+ $text = $this->parserOutput->getText( [
+ 'enableSectionEditTokens' => false,
+ 'allowTOC' => false,
+ ] );
if ( strlen( $text ) == 0 ) {
$this->allText = "";
// empty text - nothing to seek here
/**
* Helper class for making a copy of the database, mostly for unit testing.
*
- * Copyright © 2010 Chad Horohoe <chad@anyonecanedit.org>
- * https://www.mediawiki.org/
- *
* 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
if ( Hooks::run( 'DifferenceEngineRenderRevisionAddParserOutput',
[ $this, $out, $parserOutput, $wikiPage ] )
) {
- $out->addParserOutput( $parserOutput );
+ $out->addParserOutput( $parserOutput, [
+ 'enableSectionEditLinks' => $this->mNewRev->isCurrent()
+ && $this->mNewRev->getTitle()->quickUserCan( 'edit', $this->getUser() ),
+ ] );
}
}
}
try {
$out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
- $html = $out->getText();
+ $html = $out->getText( [
+ 'enableSectionEditLinks' => false,
+ ] );
} catch ( MediaWiki\Services\ServiceDisabledException $e ) {
$html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
protected $logger;
/** @var StatsdDataFactoryInterface */
protected $stats;
+ /** @var bool Whether to use "interim" caching while keys are tombstoned */
+ protected $useInterimHoldOffCaching = true;
/** @var int ERR_* constant for the "last error" registry */
protected $lastRelayError = self::ERR_NONE;
* @return mixed
*/
protected function getInterimValue( $key, $versioned, $minTime, &$asOf ) {
+ if ( !$this->useInterimHoldOffCaching ) {
+ return false; // disabled
+ }
+
$wrapped = $this->cache->get( self::INTERIM_KEY_PREFIX . $key );
list( $value ) = $this->unwrap( $wrapped, $this->getCurrentTime() );
if ( $value !== false && $this->isValid( $value, $versioned, $asOf, $minTime ) ) {
$this->processCaches = [];
}
+ /**
+ * Disable the use of brief caching for tombstoned keys
+ *
+ * When a key is purged via delete(), there normally is a period where caching
+ * is hold-off limited to an extremely short time. This method will disable that
+ * caching, forcing the callback to run for any of:
+ * - WANObjectCache::getWithSetCallback()
+ * - WANObjectCache::getMultiWithSetCallback()
+ * - WANObjectCache::getMultiWithUnionSetCallback()
+ *
+ * This is useful when both:
+ * - a) the database used by the callback is known to be up-to-date enough
+ * for some particular purpose (e.g. replica DB has applied transaction X)
+ * - b) the caller needs to exploit that fact, and therefore needs to avoid the
+ * use of inherently volatile and possibly stale interim keys
+ *
+ * @see WANObjectCache::delete()
+ * @param bool $enabled Whether to enable interim caching
+ * @since 1.31
+ */
+ public function useInterimHoldOffCaching( $enabled ) {
+ $this->useInterimHoldOffCaching = $enabled;
+ }
+
/**
* @param int $flag ATTR_* class constant
* @return int QOS_* class constant
/** @var ParserOutput */
public $mParserOutput;
+ /**
+ * @var bool Whether render() was called. With the way subclasses work
+ * here, there doesn't seem to be any other way to stop calling
+ * OutputPage::enableSectionEditLinks() and still have it work as it did before.
+ */
+ private $disableSectionEditForRender = false;
+
/**
* Constructor and clear the article
* @param Title $title Reference to a Title object.
$parserCache = MediaWikiServices::getInstance()->getParserCache();
$parserOptions = $this->getParserOptions();
+ $poOptions = [];
# Render printable version, use printable version cache
if ( $outputPage->isPrintable() ) {
$parserOptions->setIsPrintable( true );
$parserOptions->setEditSection( false );
- } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
+ $poOptions['enableSectionEditLinks'] = false;
+ } elseif ( $this->disableSectionEditForRender
+ || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
+ ) {
$parserOptions->setEditSection( false );
+ $poOptions['enableSectionEditLinks'] = false;
}
# Try client and file cache
} else {
wfDebug( __METHOD__ . ": showing parser cache contents\n" );
}
- $outputPage->addParserOutput( $this->mParserOutput );
+ $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
# Ensure that UI elements requiring revision ID have
# the correct version information.
$outputPage->setRevisionId( $this->mPage->getLatest() );
}
$this->mParserOutput = $poolArticleView->getParserOutput();
- $outputPage->addParserOutput( $this->mParserOutput );
+ $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
if ( $content->getRedirectTarget() ) {
$outputPage->addSubtitle( "<span id=\"redirectsub\">" .
$this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
$this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
$this->getContext()->getOutput()->setArticleBodyOnly( true );
$this->getContext()->getOutput()->enableSectionEditLinks( false );
+ $this->disableSectionEditForRender = true;
$this->view();
}
/**
* Create "edit section" links?
+ * @deprecated since 1.31, use ParserOutput::getText() options instead.
* @return bool
*/
public function getEditSection() {
/**
* Create "edit section" links?
+ * @deprecated since 1.31, use ParserOutput::getText() options instead.
* @param bool|null $x New value (null is no change)
* @return bool Old value
*/
* @ingroup Parser
*/
class ParserOutput extends CacheTime {
+ /**
+ * Feature flag to indicate to extensions that MediaWiki core supports and
+ * uses getText() stateless transforms.
+ */
+ const SUPPORTS_STATELESS_TRANSFORMS = 1;
+
/**
* @var string $mText The output text
*/
public $mSections = [];
/**
+ * @deprecated since 1.31 Use getText() options.
* @var bool $mEditSectionTokens prefix/suffix markers if edit sections were output as tokens.
*/
- public $mEditSectionTokens = false;
+ public $mEditSectionTokens = true;
/**
* @var array $mProperties Name/value pairs to be cached in the DB.
public $mTimestamp;
/**
+ * @deprecated since 1.31 Use getText() options.
* @var bool $mTOCEnabled Whether TOC should be shown, can't override __NOTOC__.
*/
public $mTOCEnabled = true;
return $this->mText;
}
- public function getText() {
+ /**
+ * Get the output HTML
+ *
+ * @param array $options (since 1.31) Transformations to apply to the HTML
+ * - allowTOC: (bool) Show the TOC, assuming there were enough headings
+ * to generate one and `__NOTOC__` wasn't used. Default is true,
+ * but might be statefully overridden.
+ * - enableSectionEditLinks: (bool) Include section edit links, assuming
+ * section edit link tokens are present in the HTML. Default is true,
+ * but might be statefully overridden.
+ * @return string HTML
+ */
+ public function getText( $options = [] ) {
+ // @todo Warn if !array_key_exists( 'allowTOC', $options ) && empty( $this->mTOCEnabled )
+
+ // @todo Warn if !array_key_exists( 'enableSectionEditLinks', $options )
+ // && !$this->mEditSectionTokens
+ // Note that while $this->mEditSectionTokens formerly defaulted to false,
+ // ParserOptions->getEditSection() defaults to true and Parser copies
+ // that to us so true makes more sense as the stateless default.
+
+ $options += [
+ // empty() here because old cached versions might lack the field somehow.
+ // In that situation, the historical behavior (possibly buggy) is to remove the TOC.
+ 'allowTOC' => !empty( $this->mTOCEnabled ),
+ 'enableSectionEditLinks' => $this->mEditSectionTokens,
+ ];
$text = $this->mText;
- if ( $this->mEditSectionTokens ) {
+
+ Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
+
+ if ( $options['enableSectionEditLinks'] ) {
$text = preg_replace_callback(
self::EDITSECTION_REGEX,
function ( $m ) {
$text = preg_replace( self::EDITSECTION_REGEX, '', $text );
}
- // If you have an old cached version of this class - sorry, you can't disable the TOC
- if ( isset( $this->mTOCEnabled ) && $this->mTOCEnabled ) {
+ if ( $options['allowTOC'] ) {
$text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
} else {
$text = preg_replace(
$text
);
}
+
return $text;
}
return $this->mSections;
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function getEditSectionTokens() {
return $this->mEditSectionTokens;
}
return $this->mLimitReportJSData;
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function getTOCEnabled() {
return $this->mTOCEnabled;
}
return wfSetVar( $this->mSections, $toc );
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function setEditSectionTokens( $t ) {
return wfSetVar( $this->mEditSectionTokens, $t );
}
return wfSetVar( $this->mTimestamp, $timestamp );
}
+ /**
+ * @deprecated since 1.31 Use getText() options.
+ */
public function setTOCEnabled( $flag ) {
return wfSetVar( $this->mTOCEnabled, $flag );
}
/*interface*/false,
$wgContLang
);
- $content = $parserOutput->getText();
+ $content = $parserOutput->getText( [
+ 'enableSectionEditLinks' => false,
+ ] );
// Add only metadata here (including the language links), text is added below
$this->getOutput()->addParserOutputMetadata( $parserOutput );
$popts->setEditSection( false );
$pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
- $out->addParserOutput( $pout );
+ $out->addParserOutput( $pout, [
+ 'enableSectionEditLinks' => false,
+ ] );
}
if ( $isText ) {
* is the execute() method. See docs/maintenance.txt for more info
* and a quick demo of how to use it.
*
- * @author Chad Horohoe <chad@anyonecanedit.org>
* @since 1.16
* @ingroup Maintenance
*/
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
- * @author Chad Horohoe <chad@anyonecanedit.org>
* @file
* @ingroup Maintenance
*/
$out = $parser->getPreloadText( $test['input'], $title, $options );
} else {
$output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
+ $out = $output->getText( [
+ 'allowTOC' => !isset( $opts['notoc'] )
+ ] );
if ( isset( $opts['tidy'] ) ) {
$out = preg_replace( '/\s+$/', '', $out );
}
public function testGetPrefixedDBKey( Title $title, $expected ) {
$this->assertEquals( $expected, $title->getPrefixedDBkey() );
}
+
+ /**
+ * @dataProvider provideGetFragmentForURL
+ *
+ * @param string $titleStr
+ * @param string $expected
+ */
+ public function testGetFragmentForURL( $titleStr, $expected ) {
+ $this->setMwGlobals( [
+ 'wgFragmentMode' => [ 'html5' ],
+ 'wgExternalInterwikiFragmentMode' => 'legacy',
+ ] );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->insert( 'interwiki',
+ [
+ [
+ 'iw_prefix' => 'de',
+ 'iw_url' => 'http://de.wikipedia.org/wiki/',
+ 'iw_api' => 'http://de.wikipedia.org/w/api.php',
+ 'iw_wikiid' => 'dewiki',
+ 'iw_local' => 1,
+ 'iw_trans' => 0,
+ ],
+ [
+ 'iw_prefix' => 'zz',
+ 'iw_url' => 'http://zzwiki.org/wiki/',
+ 'iw_api' => 'http://zzwiki.org/w/api.php',
+ 'iw_wikiid' => 'zzwiki',
+ 'iw_local' => 0,
+ 'iw_trans' => 0,
+ ],
+ ],
+ __METHOD__,
+ [ 'IGNORE' ]
+ );
+
+ $title = Title::newFromText( $titleStr );
+ self::assertEquals( $expected, $title->getFragmentForURL() );
+
+ $dbw->delete( 'interwiki', '*', __METHOD__ );
+ }
+
+ public function provideGetFragmentForURL() {
+ return [
+ [ 'Foo', '' ],
+ [ 'Foo#ümlåût', '#ümlåût' ],
+ [ 'de:Foo#Bå®', '#Bå®' ],
+ [ 'zz:Foo#тест', '#.D1.82.D0.B5.D1.81.D1.82' ],
+ ];
+ }
}
$calls = 0;
$func = function () use ( &$calls, $value, $cache, $key ) {
++$calls;
- // Immediately kill any mutex rather than waiting a second
- $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
return $value;
};
$this->assertEquals( $value, $ret );
$this->assertEquals( 1, $calls, 'Value was populated' );
- // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
+ // Acquire the mutex to verify that getWithSetCallback uses lockTSE properly
$this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
$checkKeys = [ wfRandomString() ]; // new check keys => force misses
$ret = $cache->getWithSetCallback( $key, 30, $func,
[ 'lockTSE' => 5, 'checkKeys' => $checkKeys ] );
- $this->assertEquals( $value, $ret, 'Callback was not used; used interim' );
- $this->assertEquals( 2, $calls, 'Callback was not used; used interim' );
+ $this->assertEquals( $value, $ret, 'Callback was not used; used interim (mutex failed)' );
+ $this->assertEquals( 2, $calls, 'Callback was not used; used interim (mutex failed)' );
}
/**
];
}
+ /**
+ * @covers WANObjectCache::useInterimHoldOffCaching
+ * @covers WANObjectCache::getInterimValue
+ */
+ public function testInterimHoldOffCaching() {
+ $cache = $this->cache;
+
+ $value = 'CRL-40-940';
+ $wasCalled = 0;
+ $func = function () use ( &$wasCalled, $value ) {
+ $wasCalled++;
+
+ return $value;
+ };
+
+ $cache->useInterimHoldOffCaching( true );
+
+ $key = wfRandomString( 32 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 1, $wasCalled, 'Value cached' );
+ $cache->delete( $key );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value regenerated (got mutex)' ); // sets interim
+ // Lock up the mutex so interim cache is used
+ $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value interim cached (failed mutex)' );
+ $this->internalCache->delete( $cache::MUTEX_KEY_PREFIX . $key );
+
+ $cache->useInterimHoldOffCaching( false );
+
+ $wasCalled = 0;
+ $key = wfRandomString( 32 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 1, $wasCalled, 'Value cached' );
+ $cache->delete( $key );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 2, $wasCalled, 'Value regenerated (got mutex)' );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 3, $wasCalled, 'Value still regenerated (got mutex)' );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 4, $wasCalled, 'Value still regenerated (got mutex)' );
+ // Lock up the mutex so interim cache is used
+ $this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
+ $v = $cache->getWithSetCallback( $key, 60, $func );
+ $this->assertEquals( 5, $wasCalled, 'Value still regenerated (failed mutex)' );
+ }
+
/**
* @covers WANObjectCache::touchCheckKey
* @covers WANObjectCache::resetCheckKey
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @group Database
* ^--- trigger DB shadowing because we are using Title magic
$this->assertArrayNotHasKey( 'foo', $properties );
}
+ /**
+ * @covers ParserOutput::getText
+ * @dataProvider provideGetText
+ * @param array $options Options to getText()
+ * @param array $poState ParserOptions state fields to set
+ * @param string $text Parser text
+ * @param string $expect Expected output
+ */
+ public function testGetText( $options, $poState, $text, $expect ) {
+ $this->setMwGlobals( [
+ 'wgArticlePath' => '/wiki/$1',
+ 'wgScriptPath' => '/w',
+ 'wgScript' => '/w/index.php',
+ ] );
+
+ $po = new ParserOutput( $text );
+
+ // Emulate Parser
+ $po->setEditSectionTokens( true );
+
+ if ( $poState ) {
+ $wrap = TestingAccessWrapper::newFromObject( $po );
+ foreach ( $poState as $key => $value ) {
+ $wrap->$key = $value;
+ }
+ }
+
+ $actual = $po->getText( $options );
+ $this->assertSame( $expect, $actual );
+ }
+
+ public static function provideGetText() {
+ // @codingStandardsIgnoreStart Generic.Files.LineLength
+ $text = <<<EOF
+<p>Test document.
+</p>
+<mw:toc><div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+</mw:toc>
+<h2><span class="mw-headline" id="Section_1">Section 1</span><mw:editsection page="Test Page" section="1">Section 1</mw:editsection></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><mw:editsection page="Test Page" section="2">Section 2</mw:editsection></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><mw:editsection page="Test Page" section="3">Section 2.1</mw:editsection></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><mw:editsection page="Test Page" section="4">Section 3</mw:editsection></h2>
+<p>Three
+</p>
+EOF;
+
+ return [
+ 'No stateless options, default state' => [
+ [], [], $text, <<<EOF
+<p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'No stateless options, TOC statefully disabled' => [
+ [], [ 'mTOCEnabled' => false ], $text, <<<EOF
+<p>Test document.
+</p>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'No stateless options, section edits statefully disabled' => [
+ [], [ 'mEditSectionTokens' => false ], $text, <<<EOF
+<p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'Stateless options override stateful settings' => [
+ [ 'allowTOC' => true, 'enableSectionEditLinks' => true ],
+ [ 'mTOCEnabled' => false, 'mEditSectionTokens' => false ],
+ $text, <<<EOF
+<p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'Statelessly disable section edit links' => [
+ [ 'enableSectionEditLinks' => false ], [], $text, <<<EOF
+<p>Test document.
+</p>
+<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
+<ul>
+<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
+<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
+<ul>
+<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
+</ul>
+</div>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ 'Statelessly disable TOC' => [
+ [ 'allowTOC' => false ], [], $text, <<<EOF
+<p>Test document.
+</p>
+
+<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>One
+</p>
+<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Two
+</p>
+<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
+<p>Two point one
+</p>
+<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+ ],
+ ];
+ // @codingStandardsIgnoreEnd
+ }
+
}