Merge "Add ParserFetchTemplate hook"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 15 Nov 2016 23:36:18 +0000 (23:36 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 15 Nov 2016 23:36:18 +0000 (23:36 +0000)
1  2 
docs/hooks.txt
includes/parser/Parser.php

diff --combined docs/hooks.txt
@@@ -464,41 -464,6 +464,41 @@@ $moduleManager: ApiModuleManager Modul
  action=query submodule. Use this to extend core API modules.
  &$module: Module object
  
 +'ApiQueryBaseAfterQuery': Called for (some) API query modules after the
 +database query has returned. An API query module wanting to use this hook
 +should see the ApiQueryBase::select() and ApiQueryBase::processRow()
 +documentation.
 +$module: ApiQueryBase module in question
 +$result: ResultWrapper|bool returned from the IDatabase::select()
 +&$hookData: array that was passed to the 'ApiQueryBaseBeforeQuery' hook and
 + will be passed to the 'ApiQueryBaseProcessRow' hook, intended for inter-hook
 + communication.
 +
 +'ApiQueryBaseBeforeQuery': Called for (some) API query modules before a
 +database query is made. WARNING: It would be very easy to misuse this hook and
 +break the module! Any joins added *must* join on a unique key of the target
 +table unless you really know what you're doing. An API query module wanting to
 +use this hook should see the ApiQueryBase::select() and
 +ApiQueryBase::processRow() documentation.
 +$module: ApiQueryBase module in question
 +&$tables: array of tables to be queried
 +&$fields: array of columns to select
 +&$conds: array of WHERE conditionals for query
 +&$query_options: array of options for the database request
 +&$join_conds: join conditions for the tables
 +&$hookData: array that will be passed to the 'ApiQueryBaseAfterQuery' and
 + 'ApiQueryBaseProcessRow' hooks, intended for inter-hook communication.
 +
 +'ApiQueryBaseProcessRow': Called for (some) API query modules as each row of
 +the database result is processed. Return false to stop processing the result
 +set. An API query module wanting to use this hook should see the
 +ApiQueryBase::select() and ApiQueryBase::processRow() documentation.
 +$module: ApiQueryBase module in question
 +$row: stdClass Database result row
 +&$data: array to be included in the ApiResult.
 +&$hookData: array that was be passed to the 'ApiQueryBaseBeforeQuery' and
 + 'ApiQueryBaseAfterQuery' hooks, intended for inter-hook communication.
 +
  'APIQueryGeneratorAfterExecute': After calling the executeGenerator() method of
  an action=query submodule. Use this to extend core API modules.
  &$module: Module object
@@@ -565,18 -530,6 +565,18 @@@ your callback to the $tokenFunctions ar
  makes no sense).
  &$tokenFunctions: array(action => callback)
  
 +'ApiQueryWatchlistExtractOutputData': Extract row data for ApiQueryWatchlist.
 +$module: ApiQueryWatchlist instance
 +$watchedItem: WatchedItem instance
 +$recentChangeInfo: Array of recent change info data
 +&$vals: Associative array of data to be output for the row
 +
 +'ApiQueryWatchlistPrepareWatchedItemQueryServiceOptions': Populate the options
 +to be passed from ApiQueryWatchlist to WatchedItemQueryService.
 +$module: ApiQueryWatchlist instance
 +$params: Array of parameters, as would be returned by $module->extractRequestParams()
 +&$options: Array of options for WatchedItemQueryService::getWatchedItemsWithRecentChangeInfo()
 +
  'ApiRsdServiceApis': Add or remove APIs from the RSD services list. Each service
  should have its own entry in the $apis array and have a unique name, passed as
  key for the array that represents the service data. In this data array, the
@@@ -643,7 -596,7 +643,7 @@@ $outputPage: OutputPage that can be use
  &$user: the user that deleted the article
  $reason: the reason the article was deleted
  $id: id of the article that was deleted
 -$content: the Content of the deleted page
 +$content: the Content of the deleted page (or null, when deleting a broken page)
  $logEntry: the ManualLogEntry used to record the deletion
  $archivedRevisionCount: the number of revisions archived during the deletion
  
