Merge "objectcache: Add @ingroup to RedisBagOStuff"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 1 Dec 2017 06:02:13 +0000 (06:02 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 1 Dec 2017 06:02:13 +0000 (06:02 +0000)
27 files changed:
RELEASE-NOTES-1.31
docs/hooks.txt
includes/DefaultSettings.php
includes/EditPage.php
includes/Message.php
includes/OutputPage.php
includes/Setup.php
includes/Status.php
includes/Title.php
includes/api/ApiParse.php
includes/api/ApiPurge.php
includes/content/WikiTextStructure.php
includes/db/CloneDatabase.php
includes/diff/DifferenceEngine.php
includes/installer/Installer.php
includes/libs/objectcache/WANObjectCache.php
includes/page/Article.php
includes/parser/ParserOptions.php
includes/parser/ParserOutput.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialUndelete.php
maintenance/Maintenance.php
maintenance/doMaintenance.php
tests/parser/ParserTestRunner.php
tests/phpunit/includes/TitleTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/parser/ParserOutputTest.php

index 4b55f4b..4a2876d 100644 (file)
@@ -33,6 +33,8 @@ production.
   [[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 ===
 
@@ -121,6 +123,18 @@ changes to languages because of Phabricator reports.
 * 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
index 685a182..29883b2 100644 (file)
@@ -2594,6 +2594,12 @@ RejectParserCacheValue hook) because MediaWiki won't do it for you.
   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
index 21efb28..c17bf7e 100644 (file)
  * @file
  */
 
-/**
- * @defgroup Globalsettings Global settings
- */
-
 /**
  * @cond file_level_code
  * This is not a valid entry point, perform no further processing unless
index ff224c5..bcaab3a 100644 (file)
@@ -4012,7 +4012,10 @@ class EditPage {
                $parserOutput->setEditSectionTokens( false ); // no section edit links
                return [
                        'parserOutput' => $parserOutput,
-                       'html' => $parserOutput->getText() ];
+                       'html' => $parserOutput->getText( [
+                               'enableSectionEditLinks' => false
+                       ] )
+               ];
        }
 
        /**
index 3b2f3cc..16ae839 100644 (file)
@@ -1244,7 +1244,9 @@ class Message implements MessageSpecifier, Serializable {
                        $this->getLanguage()
                );
 
-               return $out instanceof ParserOutput ? $out->getText() : $out;
+               return $out instanceof ParserOutput
+                       ? $out->getText( [ 'enableSectionEditLinks' => false ] )
+                       : $out;
        }
 
        /**
index a5f9c18..92963fd 100644 (file)
@@ -1783,7 +1783,9 @@ class OutputPage extends ContextSource {
 
                $popts->setTidy( $oldTidy );
 
-               $this->addParserOutput( $parserOutput );
+               $this->addParserOutput( $parserOutput, [
+                       'enableSectionEditLinks' => false,
+               ] );
        }
 
        /**
@@ -1868,9 +1870,10 @@ class OutputPage extends ContextSource {
         *
         * @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() );
@@ -1884,9 +1887,10 @@ class OutputPage extends ContextSource {
         *
         * @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 ] );
@@ -1897,16 +1901,22 @@ class OutputPage extends ContextSource {
         * 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 );
        }
 
        /**
@@ -1957,7 +1967,9 @@ class OutputPage extends ContextSource {
                        $popts->setTargetLanguage( $oldLang );
                }
 
-               return $parserOutput->getText();
+               return $parserOutput->getText( [
+                       'enableSectionEditLinks' => false,
+               ] );
        }
 
        /**
@@ -3957,6 +3969,7 @@ class OutputPage extends ContextSource {
         * 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;
@@ -3965,6 +3978,7 @@ class OutputPage extends ContextSource {
        /**
         * @return bool
         * @since 1.23
+        * @deprecated since 1.31, use $poOptions to addParserOutput() instead.
         */
        public function sectionEditLinksEnabled() {
                return $this->mEnableSectionEditLinks;
index 081ea68..d6f4b2f 100644 (file)
@@ -736,17 +736,20 @@ if ( !$wgDBerrorLogTZ ) {
 
 // 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 ) {
index a35af6e..f17f173 100644 (file)
@@ -316,7 +316,9 @@ class Status extends StatusValue {
                $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;
        }
 
        /**
index 829be44..d0d77e3 100644 (file)
@@ -1464,7 +1464,9 @@ class Title implements LinkTarget {
        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() );
index 15b94fb..ec015da 100644 (file)
@@ -288,10 +288,6 @@ class ApiParse extends ApiBase {
                        $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'] ) );
@@ -347,7 +343,10 @@ class ApiParse extends ApiBase {
                }
 
                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';
                }
 
index 4b8ce7f..b7cfc2c 100644 (file)
@@ -1,12 +1,6 @@
 <?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
index aeb96b6..0eadc3c 100644 (file)
@@ -146,9 +146,10 @@ class WikiTextStructure {
                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
index 3d22c03..16d10d1 100644 (file)
@@ -2,9 +2,6 @@
 /**
  * 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
index 51b9f15..7e05be6 100644 (file)
@@ -634,7 +634,10 @@ class DifferenceEngine extends ContextSource {
                                        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() ),
+                                               ] );
                                        }
                                }
                        }
index 2906a83..46978e1 100644 (file)
@@ -688,7 +688,9 @@ abstract class Installer {
 
                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 );
 
index db27e42..74ec7b9 100644 (file)
@@ -91,6 +91,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
        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;
@@ -1104,6 +1106,10 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         * @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 ) ) {
@@ -1495,6 +1501,30 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                $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
index c9dc273..dadf311 100644 (file)
@@ -75,6 +75,13 @@ class Article implements Page {
        /** @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.
@@ -469,12 +476,17 @@ class Article implements Page {
                $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
@@ -533,7 +545,7 @@ class Article implements Page {
                                                        } 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() );
@@ -597,7 +609,7 @@ class Article implements Page {
                                        }
 
                                        $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>" );
@@ -1515,6 +1527,7 @@ class Article implements Page {
                $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
                $this->getContext()->getOutput()->setArticleBodyOnly( true );
                $this->getContext()->getOutput()->enableSectionEditLinks( false );
+               $this->disableSectionEditForRender = true;
                $this->view();
        }
 
index 5e2845f..f99089b 100644 (file)
@@ -869,6 +869,7 @@ class ParserOptions {
 
        /**
         * Create "edit section" links?
+        * @deprecated since 1.31, use ParserOutput::getText() options instead.
         * @return bool
         */
        public function getEditSection() {
@@ -877,6 +878,7 @@ class ParserOptions {
 
        /**
         * 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
         */
index 3480a51..ff9c28d 100644 (file)
  * @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
         */
@@ -144,9 +150,10 @@ class ParserOutput extends CacheTime {
        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.
@@ -164,6 +171,7 @@ class ParserOutput extends CacheTime {
        public $mTimestamp;
 
        /**
+        * @deprecated since 1.31 Use getText() options.
         * @var bool $mTOCEnabled Whether TOC should be shown, can't override __NOTOC__.
         */
        public $mTOCEnabled = true;
@@ -250,9 +258,38 @@ class ParserOutput extends CacheTime {
                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 ) {
@@ -278,8 +315,7 @@ class ParserOutput extends CacheTime {
                        $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(
@@ -288,6 +324,7 @@ class ParserOutput extends CacheTime {
                                $text
                        );
                }
+
                return $text;
        }
 
@@ -339,6 +376,9 @@ class ParserOutput extends CacheTime {
                return $this->mSections;
        }
 
+       /**
+        * @deprecated since 1.31 Use getText() options.
+        */
        public function getEditSectionTokens() {
                return $this->mEditSectionTokens;
        }
@@ -426,6 +466,9 @@ class ParserOutput extends CacheTime {
                return $this->mLimitReportJSData;
        }
 
+       /**
+        * @deprecated since 1.31 Use getText() options.
+        */
        public function getTOCEnabled() {
                return $this->mTOCEnabled;
        }
@@ -454,6 +497,9 @@ class ParserOutput extends CacheTime {
                return wfSetVar( $this->mSections, $toc );
        }
 
+       /**
+        * @deprecated since 1.31 Use getText() options.
+        */
        public function setEditSectionTokens( $t ) {
                return wfSetVar( $this->mEditSectionTokens, $t );
        }
@@ -470,6 +516,9 @@ class ParserOutput extends CacheTime {
                return wfSetVar( $this->mTimestamp, $timestamp );
        }
 
+       /**
+        * @deprecated since 1.31 Use getText() options.
+        */
        public function setTOCEnabled( $flag ) {
                return wfSetVar( $this->mTOCEnabled, $flag );
        }
index dfa13b6..cfc7a85 100644 (file)
@@ -608,7 +608,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                                /*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 );
 
index 71dee3d..0c038c1 100644 (file)
@@ -455,7 +455,9 @@ class SpecialUndelete extends SpecialPage {
                        $popts->setEditSection( false );
 
                        $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
-                       $out->addParserOutput( $pout );
+                       $out->addParserOutput( $pout, [
+                               'enableSectionEditLinks' => false,
+                       ] );
                }
 
                if ( $isText ) {
index 10082e9..255892b 100644 (file)
@@ -50,7 +50,6 @@ use Wikimedia\Rdbms\IMaintainableDatabase;
  * 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
  */
index b5beef6..2ac756c 100644 (file)
@@ -21,7 +21,6 @@
  * 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
  */
index 149ba80..44a00a8 100644 (file)
@@ -853,8 +853,9 @@ class ParserTestRunner {
                        $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 );
                        }
index 5a92b99..75e0c3e 100644 (file)
@@ -911,4 +911,54 @@ class TitleTest extends MediaWikiTestCase {
        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' ],
+               ];
+       }
 }
index df8228d..c2be911 100644 (file)
@@ -769,8 +769,6 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
                $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;
                };
 
@@ -778,7 +776,7 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
                $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
@@ -795,8 +793,8 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
 
                $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)' );
        }
 
        /**
@@ -1187,6 +1185,58 @@ class WANObjectCacheTest extends PHPUnit_Framework_TestCase {
                ];
        }
 
+       /**
+        * @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
index ec8f0d0..441d60d 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Wikimedia\TestingAccessWrapper;
+
 /**
  * @group Database
  *        ^--- trigger DB shadowing because we are using Title magic
@@ -89,4 +91,231 @@ class ParserOutputTest extends MediaWikiTestCase {
                $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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;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&amp;action=edit&amp;section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
+<p>Three
+</p>
+EOF
+                       ],
+               ];
+               // @codingStandardsIgnoreEnd
+       }
+
 }