@@@ -1066,18 -1019,6 +1066,18 @@@ $user: user initiating the actio
  uses are in active use.
  &$tags: list of all active tags. Append to this array.
  
 +'ChangeTagsAfterUpdateTags': Called after tags have been updated with the
 +ChangeTags::updateTags function. Params:
 +$addedTags: tags effectively added in the update
 +$removedTags: tags effectively removed in the update
 +$prevTags: tags that were present prior to the update
 +$rc_id: recentchanges table id
 +$rev_id: revision table id
 +$log_id: logging table id
 +$params: tag params
 +$rc: RecentChange being tagged when the tagging accompanies the action or null
 +$user: User who performed the tagging when the tagging is subsequent to the action or null
 +
  'Collation::factory': Called if $wgCategoryCollation is an unknown collation.
  $collationName: Name of the collation in question
  &$collationObject: Null. Replace with a subclass of the Collation class that
@@@ -1200,6 -1141,7 +1200,6 @@@ wrapped in a span element which has cla
  $differenceEngine: DifferenceEngine object
  &$markAsPatrolledLink: The "mark as patrolled" link HTML (string)
  $rcid: Recent change ID (rc_id) for this change (int)
 -$token: Patrol token; $rcid is used in generating this variable
  
  'DifferenceEngineMarkPatrolledRCID': Allows extensions to possibly change the rcid parameter.
  For example the rcid might be set to zero due to the user being the same as the
@@@ -1254,7 -1196,7 +1254,7 @@@ $out: OutputPage objec
  $parserOutput: ParserOutput object
  $wikiPage: WikiPage object
  
 -DifferenceEngineRenderRevisionShowFinalPatrolLink': An extension can hook into this hook
 +'DifferenceEngineRenderRevisionShowFinalPatrolLink': An extension can hook into this hook
  point and return false to not show the final "mark as patrolled" link on the bottom
  of a page.
  This hook has no arguments.
@@@ -1925,8 -1867,8 +1925,8 @@@ $code: language of the preferred transl
  in various places to allow extensions to define the effective language
  links for a page.
  $title: The page's Title.
 -&$links: Associative array mapping language codes to prefixed links of the
 -  form "language:title".
 +&$links: Array with elements of the form "language:title" in the order
 +  that they will be output.
  &$linkFlags: Associative array mapping prefixed links to arrays of flags.
    Currently unused, but planned to provide support for marking individual
    language links in the UI, e.g. for featured articles.
@@@ -1995,8 -1937,8 +1995,8 @@@ LinkRenderer, before processing starts
  processing and return $ret.
  $linkRenderer: the LinkRenderer object
  $target: the LinkTarget that the link is pointing to
 -&$html: the contents that the <a> tag should have (raw HTML); null means
 -  "default".
 +&$text: the contents that the <a> tag should have; either a plain, unescaped
 +  string or a HtmlArmor object; null means "default".
  &$customAttribs: the HTML attributes that the <a> tag should have, in
    associative array form, with keys and values unescaped.  Should be merged
    with default values, with a value of false meaning to suppress the
@@@ -2013,8 -1955,7 +2013,8 @@@ return false, $ret will be returned
  $linkRenderer: the LinkRenderer object
  $target: the LinkTarget object that the link is pointing to
  $isKnown: boolean indicating whether the page is known or not
 -&$html: the final (raw HTML) contents of the <a> tag, after processing.
 +&$text: the contents that the <a> tag should have; either a plain, unescaped
 +  string or a HtmlArmor object.
  &$attribs: the final HTML attributes of the <a> tag, after processing, in
    associative array form.
  &$ret: the value to return if your hook returns false.
@@@ -2032,7 -1973,6 +2032,7 @@@ $insertions: an array of links to inser
  'LinksUpdateComplete': At the end of LinksUpdate::doUpdate() when updating,
  including delete and insert, has completed for all link tables
  &$linksUpdate: the LinksUpdate object
 +$ticket: prior result of LBFactory::getEmptyTransactionTicket()
  
  'LinksUpdateConstructed': At the end of LinksUpdate() is construction.
  &$linksUpdate: the LinksUpdate object
@@@ -2464,6 -2404,13 +2464,13 @@@ $revId: ID of the revision that was par
  'ParserCloned': Called when the parser is cloned.
  $parser: Newly-cloned Parser object
  
+ 'ParserFetchTemplate': Called when the parser fetches a template
+ $parser: Parser Parser object or false
+ $title: Title object of the template to be fetched
+ $rev: Revision object of the template
+ &$text: Transclusion text of the template or false or null
+ &$deps: Array of template dependencies with 'title', 'page_id', 'rev_id' keys
  'ParserFirstCallInit': Called when the parser initialises for the first time.
  &$parser: Parser object being cleared
  
@@@ -2486,24 -2433,12 +2493,24 @@@ cache or return false to not use it
  &$parser: Parser object
  &$varCache: variable cache (array)
  
 -'ParserLimitReport': DEPRECATED! Use ParserLimitReportPrepare instead.
 +'ParserLimitReport': DEPRECATED! Use ParserLimitReportPrepare and
 +ParserLimitReportFormat instead.
  Called at the end of Parser:parse() when the parser will
  include comments about size of the text parsed.
  $parser: Parser object
  &$limitReport: text that will be included (without comment tags)
  
 +'ParserLimitReportFormat': Called for each row in the parser limit report that
 +needs formatting. If nothing handles this hook, the default is to use "$key" to
 +get the label, and "$key-value" or "$key-value-text"/"$key-value-html" to
 +format the value.
 +$key: Key for the limit report item (string)
 +&$value: Value of the limit report item
 +&$report: String onto which to append the data
 +$isHTML: If true, $report is an HTML table with two columns; if false, it's
 +  text intended for display in a monospaced font.
 +$localize: If false, $report should be output in English.
 +
  'ParserLimitReportPrepare': Called at the end of Parser:parse() when the parser
  will include comments about size of the text parsed. Hooks should use
  $output->setLimitReportData() to populate data. Functions for this hook should
@@@ -2532,6 -2467,10 +2539,6 @@@ $showEditLinks: boolean describing whet
  &$globals: Array with all the globals which should be set for parser tests.
    The arrays keys serve as the globals names, its values are the globals values.
  
 -'ParserTestParser': Called when creating a new instance of Parser in
 -tests/parser/parserTest.inc.
 -&$parser: Parser object created
 -
  'ParserTestTables': Alter the list of tables to duplicate when parser tests are
  run. Use when page save hooks require the presence of custom tables to ensure
  that tests continue to run properly.
@@@ -2766,13 -2705,6 +2773,13 @@@ $page: WikiPage that is being indexe
  $output: ParserOutput that is produced from the page
  $engine: SearchEngine for which the indexing is intended
  
 +'SearchResultsAugment': Allows extension to add its code to the list of search
 +result augmentors.
 +&$setAugmentors: List of whole-set augmentor objects, must implement ResultSetAugmentor
 +&$rowAugmentors: List of per-row augmentor objects, must implement ResultAugmentor.
 +Note that lists should be in the format name => object and the names in both lists should
 +be distinct.
 +
  'SecondaryDataUpdates': Allows modification of the list of DataUpdates to
  perform when page content is modified. Currently called by
  AbstractContent::getSecondaryDataUpdates.
@@@ -3368,7 -3300,7 +3375,7 @@@ PageArchive object has been created bu
  &$archive: PageArchive object
  $title: Title object of the page that we're viewing
  
 -'UndeleteForm::undelete': Called un UndeleteForm::undelete, after checking that
 +'UndeleteForm::undelete': Called in UndeleteForm::undelete, after checking that
  the site is not in read-only mode, that the Title object is not null and after
  a PageArchive object has been constructed but before performing any further
  processing.
@@@ -3724,16 -3656,6 +3731,16 @@@ $userId: User id of the current use
  $userText: User name of the current user
  &$items: Array of user tool links as HTML fragments
  
 +'UsersPagerDoBatchLookups': Called in UsersPager::doBatchLookups() to give
 +extensions providing user group data from an alternate source a chance to add
 +their data into the cache array so that things like global user groups are
 +displayed correctly in Special:ListUsers.
 +$dbr: Read-only database handle
 +$userIds: Array of user IDs whose groups we should look up
 +&$cache: Array of user ID -> internal user group name (e.g. 'sysop') mappings
 +&$groups: Array of group name -> bool true mappings for members of a given user
 +group
 +
  'ValidateExtendedMetadataCache': Called to validate the cached metadata in
  FormatMetadata::getExtendedMeta (return false means cache will be
  invalidated and GetExtendedMetadata hook called again).
@@@ -3754,10 -3676,6 +3761,10 @@@ used to alter the SQL query which gets 
  &$user: user that watched
  &$page: WikiPage object watched
  
 +'WatchedItemQueryServiceExtensions': Create a WatchedItemQueryServiceExtension.
 +&$extensions: Add WatchedItemQueryServiceExtension objects to this array
 +$watchedItemQueryService: Service object
 +
  'WatchlistEditorBeforeFormRender': Before building the Special:EditWatchlist
  form, used to manipulate the list of pages or preload data based on that list.
  &$watchlistInfo: array of watchlisted pages in
@@@ -3810,14 -3728,9 +3817,14 @@@ a page is deleted. Called in WikiPage::
  specific to a content model should be provided by the respective Content's
  getDeletionUpdates() method.
  $page: the WikiPage
 -$content: the Content to generate updates for
 +$content: the Content to generate updates for (or null, if the Content could not be loaded
 +due to an error)
  &$updates: the array of DataUpdate objects. Hook function may want to add to it.
  
 +'WikiPageFactory': Override WikiPage class used for a title
 +$title: Title of the page
 +&$page: Variable to set the created WikiPage to.
 +
  'XmlDumpWriterOpenPage': Called at the end of XmlDumpWriter::openPage, to allow
  extra metadata to be added.
  $obj: The XmlDumpWriter object.
@@@ -22,7 -22,6 +22,7 @@@
   */
  use MediaWiki\Linker\LinkRenderer;
  use MediaWiki\MediaWikiServices;
 +use Wikimedia\ScopedCallback;
  
  /**
   * @defgroup Parser Parser
@@@ -252,7 -251,7 +252,7 @@@ class Parser 
        protected $mProfiler;
  
        /**
 -       * @var \MediaWiki\Linker\LinkRenderer
 +       * @var LinkRenderer
         */
        protected $mLinkRenderer;
  
         * @param int $revid Number to pass in {{REVISIONID}}
         * @return ParserOutput A ParserOutput
         */
 -      public function parse( $text, Title $title, ParserOptions $options,
 +      public function parse(
 +              $text, Title $title, ParserOptions $options,
                $linestart = true, $clearState = true, $revid = null
        ) {
                /**
                        }
                }
  
 +              # Done parsing! Compute runtime adaptive expiry if set
 +              $this->mOutput->finalizeAdaptiveCacheExpiry();
 +
 +              # Warn if too many heavyweight parser functions were used
                if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
                        $this->limitationWarn( 'expensive-parserfunction',
                                $this->mExpensiveFunctionCount,
                                [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
                        );
                        $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
 -                              [ $this->mExpensiveFunctionCount,
 -                                      $this->mOptions->getExpensiveParserFunctionLimit() ]
 +                              [ $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() ]
                        );
                        Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
  
 -                      $limitReport = '';
 -                      Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
 -                      if ( $limitReport != '' ) {
 -                              // Sanitize for comment. Note '‐' in the replacement is U+2010,
 -                              // which looks much like the problematic '-'.
 -                              $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
 -                              $text .= "\n<!-- \nNewPP limit report\n$limitReport-->\n";
 +                      $limitReport = "NewPP limit report\n";
 +                      if ( $wgShowHostnames ) {
 +                              $limitReport .= 'Parsed by ' . wfHostname() . "\n";
 +                      }
 +                      $limitReport .= 'Cached time: ' . $this->mOutput->getCacheTime() . "\n";
 +                      $limitReport .= 'Cache expiry: ' . $this->mOutput->getCacheExpiry() . "\n";
 +                      $limitReport .= 'Dynamic content: ' .
 +                              ( $this->mOutput->hasDynamicContent() ? 'true' : 'false' ) .
 +                              "\n";
 +
 +                      foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
 +                              if ( Hooks::run( 'ParserLimitReportFormat',
 +                                      [ $key, &$value, &$limitReport, false, false ]
 +                              ) ) {
 +                                      $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
 +                                      $valueMsg = wfMessage( [ "$key-value-text", "$key-value" ] )
 +                                              ->inLanguage( 'en' )->useDatabase( false );
 +                                      if ( !$valueMsg->exists() ) {
 +                                              $valueMsg = new RawMessage( '$1' );
 +                                      }
 +                                      if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
 +                                              $valueMsg->params( $value );
 +                                              $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
 +                                      }
 +                              }
                        }
 +                      // Since we're not really outputting HTML, decode the entities and
 +                      // then re-encode the things that need hiding inside HTML comments.
 +                      $limitReport = htmlspecialchars_decode( $limitReport );
 +                      Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
 +
 +                      // Sanitize for comment. Note '‐' in the replacement is U+2010,
 +                      // which looks much like the problematic '-'.
 +                      $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
 +                      $text .= "\n<!-- \n$limitReport-->\n";
  
 -                      // Add on template profiling data in human/machine readable way
 +                      // Add on template profiling data
                        $dataByFunc = $this->mProfiler->getFunctionStats();
                        uasort( $dataByFunc, function ( $a, $b ) {
                                return $a['real'] < $b['real']; // descending order
                        } );
 -                      $profileReport = [];
 +                      $profileReport = "Transclusion expansion time report (%,ms,calls,template)\n";
                        foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
 -                              $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
 -                                      $item['%real'], $item['real'], $item['calls'], $item['name'] );
 +                              $profileReport .= sprintf( "%6.2f%% %8.3f %6d - %s\n",
 +                                      $item['%real'], $item['real'], $item['calls'],
 +                                      htmlspecialchars( $item['name'] ) );
                        }
 -                      $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
 +                      $text .= "\n<!-- \n$profileReport-->\n";
  
 -                      // Add other cache related metadata
 -                      if ( $wgShowHostnames ) {
 -                              $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
 -                      }
 -                      $this->mOutput->setLimitReportData( 'cachereport-timestamp',
 -                              $this->mOutput->getCacheTime() );
 -                      $this->mOutput->setLimitReportData( 'cachereport-ttl',
 -                              $this->mOutput->getCacheExpiry() );
 -                      $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
 -                              $this->mOutput->hasDynamicContent() );
 -
 -                      if ( $this->mGeneratedPPNodeCount
 -                              > $this->mOptions->getMaxGeneratedPPNodeCount() / 10
 -                      ) {
 +                      if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
                                wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
                                        $this->mTitle->getPrefixedDBkey() );
                        }
        }
  
        /**
 -       * Get a \MediaWiki\Linker\LinkRenderer instance to make links with
 +       * Get a LinkRenderer instance to make links with
         *
         * @since 1.28
 -       * @return \MediaWiki\Linker\LinkRenderer
 +       * @return LinkRenderer
         */
        public function getLinkRenderer() {
                if ( !$this->mLinkRenderer ) {
         * the form:
         *
         * @code
 -       *   'UNIQ-xxxxx' => array(
 +       *   'UNIQ-xxxxx' => [
         *     'element',
         *     'tag content',
 -       *     array( 'param' => 'x' ),
 -       *     '<element param="x">tag content</element>' ) )
 +       *     [ 'param' => 'x' ],
 +       *     '<element param="x">tag content</element>' ]
         * @endcode
         *
         * @param array $elements List of element names. Comments are always extracted.
                } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
                        # RFC or PMID
                        if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
 +                              if ( !$this->mOptions->getMagicRFCLinks() ) {
 +                                      return $m[0];
 +                              }
                                $keyword = 'RFC';
                                $urlmsg = 'rfcurl';
                                $cssClass = 'mw-magiclink-rfc';
 +                              $trackingCat = 'magiclink-tracking-rfc';
                                $id = $m[5];
                        } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
 +                              if ( !$this->mOptions->getMagicPMIDLinks() ) {
 +                                      return $m[0];
 +                              }
                                $keyword = 'PMID';
                                $urlmsg = 'pubmedurl';
                                $cssClass = 'mw-magiclink-pmid';
 +                              $trackingCat = 'magiclink-tracking-pmid';
                                $id = $m[5];
                        } else {
                                throw new MWException( __METHOD__ . ': unrecognised match type "' .
                                        substr( $m[0], 0, 20 ) . '"' );
                        }
                        $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
 +                      $this->addTrackingCategory( $trackingCat );
                        return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
 -              } elseif ( isset( $m[6] ) && $m[6] !== '' ) {
 +              } elseif ( isset( $m[6] ) && $m[6] !== ''
 +                      && $this->mOptions->getMagicISBNLinks()
 +              ) {
                        # ISBN
                        $isbn = $m[6];
                        $space = self::SPACE_NOT_NL; #  non-newline space
                                ' ' => '',
                                'x' => 'X',
                        ] );
 +                      $this->addTrackingCategory( 'magiclink-tracking-isbn' );
                        return $this->getLinkRenderer()->makeKnownLink(
                                SpecialPage::getTitleFor( 'Booksources', $num ),
                                "ISBN $isbn",
         * Replace external links (REL)
         *
         * Note: this is all very hackish and the order of execution matters a lot.
 -       * Make sure to run tests/parserTests.php if you change this code.
 +       * Make sure to run tests/parser/parserTests.php if you change this code.
         *
         * @private
         *
                                                $context->setUser( User::newFromName( '127.0.0.1', false ) );
                                        }
                                        $context->setLanguage( $this->mOptions->getUserLangObj() );
 -                                      $ret = SpecialPageFactory::capturePath( $title, $context, $this->getLinkRenderer() );
 +                                      $ret = SpecialPageFactory::capturePath(
 +                                              $title, $context, $this->getLinkRenderer() );
                                        if ( $ret ) {
                                                $text = $context->getOutput()->getHTML();
                                                $this->mOutput->addOutputPageMetadata( $context->getOutput() );
                                                $found = true;
                                                $isHTML = true;
                                                if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
 -                                                      $this->mOutput->updateCacheExpiry( $specialPage->maxIncludeCacheTime() );
 +                                                      $this->mOutput->updateRuntimeAdaptiveExpiry(
 +                                                              $specialPage->maxIncludeCacheTime()
 +                                                      );
                                                }
                                        }
                                } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
         * @since 1.24
         * @param Title $title
         * @param Parser|bool $parser
 -       * @return Revision
 +       * @return Revision|bool False if missing
         */
 -      public static function statelessFetchRevision( $title, $parser = false ) {
 -              return Revision::newFromTitle( $title );
 +      public static function statelessFetchRevision( Title $title, $parser = false ) {
 +              $pageId = $title->getArticleID();
 +              $revId = $title->getLatestRevID();
 +
 +              $rev = Revision::newKnownCurrent( wfGetDB( DB_REPLICA ), $pageId, $revId );
 +              if ( $rev ) {
 +                      $rev->setTitle( $title );
 +              }
 +
 +              return $rev;
        }
  
        /**
                                $content = $rev->getContent();
                                $text = $content ? $content->getWikitextForTransclusion() : null;
  
+                               Hooks::run( 'ParserFetchTemplate',
+                                       [ $parser, $title, $rev, &$text, &$deps ] );
                                if ( $text === false || $text === null ) {
                                        $text = false;
                                        break;
         */
        public function fetchScaryTemplateMaybeFromCache( $url ) {
                global $wgTranscludeCacheExpiry;
 -              $dbr = wfGetDB( DB_SLAVE );
 +              $dbr = wfGetDB( DB_REPLICA );
                $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
                $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
                                [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
         * @return string
         */
        public function extensionSubstitution( $params, $frame ) {
 +              static $errorStr = '<span class="error">';
 +              static $errorLen = 20;
 +
                $name = $frame->expand( $params['name'] );
 +              if ( substr( $name, 0, $errorLen ) === $errorStr ) {
 +                      // Probably expansion depth or node count exceeded. Just punt the
 +                      // error up.
 +                      return $name;
 +              }
 +
                $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
 +              if ( substr( $attrText, 0, $errorLen ) === $errorStr ) {
 +                      // See above
 +                      return $attrText;
 +              }
 +
 +              // We can't safely check if the expansion for $content resulted in an
 +              // error, because the content could happen to be the error string
 +              // (T149622).
                $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
 +
                $marker = self::MARKER_PREFIX . "-$name-"
                        . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
  
                                $output = "<$name$attrText/>";
                        } else {
                                $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
 +                              if ( substr( $close, 0, $errorLen ) === $errorStr ) {
 +                                      // See above
 +                                      return $close;
 +                              }
                                $output = "<$name$attrText>$content$close";
                        }
                }
                        # * <b> (r105284)
                        # * <bdi> (bug 72884)
                        # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
 +                      # * <s> and <strike> (T35715)
                        # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
                        # to allow setting directionality in toc items.
                        $tocline = preg_replace(
                                [
 -                                      '#<(?!/?(span|sup|sub|bdi|i|b)(?: [^>]*)?>).*?>#',
 -                                      '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b))(?: .*?)?>#'
 +                                      '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#',
 +                                      '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#'
                                ],
                                [ '', '<$1>' ],
                                $safeHeadline
  
                        # HTML names must be case-insensitively unique (bug 10721).
                        # This does not apply to Unicode characters per
 -                      # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
 +                      # https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
                        # @todo FIXME: We may be changing them depending on the current locale.
                        $arrayKey = strtolower( $safeHeadline );
                        if ( $legacyHeadline === false ) {