]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
// '\\1\\3<div\\5>\\6</div>\\8\\9',
// # remove empty italic or bold tag pairs, some
// # introduced by rules above
// '/<([bi])><\/\\1>/' => '',
// ];
//
// $text = preg_replace(
// array_keys($tidyregs),
// array_values($tidyregs),
// $text);
// }
//
// if ($isMain) {
// Hooks::run('ParserAfterTidy', [ &$this, &$text ]);
// }
//
// return $text;
// }
//
// /**
// * Replace special strings like "ISBN xxx" and "RFC xxx" with
// * magic external links.
// *
// * DML
// * @private
// *
// * @param String $text
// *
// * @return String
// */
// public function doMagicLinks($text) {
// $prots = wfUrlProtocolsWithoutProtRel();
// $urlChar = self::EXT_LINK_URL_CLASS;
// $addr = self::EXT_LINK_ADDR;
// $space = self::SPACE_NOT_NL; # non-newline space
// $spdash = "(?:-|$space)"; # a dash or a non-newline space
// $spaces = "$space++"; # possessive match of 1 or more spaces
// $text = preg_replace_callback(
// '!(?: # Start cases
// (
].*?) | # m[1]: Skip link text
// (<.*?>) | # m[2]: Skip stuff inside
// # HTML elements' . "
// (\b(?i:$prots)($addr$urlChar*)) | # m[3]: Free external links
// # m[4]: Post-protocol path
// \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
// ([0-9]+)\b |
// \bISBN $spaces ( # m[6]: ISBN, capture number
// (?: 97[89] $spdash?)? # optional 13-digit ISBN prefix
// (?: [0-9] $spdash?){9} # 9 digits with opt. delimiters
// [0-9Xx] # check digit
// )\b
// )!xu", [ &$this, 'magicLinkCallback' ], $text);
// return $text;
// }
//
// /**
// * @throws MWException
// * @param array $m
// * @return HTML|String
// */
// public function magicLinkCallback($m) {
// if (isset($m[1]) && $m[1] !== '') {
// # Skip anchor
// return $m[0];
// } elseif (isset($m[2]) && $m[2] !== '') {
// # Skip HTML element
// return $m[0];
// } elseif (isset($m[3]) && $m[3] !== '') {
// # Free external link
// return this.makeFreeExternalLink($m[0], strlen($m[4]));
// } 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] !== ''
// && this.mOptions->getMagicISBNLinks()
// ) {
// # ISBN
// $isbn = $m[6];
// $space = self::SPACE_NOT_NL; # non-newline space
// $isbn = preg_replace("/$space/", ' ', $isbn);
// $num = strtr($isbn, [
// '-' => '',
// ' ' => '',
// 'x' => 'X',
// ]);
// this.addTrackingCategory('magiclink-tracking-isbn');
// return this.getLinkRenderer()->makeKnownLink(
// SpecialPage::getTitleFor('Booksources', $num),
// "ISBN $isbn",
// [
// 'class' => '@gplx.Internal protected mw-magiclink-isbn',
// 'title' => false // suppress title attribute
// ]
// );
// } else {
// return $m[0];
// }
// }
//
// /**
// * Make a free external link, given a user-supplied URL
// *
// * @param String $url
// * @param int $numPostProto
// * The number of characters after the protocol.
// * @return String HTML
// * @private
// */
// public function makeFreeExternalLink($url, $numPostProto) {
// $trail = '';
//
// # The characters '<' and '>' (which were escaped by
// # removeHTMLtags()) should not be included in
// # URLs, per RFC 2396.
// # Make terminate a URL as well (bug T84937)
// $m2 = [];
// if (preg_match(
// '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
// $url,
// $m2,
// PREG_OFFSET_CAPTURE
// )) {
// $trail = substr($url, $m2[0][1]) . $trail;
// $url = substr($url, 0, $m2[0][1]);
// }
//
// # Move trailing punctuation to $trail
// $sep = ',;\.:!?';
// # If there is no left bracket, then consider right brackets fair game too
// if (strpos($url, '(') === false) {
// $sep .= ')';
// }
//
// $urlRev = strrev($url);
// $numSepChars = strspn($urlRev, $sep);
// # Don't break a trailing HTML entity by moving the ; into $trail
// # This is in hot code, so use substr_compare to avoid having to
// # create a new String Object for the comparison
// if ($numSepChars && substr_compare($url, ";", -$numSepChars, 1) === 0) {
// # more optimization: instead of running preg_match with a $
// # anchor, which can be slow, do the match on the reversed
// # String starting at the desired offset.
// # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
// if (preg_match('/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars)) {
// $numSepChars--;
// }
// }
// if ($numSepChars) {
// $trail = substr($url, -$numSepChars) . $trail;
// $url = substr($url, 0, -$numSepChars);
// }
//
// # Verify that we still have a real URL after trail removal, and
// # not just lone protocol
// if (strlen($trail) >= $numPostProto) {
// return $url . $trail;
// }
//
// $url = Sanitizer::cleanUrl($url);
//
// # Is this an external image?
// $text = this.maybeMakeExternalImage($url);
// if ($text === false) {
// # Not an image, make a link
// $text = Linker::makeExternalLink($url,
// this.getConverterLanguage()->markNoConversion($url, true),
// true, 'free',
// this.getExternalLinkAttribs($url), this.mTitle);
// # Register it in the output Object...
// # Replace unnecessary URL escape codes with their equivalent characters
// $pasteurized = self::normalizeLinkUrl($url);
// this.mOutput->addExternalLink($pasteurized);
// }
// return $text . $trail;
// }
//
// /**
// * Parse headers and return html
// *
// * @private
// *
// * @param String $text
// *
// * @return String
// */
// public function doHeadings($text) {
// for ($i = 6; $i >= 1; --$i) {
// $h = str_repeat('=', $i);
// $text = preg_replace("/^$h(.+)$h\\s*$/m", "
\\1", $text);
// }
// return $text;
// }
//
// /**
// * Replace single quotes with HTML markup
// * @private
// *
// * @param String $text
// *
// * @return String The altered text
// */
// public function doAllQuotes($text) {
// $outtext = '';
// $lines = StringUtils::explode("\n", $text);
// foreach ($lines as $line) {
// $outtext .= this.doQuotes($line) . "\n";
// }
// $outtext = substr($outtext, 0, -1);
// return $outtext;
// }
//
// /**
// * Helper function for doAllQuotes()
// *
// * @param String $text
// *
// * @return String
// */
// public function doQuotes($text) {
// $arr = preg_split("/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
// $countarr = count($arr);
// if ($countarr == 1) {
// return $text;
// }
//
// // First, do some preliminary work. This may shift some apostrophes from
// // being mark-up to being text. It also counts the number of occurrences
// // of bold and italics mark-ups.
// $numbold = 0;
// $numitalics = 0;
// for ($i = 1; $i < $countarr; $i += 2) {
// $thislen = strlen($arr[$i]);
// // If there are ever four apostrophes, assume the first is supposed to
// // be text, and the remaining three constitute mark-up for bold text.
// // (T15227: ''''foo'''' turns into ' ''' foo ' ''')
// if ($thislen == 4) {
// $arr[$i - 1] .= "'";
// $arr[$i] = "'''";
// $thislen = 3;
// } elseif ($thislen > 5) {
// // If there are more than 5 apostrophes in a row, assume they're all
// // text except for the last 5.
// // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
// $arr[$i - 1] .= str_repeat("'", $thislen - 5);
// $arr[$i] = "'''''";
// $thislen = 5;
// }
// // Count the number of occurrences of bold and italics mark-ups.
// if ($thislen == 2) {
// $numitalics++;
// } elseif ($thislen == 3) {
// $numbold++;
// } elseif ($thislen == 5) {
// $numitalics++;
// $numbold++;
// }
// }
//
// // If there is an odd number of both bold and italics, it is likely
// // that one of the bold ones was meant to be an apostrophe followed
// // by italics. Which one we cannot know for certain, but it is more
// // likely to be one that has a single-letter word before it.
// if (($numbold % 2 == 1) && ($numitalics % 2 == 1)) {
// $firstsingleletterword = -1;
// $firstmultiletterword = -1;
// $firstspace = -1;
// for ($i = 1; $i < $countarr; $i += 2) {
// if (strlen($arr[$i]) == 3) {
// $x1 = substr($arr[$i - 1], -1);
// $x2 = substr($arr[$i - 1], -2, 1);
// if ($x1 === ' ') {
// if ($firstspace == -1) {
// $firstspace = $i;
// }
// } elseif ($x2 === ' ') {
// $firstsingleletterword = $i;
// // if $firstsingleletterword is set, we don't
// // look at the other options, so we can bail early.
// break;
// } else {
// if ($firstmultiletterword == -1) {
// $firstmultiletterword = $i;
// }
// }
// }
// }
//
// // If there is a single-letter word, use it!
// if ($firstsingleletterword > -1) {
// $arr[$firstsingleletterword] = "''";
// $arr[$firstsingleletterword - 1] .= "'";
// } elseif ($firstmultiletterword > -1) {
// // If not, but there's a multi-letter word, use that one.
// $arr[$firstmultiletterword] = "''";
// $arr[$firstmultiletterword - 1] .= "'";
// } elseif ($firstspace > -1) {
// // ... otherwise use the first one that has neither.
// // (notice that it is possible for all three to be -1 if, for example,
// // there is only one pentuple-apostrophe in the line)
// $arr[$firstspace] = "''";
// $arr[$firstspace - 1] .= "'";
// }
// }
//
// // Now let's actually convert our apostrophic mush to HTML!
// $output = '';
// $buffer = '';
// $state = '';
// $i = 0;
// foreach ($arr as $r) {
// if (($i % 2) == 0) {
// if ($state === 'both') {
// $buffer .= $r;
// } else {
// $output .= $r;
// }
// } else {
// $thislen = strlen($r);
// if ($thislen == 2) {
// if ($state === 'i') {
// $output .= '';
// $state = '';
// } elseif ($state === 'bi') {
// $output .= '';
// $state = 'b';
// } elseif ($state === 'ib') {
// $output .= '
';
// $state = 'b';
// } elseif ($state === 'both') {
// $output .= '' . $buffer . '';
// $state = 'b';
// } else { // $state can be 'b' or ''
// $output .= '';
// $state .= 'i';
// }
// } elseif ($thislen == 3) {
// if ($state === 'b') {
// $output .= '';
// $state = '';
// } elseif ($state === 'bi') {
// $output .= '';
// $state = 'i';
// } elseif ($state === 'ib') {
// $output .= '';
// $state = 'i';
// } elseif ($state === 'both') {
// $output .= '' . $buffer . '';
// $state = 'i';
// } else { // $state can be 'i' or ''
// $output .= '';
// $state .= 'b';
// }
// } elseif ($thislen == 5) {
// if ($state === 'b') {
// $output .= '';
// $state = 'i';
// } elseif ($state === 'i') {
// $output .= '';
// $state = 'b';
// } elseif ($state === 'bi') {
// $output .= '';
// $state = '';
// } elseif ($state === 'ib') {
// $output .= '';
// $state = '';
// } elseif ($state === 'both') {
// $output .= '
' . $buffer . '';
// $state = '';
// } else { // ($state == '')
// $buffer = '';
// $state = 'both';
// }
// }
// }
// $i++;
// }
// // Now close all remaining tags. Notice that the order is important.
// if ($state === 'b' || $state === 'ib') {
// $output .= '';
// }
// if ($state === 'i' || $state === 'bi' || $state === 'ib') {
// $output .= '';
// }
// if ($state === 'bi') {
// $output .= '';
// }
// // There might be lonely ''''', so make sure we have a buffer
// if ($state === 'both' && $buffer) {
// $output .= '
' . $buffer . '';
// }
// return $output;
// }
//
// /**
// * Replace external links (REL)
// *
// * Note: this is all very hackish and the order of execution matters a lot.
// * Make sure to run tests/parser/parserTests.php if you change this code.
// *
// * @private
// *
// * @param String $text
// *
// * @throws MWException
// * @return String
// */
// public function replaceExternalLinks($text) {
//
// $bits = preg_split(this.mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
// if ($bits === false) {
// throw new MWException("PCRE needs to be compiled with "
// . "--enable-unicode-properties in order for MediaWiki to function");
// }
// $s = array_shift($bits);
//
// $i = 0;
// while ($i < count($bits)) {
// $url = $bits[$i++];
// $i++; // protocol
// $text = $bits[$i++];
// $trail = $bits[$i++];
//
// # The characters '<' and '>' (which were escaped by
// # removeHTMLtags()) should not be included in
// # URLs, per RFC 2396.
// $m2 = [];
// if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) {
// $text = substr($url, $m2[0][1]) . ' ' . $text;
// $url = substr($url, 0, $m2[0][1]);
// }
//
// # If the link text is an image URL, replace it with an
![]()
tag
// # This happened by accident in the original parser, but some people used it extensively
// $img = this.maybeMakeExternalImage($text);
// if ($img !== false) {
// $text = $img;
// }
//
// $dtrail = '';
//
// # Set linktype for CSS - if URL==text, link is essentially free
// $linktype = ($text === $url) ? 'free' : 'text';
//
// # No link text, e.g. [http://domain.tld/some.link]
// if ($text == '') {
// # Autonumber
// $langObj = this.getTargetLanguage();
// $text = '[' . $langObj->formatNum(++this.mAutonumber) . ']';
// $linktype = 'autonumber';
// } else {
// # Have link text, e.g. [http://domain.tld/some.link text]s
// # Check for trail
// list($dtrail, $trail) = Linker::splitTrail($trail);
// }
//
// $text = this.getConverterLanguage()->markNoConversion($text);
//
// $url = Sanitizer::cleanUrl($url);
//
// # Use the encoded URL
// # This means that users can paste URLs directly into the text
// # Funny characters like � aren't valid in URLs anyway
// # This was changed in August 2004
// $s .= Linker::makeExternalLink($url, $text, false, $linktype,
// this.getExternalLinkAttribs($url), this.mTitle) . $dtrail . $trail;
//
// # Register link in the output Object.
// # Replace unnecessary URL escape codes with the referenced character
// # This prevents spammers from hiding links from the filters
// $pasteurized = self::normalizeLinkUrl($url);
// this.mOutput->addExternalLink($pasteurized);
// }
//
// return $s;
// }
//
// /**
// * Get the rel attribute for a particular external link.
// *
// * @since 1.21
// * @param String|boolean $url Optional URL, to extract the domain from for rel =>
// * nofollow if appropriate
// * @param Title $title Optional Title, for wgNoFollowNsExceptions lookups
// * @return String|null Rel attribute for $url
// */
// public static function getExternalLinkRel($url = false, $title = null) {
// global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
// $ns = $title ? $title->getNamespace() : false;
// if ($wgNoFollowLinks && !in_array($ns, $wgNoFollowNsExceptions)
// && !wfMatchesDomainList($url, $wgNoFollowDomainExceptions)
// ) {
// return 'nofollow';
// }
// return null;
// }
//
// /**
// * Get an associative array of additional HTML attributes appropriate for a
// * particular external link. This currently may include rel => nofollow
// * (depending on configuration, namespace, and the URL's domain) and/or a
// * target attribute (depending on configuration).
// *
// * @param String $url URL to extract the domain from for rel =>
// * nofollow if appropriate
// * @return array Associative array of HTML attributes
// */
// public function getExternalLinkAttribs($url) {
// $attribs = [];
// $rel = self::getExternalLinkRel($url, this.mTitle);
//
// $target = this.mOptions->getExternalLinkTarget();
// if ($target) {
// $attribs['target'] = $target;
// if (!in_array($target, [ '_self', '_parent', '_top' ])) {
// // T133507. New windows can navigate parent cross-origin.
// // Including noreferrer due to lacking browser
// // support of noopener. Eventually noreferrer should be removed.
// if ($rel !== '') {
// $rel .= ' ';
// }
// $rel .= 'noreferrer noopener';
// }
// }
// $attribs['rel'] = $rel;
// return $attribs;
// }
//
// /**
// * Replace unusual escape codes in a URL with their equivalent characters
// *
// * This generally follows the syntax defined in RFC 3986, with special
// * consideration for HTTP query strings.
// *
// * @param String $url
// * @return String
// */
// public static function normalizeLinkUrl($url) {
// # First, make sure unsafe characters are encoded
// $url = preg_replace_callback('/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
// function ($m) {
// return rawurlencode($m[0]);
// },
// $url
// );
//
// $ret = '';
// $end = strlen($url);
//
// # Fragment part - 'fragment'
// $start = strpos($url, '#');
// if ($start !== false && $start < $end) {
// $ret = self::normalizeUrlComponent(
// substr($url, $start, $end - $start), '"#%<>[\]^`{|}') . $ret;
// $end = $start;
// }
//
// # Query part - 'query' minus &=+;
// $start = strpos($url, '?');
// if ($start !== false && $start < $end) {
// $ret = self::normalizeUrlComponent(
// substr($url, $start, $end - $start), '"#%<>[\]^`{|}&=+;') . $ret;
// $end = $start;
// }
//
// # Scheme and path part - 'pchar'
// # (we assume no userinfo or encoded colons in the host)
// $ret = self::normalizeUrlComponent(
// substr($url, 0, $end), '"#%<>[\]^`{|}/?') . $ret;
//
// return $ret;
// }
//
// private static function normalizeUrlComponent($component, $unsafe) {
// $callback = function ($matches) use ($unsafe) {
// $char = urldecode($matches[0]);
// $ord = ord($char);
// if ($ord > 32 && $ord < 127 && strpos($unsafe, $char) === false) {
// # Unescape it
// return $char;
// } else {
// # Leave it escaped, but use uppercase for a-f
// return strtoupper($matches[0]);
// }
// };
// return preg_replace_callback('/%[0-9A-Fa-f]{2}/', $callback, $component);
// }
//
// /**
// * make an image if it's allowed, either through the global
// * option, through the exception, or through the on-wiki whitelist
// *
// * @param String $url
// *
// * @return String
// */
// private function maybeMakeExternalImage($url) {
// $imagesfrom = this.mOptions->getAllowExternalImagesFrom();
// $imagesexception = !empty($imagesfrom);
// $text = false;
// # $imagesfrom could be either a single String or an array of strings, parse out the latter
// if ($imagesexception && is_array($imagesfrom)) {
// $imagematch = false;
// foreach ($imagesfrom as $match) {
// if (strpos($url, $match) === 0) {
// $imagematch = true;
// break;
// }
// }
// } elseif ($imagesexception) {
// $imagematch = (strpos($url, $imagesfrom) === 0);
// } else {
// $imagematch = false;
// }
//
// if (this.mOptions->getAllowExternalImages()
// || ($imagesexception && $imagematch)
// ) {
// if (preg_match(self::EXT_IMAGE_REGEX, $url)) {
// # Image found
// $text = Linker::makeExternalImage($url);
// }
// }
// if (!$text && this.mOptions->getEnableImageWhitelist()
// && preg_match(self::EXT_IMAGE_REGEX, $url)
// ) {
// $whitelist = explode(
// "\n",
// wfMessage('external_image_whitelist')->inContentLanguage()->text()
// );
//
// foreach ($whitelist as $entry) {
// # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
// if (strpos($entry, '#') === 0 || $entry === '') {
// continue;
// }
// if (preg_match('/' . str_replace('/', '\\/', $entry) . '/i', $url)) {
// # Image matches a whitelist entry
// $text = Linker::makeExternalImage($url);
// break;
// }
// }
// }
// return $text;
// }
//
// /**
// * Process [[ ]] wikilinks
// *
// * @param String $s
// *
// * @return String Processed text
// *
// * @private
// */
// public function replaceInternalLinks($s) {
// this.mLinkHolders->merge(this.replaceInternalLinks2($s));
// return $s;
// }
//
// /**
// * Process [[ ]] wikilinks (RIL)
// * @param String $s
// * @throws MWException
// * @return LinkHolderArray
// *
// * @private
// */
// public function replaceInternalLinks2(&$s) {
// global $wgExtraInterlanguageLinkPrefixes;
//
// static $tc = false, $e1, $e1_img;
// # the % is needed to support urlencoded titles as well
// if (!$tc) {
// $tc = Title::legalChars() . '#%';
// # Match a link having the form [[namespace:link|alternate]]trail
// $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
// # Match cases where there is no "]]", which might still be images
// $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
// }
//
// $holders = new LinkHolderArray($this);
//
// # split the entire text String on occurrences of [[
// $a = StringUtils::explode('[[', ' ' . $s);
// # get the first element (all text up to first [[), and remove the space we added
// $s = $a->current();
// $a->next();
// $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
// $s = substr($s, 1);
//
// $useLinkPrefixExtension = this.getTargetLanguage()->linkPrefixExtension();
// $e2 = null;
// if ($useLinkPrefixExtension) {
// # Match the end of a line for a word that's not followed by whitespace,
// # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
// global $wgContLang;
// $charset = $wgContLang->linkPrefixCharset();
// $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
// }
//
// if (is_null(this.mTitle)) {
// throw new MWException(__METHOD__ . ": \this.mTitle is null\n");
// }
// $nottalk = !this.mTitle->isTalkPage();
//
// if ($useLinkPrefixExtension) {
// $m = [];
// if (preg_match($e2, $s, $m)) {
// $first_prefix = $m[2];
// } else {
// $first_prefix = false;
// }
// } else {
// $prefix = '';
// }
//
// $useSubpages = this.areSubpagesAllowed();
//
// // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
// # Loop for each link
// for (; $line !== false && $line !== null; $a->next(), $line = $a->current()) {
// // @codingStandardsIgnoreEnd
//
// # Check for excessive memory usage
// if ($holders->isBig()) {
// # Too big
// # Do the existence check, replace the link holders and clear the array
// $holders->replace($s);
// $holders->clear();
// }
//
// if ($useLinkPrefixExtension) {
// if (preg_match($e2, $s, $m)) {
// $prefix = $m[2];
// $s = $m[1];
// } else {
// $prefix = '';
// }
// # first link
// if ($first_prefix) {
// $prefix = $first_prefix;
// $first_prefix = false;
// }
// }
//
// $might_be_img = false;
//
// if (preg_match($e1, $line, $m)) { # page with normal text or alt
// $text = $m[2];
// # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
// # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
// # the real problem is with the $e1 regex
// # See T1500.
// # Still some problems for cases where the ] is meant to be outside punctuation,
// # and no image is in sight. See T4095.
// if ($text !== ''
// && substr($m[3], 0, 1) === ']'
// && strpos($text, '[') !== false
// ) {
// $text .= ']'; # so that replaceExternalLinks($text) works later
// $m[3] = substr($m[3], 1);
// }
// # fix up urlencoded title texts
// if (strpos($m[1], '%') !== false) {
// # Should anchors '#' also be rejected?
// $m[1] = str_replace([ '<', '>' ], [ '<', '>' ], rawurldecode($m[1]));
// }
// $trail = $m[3];
// } elseif (preg_match($e1_img, $line, $m)) {
// # Invalid, but might be an image with a link in its caption
// $might_be_img = true;
// $text = $m[2];
// if (strpos($m[1], '%') !== false) {
// $m[1] = str_replace([ '<', '>' ], [ '<', '>' ], rawurldecode($m[1]));
// }
// $trail = "";
// } else { # Invalid form; output directly
// $s .= $prefix . '[[' . $line;
// continue;
// }
//
// $origLink = ltrim($m[1], ' ');
//
// # Don't allow @gplx.Internal protected links to pages containing
// # PROTO: where PROTO is a valid URL protocol; these
// # should be external links.
// if (preg_match('/^(?i:' . this.mUrlProtocols . ')/', $origLink)) {
// $s .= $prefix . '[[' . $line;
// continue;
// }
//
// # Make subpage if necessary
// if ($useSubpages) {
// $link = this.maybeDoSubpageLink($origLink, $text);
// } else {
// $link = $origLink;
// }
//
// $noforce = (substr($origLink, 0, 1) !== ':');
// if (!$noforce) {
// # Strip off leading ':'
// $link = substr($link, 1);
// }
//
// $unstrip = this.mStripState->unstripNoWiki($link);
// $nt = is_string($unstrip) ? Title::newFromText($unstrip) : null;
// if ($nt === null) {
// $s .= $prefix . '[[' . $line;
// continue;
// }
//
// $ns = $nt->getNamespace();
// $iw = $nt->getInterwiki();
//
// if ($might_be_img) { # if this is actually an invalid link
// if ($ns == NS_FILE && $noforce) { # but might be an image
// $found = false;
// while (true) {
// # look at the next 'line' to see if we can close it there
// $a->next();
// $next_line = $a->current();
// if ($next_line === false || $next_line === null) {
// break;
// }
// $m = explode(']]', $next_line, 3);
// if (count($m) == 3) {
// # the first ]] closes the inner link, the second the image
// $found = true;
// $text .= "[[{$m[0]}]]{$m[1]}";
// $trail = $m[2];
// break;
// } elseif (count($m) == 2) {
// # if there's exactly one ]] that's fine, we'll keep looking
// $text .= "[[{$m[0]}]]{$m[1]}";
// } else {
// # if $next_line is invalid too, we need look no further
// $text .= '[[' . $next_line;
// break;
// }
// }
// if (!$found) {
// # we couldn't find the end of this imageLink, so output it raw
// # but don't ignore what might be perfectly normal links in the text we've examined
// $holders->merge(this.replaceInternalLinks2($text));
// $s .= "{$prefix}[[$link|$text";
// # note: no $trail, because without an end, there *is* no trail
// continue;
// }
// } else { # it's not an image, so output it raw
// $s .= "{$prefix}[[$link|$text";
// # note: no $trail, because without an end, there *is* no trail
// continue;
// }
// }
//
// $wasblank = ($text == '');
// if ($wasblank) {
// $text = $link;
// } else {
// # T6598 madness. Handle the quotes only if they come from the alternate part
// # [[Lista d''e paise d''o munno]] ->
Lista d''e paise d''o munno
// # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
// # ->
Criticism of Harry Potter
// $text = this.doQuotes($text);
// }
//
// # Link not escaped by : , create the various objects
// if ($noforce && !$nt->wasLocalInterwiki()) {
// # Interwikis
// if (
// $iw && this.mOptions->getInterwikiMagic() && $nottalk && (
// Language::fetchLanguageName($iw, null, 'mw') ||
// in_array($iw, $wgExtraInterlanguageLinkPrefixes)
// )
// ) {
// # T26502: filter duplicates
// if (!isset(this.mLangLinkLanguages[$iw])) {
// this.mLangLinkLanguages[$iw] = true;
// this.mOutput->addLanguageLink($nt->getFullText());
// }
//
// $s = rtrim($s . $prefix);
// $s .= trim($trail, "\n") == '' ? '': $prefix . $trail;
// continue;
// }
//
// if ($ns == NS_FILE) {
// if (!wfIsBadImage($nt->getDBkey(), this.mTitle)) {
// if ($wasblank) {
// # if no parameters were passed, $text
// # becomes something like "File:Foo.png",
// # which we don't want to pass on to the
// # image generator
// $text = '';
// } else {
// # recursively parse links inside the image caption
// # actually, this will parse them in any other parameters, too,
// # but it might be hard to fix that, and it doesn't matter ATM
// $text = this.replaceExternalLinks($text);
// $holders->merge(this.replaceInternalLinks2($text));
// }
// # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
// $s .= $prefix . this.armorLinks(
// this.makeImage($nt, $text, $holders)) . $trail;
// continue;
// }
// } elseif ($ns == NS_CATEGORY) {
// $s = rtrim($s . "\n"); # T2087
//
// if ($wasblank) {
// $sortkey = this.getDefaultSort();
// } else {
// $sortkey = $text;
// }
// $sortkey = Sanitizer::decodeCharReferences($sortkey);
// $sortkey = str_replace("\n", '', $sortkey);
// $sortkey = this.getConverterLanguage()->convertCategoryKey($sortkey);
// this.mOutput->addCategory($nt->getDBkey(), $sortkey);
//
// /**
// * Strip the whitespace Category links produce, see T2087
// */
// $s .= trim($prefix . $trail, "\n") == '' ? '' : $prefix . $trail;
//
// continue;
// }
// }
//
// # Self-link checking. For some languages, variants of the title are checked in
// # LinkHolderArray::doVariants() to allow batching the existence checks necessary
// # for linking to a different variant.
// if ($ns != NS_SPECIAL && $nt->equals(this.mTitle) && !$nt->hasFragment()) {
// $s .= $prefix . Linker::makeSelfLinkObj($nt, $text, '', $trail);
// continue;
// }
//
// # NS_MEDIA is a pseudo-namespace for linking directly to a file
// # @todo FIXME: Should do batch file existence checks, see comment below
// if ($ns == NS_MEDIA) {
// # Give extensions a chance to select the file revision for us
// $options = [];
// $descQuery = false;
// Hooks::run('BeforeParserFetchFileAndTitle',
// [ $this, $nt, &$options, &$descQuery ]);
// # Fetch and register the file (file title may be different via hooks)
// list($file, $nt) = this.fetchFileAndTitle($nt, $options);
// # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
// $s .= $prefix . this.armorLinks(
// Linker::makeMediaLinkFile($nt, $file, $text)) . $trail;
// continue;
// }
//
// # Some titles, such as valid special pages or files in foreign repos, should
// # be shown as bluelinks even though they're not included in the page table
// # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
// # batch file existence checks for NS_FILE and NS_MEDIA
// if ($iw == '' && $nt->isAlwaysKnown()) {
// this.mOutput->addLink($nt);
// $s .= this.makeKnownLinkHolder($nt, $text, $trail, $prefix);
// } else {
// # Links will be added to the output link list after checking
// $s .= $holders->makeHolder($nt, $text, [], $trail, $prefix);
// }
// }
// return $holders;
// }
//
// /**
// * Render a forced-blue link inline; protect against double expansion of
// * URLs if we're in a mode that prepends full URL prefixes to @gplx.Internal protected links.
// * Since this little disaster has to split off the trail text to avoid
// * breaking URLs in the following text without breaking trails on the
// * wiki links, it's been made into a horrible function.
// *
// * @param Title $nt
// * @param String $text
// * @param String $trail
// * @param String $prefix
// * @return String HTML-wikitext mix oh yuck
// */
// protected function makeKnownLinkHolder($nt, $text = '', $trail = '', $prefix = '') {
// list($inside, $trail) = Linker::splitTrail($trail);
//
// if ($text == '') {
// $text = htmlspecialchars($nt->getPrefixedText());
// }
//
// $link = this.getLinkRenderer()->makeKnownLink(
// $nt, new HtmlArmor("$prefix$text$inside")
// );
//
// return this.armorLinks($link) . $trail;
// }
//
// /**
// * Insert a NOPARSE hacky thing into any inline links in a chunk that's
// * going to go through further parsing steps before inline URL expansion.
// *
// * Not needed quite as much as it used to be since free links are a bit
// * more sensible these days. But bracketed links are still an issue.
// *
// * @param String $text More-or-less HTML
// * @return String Less-or-more HTML with NOPARSE bits
// */
// public function armorLinks($text) {
// return preg_replace('/\b((?i)' . this.mUrlProtocols . ')/',
// self::MARKER_PREFIX . "NOPARSE$1", $text);
// }
//
// /**
// * Return true if subpage links should be expanded on this page.
// * @return boolean
// */
// public function areSubpagesAllowed() {
// # Some namespaces don't allow subpages
// return MWNamespace::hasSubpages(this.mTitle->getNamespace());
// }
//
// /**
// * Handle link to subpage if necessary
// *
// * @param String $target The source of the link
// * @param String &$text The link text, modified as necessary
// * @return String The full name of the link
// * @private
// */
// public function maybeDoSubpageLink($target, &$text) {
// return Linker::normalizeSubpageLink(this.mTitle, $target, $text);
// }
//
// /**
// * Make lists from lines starting with ':', '*', '#', etc. (DBL)
// *
// * @param String $text
// * @param boolean $linestart Whether or not this is at the start of a line.
// * @private
// * @return String The lists rendered as HTML
// */
// public function doBlockLevels($text, $linestart) {
// return BlockLevelPass::doBlockLevels($text, $linestart);
// }
//
// /**
// * Return value of a magic variable (like PAGENAME)
// *
// * @private
// *
// * @param int $index
// * @param boolean|PPFrame $frame
// *
// * @throws MWException
// * @return String
// */
// public function getVariableValue($index, $frame = false) {
// global $wgContLang, $wgSitename, $wgServer, $wgServerName;
// global $wgArticlePath, $wgScriptPath, $wgStylePath;
//
// if (is_null(this.mTitle)) {
// // If no title set, bad things are going to happen
// // later. Title should always be set since this
// // should only be called in the middle of a parse
// // operation (but the unit-tests do funky stuff)
// throw new MWException(__METHOD__ . ' Should only be '
// . ' called while parsing (no title set)');
// }
//
// /**
// * Some of these require message or data lookups and can be
// * expensive to check many times.
// */
// if (Hooks::run('ParserGetVariableValueVarCache', [ &$this, &this.mVarCache ])) {
// if (isset(this.mVarCache[$index])) {
// return this.mVarCache[$index];
// }
// }
//
// $ts = wfTimestamp(TS_UNIX, this.mOptions->getTimestamp());
// Hooks::run('ParserGetVariableValueTs', [ &$this, &$ts ]);
//
// $pageLang = this.getFunctionLang();
//
// switch ($index) {
// case '!':
// $value = '|';
// break;
// case 'currentmonth':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('m'));
// break;
// case 'currentmonth1':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('n'));
// break;
// case 'currentmonthname':
// $value = $pageLang->getMonthName(MWTimestamp::getInstance($ts)->format('n'));
// break;
// case 'currentmonthnamegen':
// $value = $pageLang->getMonthNameGen(MWTimestamp::getInstance($ts)->format('n'));
// break;
// case 'currentmonthabbrev':
// $value = $pageLang->getMonthAbbreviation(MWTimestamp::getInstance($ts)->format('n'));
// break;
// case 'currentday':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('j'));
// break;
// case 'currentday2':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('d'));
// break;
// case 'localmonth':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('m'));
// break;
// case 'localmonth1':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('n'));
// break;
// case 'localmonthname':
// $value = $pageLang->getMonthName(MWTimestamp::getLocalInstance($ts)->format('n'));
// break;
// case 'localmonthnamegen':
// $value = $pageLang->getMonthNameGen(MWTimestamp::getLocalInstance($ts)->format('n'));
// break;
// case 'localmonthabbrev':
// $value = $pageLang->getMonthAbbreviation(MWTimestamp::getLocalInstance($ts)->format('n'));
// break;
// case 'localday':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('j'));
// break;
// case 'localday2':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('d'));
// break;
// case 'pagename':
// $value = wfEscapeWikiText(this.mTitle->getText());
// break;
// case 'pagenamee':
// $value = wfEscapeWikiText(this.mTitle->getPartialURL());
// break;
// case 'fullpagename':
// $value = wfEscapeWikiText(this.mTitle->getPrefixedText());
// break;
// case 'fullpagenamee':
// $value = wfEscapeWikiText(this.mTitle->getPrefixedURL());
// break;
// case 'subpagename':
// $value = wfEscapeWikiText(this.mTitle->getSubpageText());
// break;
// case 'subpagenamee':
// $value = wfEscapeWikiText(this.mTitle->getSubpageUrlForm());
// break;
// case 'rootpagename':
// $value = wfEscapeWikiText(this.mTitle->getRootText());
// break;
// case 'rootpagenamee':
// $value = wfEscapeWikiText(wfUrlencode(str_replace(
// ' ',
// '_',
// this.mTitle->getRootText()
// )));
// break;
// case 'basepagename':
// $value = wfEscapeWikiText(this.mTitle->getBaseText());
// break;
// case 'basepagenamee':
// $value = wfEscapeWikiText(wfUrlencode(str_replace(
// ' ',
// '_',
// this.mTitle->getBaseText()
// )));
// break;
// case 'talkpagename':
// if (this.mTitle->canTalk()) {
// $talkPage = this.mTitle->getTalkPage();
// $value = wfEscapeWikiText($talkPage->getPrefixedText());
// } else {
// $value = '';
// }
// break;
// case 'talkpagenamee':
// if (this.mTitle->canTalk()) {
// $talkPage = this.mTitle->getTalkPage();
// $value = wfEscapeWikiText($talkPage->getPrefixedURL());
// } else {
// $value = '';
// }
// break;
// case 'subjectpagename':
// $subjPage = this.mTitle->getSubjectPage();
// $value = wfEscapeWikiText($subjPage->getPrefixedText());
// break;
// case 'subjectpagenamee':
// $subjPage = this.mTitle->getSubjectPage();
// $value = wfEscapeWikiText($subjPage->getPrefixedURL());
// break;
// case 'pageid': // requested in T25427
// $pageid = this.getTitle()->getArticleID();
// if ($pageid == 0) {
// # 0 means the page doesn't exist in the database,
// # which means the user is previewing a new page.
// # The vary-revision flag must be set, because the magic word
// # will have a different value once the page is saved.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n");
// }
// $value = $pageid ? $pageid : null;
// break;
// case 'revisionid':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned.
// this.mOutput->setFlag('vary-revision-id');
// wfDebug(__METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n");
// $value = this.mRevisionId;
// if (!$value && this.mOptions->getSpeculativeRevIdCallback()) {
// $value = call_user_func(this.mOptions->getSpeculativeRevIdCallback());
// this.mOutput->setSpeculativeRevIdUsed($value);
// }
// break;
// case 'revisionday':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n");
// $value = intval(substr(this.getRevisionTimestamp(), 6, 2));
// break;
// case 'revisionday2':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n");
// $value = substr(this.getRevisionTimestamp(), 6, 2);
// break;
// case 'revisionmonth':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n");
// $value = substr(this.getRevisionTimestamp(), 4, 2);
// break;
// case 'revisionmonth1':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n");
// $value = intval(substr(this.getRevisionTimestamp(), 4, 2));
// break;
// case 'revisionyear':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n");
// $value = substr(this.getRevisionTimestamp(), 0, 4);
// break;
// case 'revisiontimestamp':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned. This is for null edits.
// this.mOutput->setFlag('vary-revision');
// wfDebug(__METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n");
// $value = this.getRevisionTimestamp();
// break;
// case 'revisionuser':
// # Let the edit saving system know we should parse the page
// # *after* a revision ID has been assigned for null edits.
// this.mOutput->setFlag('vary-user');
// wfDebug(__METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n");
// $value = this.getRevisionUser();
// break;
// case 'revisionsize':
// $value = this.getRevisionSize();
// break;
// case 'namespace':
// $value = str_replace('_', ' ', $wgContLang->getNsText(this.mTitle->getNamespace()));
// break;
// case 'namespacee':
// $value = wfUrlencode($wgContLang->getNsText(this.mTitle->getNamespace()));
// break;
// case 'namespacenumber':
// $value = this.mTitle->getNamespace();
// break;
// case 'talkspace':
// $value = this.mTitle->canTalk()
// ? str_replace('_', ' ', this.mTitle->getTalkNsText())
// : '';
// break;
// case 'talkspacee':
// $value = this.mTitle->canTalk() ? wfUrlencode(this.mTitle->getTalkNsText()) : '';
// break;
// case 'subjectspace':
// $value = str_replace('_', ' ', this.mTitle->getSubjectNsText());
// break;
// case 'subjectspacee':
// $value = (wfUrlencode(this.mTitle->getSubjectNsText()));
// break;
// case 'currentdayname':
// $value = $pageLang->getWeekdayName((int)MWTimestamp::getInstance($ts)->format('w') + 1);
// break;
// case 'currentyear':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('Y'), true);
// break;
// case 'currenttime':
// $value = $pageLang->time(wfTimestamp(TS_MW, $ts), false, false);
// break;
// case 'currenthour':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('H'), true);
// break;
// case 'currentweek':
// # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
// # int to remove the padding
// $value = $pageLang->formatNum((int)MWTimestamp::getInstance($ts)->format('W'));
// break;
// case 'currentdow':
// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('w'));
// break;
// case 'localdayname':
// $value = $pageLang->getWeekdayName(
// (int)MWTimestamp::getLocalInstance($ts)->format('w') + 1
// );
// break;
// case 'localyear':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('Y'), true);
// break;
// case 'localtime':
// $value = $pageLang->time(
// MWTimestamp::getLocalInstance($ts)->format('YmdHis'),
// false,
// false
// );
// break;
// case 'localhour':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('H'), true);
// break;
// case 'localweek':
// # @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to
// # int to remove the padding
// $value = $pageLang->formatNum((int)MWTimestamp::getLocalInstance($ts)->format('W'));
// break;
// case 'localdow':
// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('w'));
// break;
// case 'numberofarticles':
// $value = $pageLang->formatNum(SiteStats::articles());
// break;
// case 'numberoffiles':
// $value = $pageLang->formatNum(SiteStats::images());
// break;
// case 'numberofusers':
// $value = $pageLang->formatNum(SiteStats::users());
// break;
// case 'numberofactiveusers':
// $value = $pageLang->formatNum(SiteStats::activeUsers());
// break;
// case 'numberofpages':
// $value = $pageLang->formatNum(SiteStats::pages());
// break;
// case 'numberofadmins':
// $value = $pageLang->formatNum(SiteStats::numberingroup('sysop'));
// break;
// case 'numberofedits':
// $value = $pageLang->formatNum(SiteStats::edits());
// break;
// case 'currenttimestamp':
// $value = wfTimestamp(TS_MW, $ts);
// break;
// case 'localtimestamp':
// $value = MWTimestamp::getLocalInstance($ts)->format('YmdHis');
// break;
// case 'currentversion':
// $value = SpecialVersion::getVersion();
// break;
// case 'articlepath':
// return $wgArticlePath;
// case 'sitename':
// return $wgSitename;
// case 'server':
// return $wgServer;
// case 'servername':
// return $wgServerName;
// case 'scriptpath':
// return $wgScriptPath;
// case 'stylepath':
// return $wgStylePath;
// case 'directionmark':
// return $pageLang->getDirMark();
// case 'contentlanguage':
// global $wgLanguageCode;
// return $wgLanguageCode;
// case 'pagelanguage':
// $value = $pageLang->getCode();
// break;
// case 'cascadingsources':
// $value = CoreParserFunctions::cascadingsources($this);
// break;
// default:
// $ret = null;
// Hooks::run(
// 'ParserGetVariableValueSwitch',
// [ &$this, &this.mVarCache, &$index, &$ret, &$frame ]
// );
//
// return $ret;
// }
//
// if ($index) {
// this.mVarCache[$index] = $value;
// }
//
// return $value;
// }
//
// /**
// * initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers
// *
// * @private
// */
// public function initialiseVariables() {
// $variableIDs = MagicWord::getVariableIDs();
// $substIDs = MagicWord::getSubstIDs();
//
// this.mVariables = new MagicWordArray($variableIDs);
// this.mSubstWords = new MagicWordArray($substIDs);
// }
//
// /**
// * Preprocess some wikitext and return the document tree.
// * This is the ghost of replace_variables().
// *
// * @param String $text The text to parse
// * @param int $flags Bitwise combination of:
// * - self::PTD_FOR_INCLUSION: Handle "
" and "" as if the text is being
// * included. Default is to assume a direct page view.
// *
// * The generated DOM tree must depend only on the input text and the flags.
// * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of T6899.
// *
// * Any flag added to the $flags parameter here, or any other parameter liable to cause a
// * change in the DOM tree for a given text, must be passed through the section identifier
// * in the section edit link and thus back to extractSections().
// *
// * The output of this function is currently only cached in process memory, but a persistent
// * cache may be implemented at a later date which takes further advantage of these strict
// * dependency requirements.
// *
// * @return PPNode
// */
// public function preprocessToDom($text, $flags = 0) {
// $dom = this.getPreprocessor()->preprocessToObj($text, $flags);
// return $dom;
// }
//
// /**
// * Return a three-element array: leading whitespace, String contents, trailing whitespace
// *
// * @param String $s
// *
// * @return array
// */
// public static function splitWhitespace($s) {
// $ltrimmed = ltrim($s);
// $w1 = substr($s, 0, strlen($s) - strlen($ltrimmed));
// $trimmed = rtrim($ltrimmed);
// $diff = strlen($ltrimmed) - strlen($trimmed);
// if ($diff > 0) {
// $w2 = substr($ltrimmed, -$diff);
// } else {
// $w2 = '';
// }
// return [ $w1, $trimmed, $w2 ];
// }
//
// /**
// * Replace magic variables, templates, and template arguments
// * with the appropriate text. Templates are substituted recursively,
// * taking care to avoid infinite loops.
// *
// * Note that the substitution depends on value of $mOutputType:
// * self::OT_WIKI: only {{subst:}} templates
// * self::OT_PREPROCESS: templates but not extension tags
// * self::OT_HTML: all templates and extension tags
// *
// * @param String $text The text to transform
// * @param boolean|PPFrame $frame Object describing the arguments passed to the
// * template. Arguments may also be provided as an associative array, as
// * was the usual case before MW1.12. Providing arguments this way may be
// * useful for extensions wishing to perform variable replacement
// * explicitly.
// * @param boolean $argsOnly Only do argument (triple-brace) expansion, not
// * double-brace expansion.
// * @return String
// */
// public function replaceVariables($text, $frame = false, $argsOnly = false) {
// # Is there any text? Also, Prevent too big inclusions!
// $textSize = strlen($text);
// if ($textSize < 1 || $textSize > this.mOptions->getMaxIncludeSize()) {
// return $text;
// }
//
// if ($frame === false) {
// $frame = this.getPreprocessor()->newFrame();
// } elseif (!($frame instanceof PPFrame)) {
// wfDebug(__METHOD__ . " called using plain parameters instead of "
// . "a PPFrame instance. Creating custom frame.\n");
// $frame = this.getPreprocessor()->newCustomFrame($frame);
// }
//
// $dom = this.preprocessToDom($text);
// $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
// $text = $frame->expand($dom, $flags);
//
// return $text;
// }
//
// /**
// * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
// *
// * @param array $args
// *
// * @return array
// */
// public static function createAssocArgs($args) {
// $assocArgs = [];
// $index = 1;
// foreach ($args as $arg) {
// $eqpos = strpos($arg, '=');
// if ($eqpos === false) {
// $assocArgs[$index++] = $arg;
// } else {
// $name = trim(substr($arg, 0, $eqpos));
// $value = trim(substr($arg, $eqpos + 1));
// if ($value === false) {
// $value = '';
// }
// if ($name !== false) {
// $assocArgs[$name] = $value;
// }
// }
// }
//
// return $assocArgs;
// }
//
// /**
// * Warn the user when a parser limitation is reached
// * Will warn at most once the user per limitation type
// *
// * The results are shown during preview and run through the Parser (See EditPage.php)
// *
// * @param String $limitationType Should be one of:
// * 'expensive-parserfunction' (corresponding messages:
// * 'expensive-parserfunction-warning',
// * 'expensive-parserfunction-category')
// * 'post-expand-template-argument' (corresponding messages:
// * 'post-expand-template-argument-warning',
// * 'post-expand-template-argument-category')
// * 'post-expand-template-inclusion' (corresponding messages:
// * 'post-expand-template-inclusion-warning',
// * 'post-expand-template-inclusion-category')
// * 'node-count-exceeded' (corresponding messages:
// * 'node-count-exceeded-warning',
// * 'node-count-exceeded-category')
// * 'expansion-depth-exceeded' (corresponding messages:
// * 'expansion-depth-exceeded-warning',
// * 'expansion-depth-exceeded-category')
// * @param String|int|null $current Current value
// * @param String|int|null $max Maximum allowed, when an explicit limit has been
// * exceeded, provide the values (optional)
// */
// public function limitationWarn($limitationType, $current = '', $max = '') {
// # does no harm if $current and $max are present but are unnecessary for the message
// # Not doing ->inLanguage(this.mOptions->getUserLangObj()), since this is shown
// # only during preview, and that would split the parser cache unnecessarily.
// $warning = wfMessage("$limitationType-warning")->numParams($current, $max)
// ->text();
// this.mOutput->addWarning($warning);
// this.addTrackingCategory("$limitationType-category");
// }
//
// /**
// * Return the text of a template, after recursively
// * replacing any variables or templates within the template.
// *
// * @param array $piece The parts of the template
// * $piece['title']: the title, i.e. the part before the |
// * $piece['parts']: the parameter array
// * $piece['lineStart']: whether the brace was at the start of a line
// * @param PPFrame $frame The current frame, contains template arguments
// * @throws Exception
// * @return String The text of the template
// */
// public function braceSubstitution($piece, $frame) {
//
// // Flags
//
// // $text has been filled
// $found = false;
// // wiki markup in $text should be escaped
// $nowiki = false;
// // $text is HTML, armour it against wikitext transformation
// $isHTML = false;
// // Force interwiki transclusion to be done in raw mode not rendered
// $forceRawInterwiki = false;
// // $text is a DOM node needing expansion in a child frame
// $isChildObj = false;
// // $text is a DOM node needing expansion in the current frame
// $isLocalObj = false;
//
// # Title Object, where $text came from
// $title = false;
//
// # $part1 is the bit before the first |, and must contain only title characters.
// # Various prefixes will be stripped from it later.
// $titleWithSpaces = $frame->expand($piece['title']);
// $part1 = trim($titleWithSpaces);
// $titleText = false;
//
// # Original title text preserved for various purposes
// $originalTitle = $part1;
//
// # $args is a list of argument nodes, starting from index 0, not including $part1
// # @todo FIXME: If piece['parts'] is null then the call to getLength()
// # below won't work b/c this $args isn't an Object
// $args = (null == $piece['parts']) ? [] : $piece['parts'];
//
// $profileSection = null; // profile templates
//
// # SUBST
// if (!$found) {
// $substMatch = this.mSubstWords->matchStartAndRemove($part1);
//
// # Possibilities for substMatch: "subst", "safesubst" or FALSE
// # Decide whether to expand template or keep wikitext as-is.
// if (this.ot['wiki']) {
// if ($substMatch === false) {
// $literal = true; # literal when in PST with no prefix
// } else {
// $literal = false; # expand when in PST with subst: or safesubst:
// }
// } else {
// if ($substMatch == 'subst') {
// $literal = true; # literal when not in PST with plain subst:
// } else {
// $literal = false; # expand when not in PST with safesubst: or no prefix
// }
// }
// if ($literal) {
// $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
// $isLocalObj = true;
// $found = true;
// }
// }
//
// # Variables
// if (!$found && $args->getLength() == 0) {
// $id = this.mVariables->matchStartToEnd($part1);
// if ($id !== false) {
// $text = this.getVariableValue($id, $frame);
// if (MagicWord::getCacheTTL($id) > -1) {
// this.mOutput->updateCacheExpiry(MagicWord::getCacheTTL($id));
// }
// $found = true;
// }
// }
//
// # MSG, MSGNW and RAW
// if (!$found) {
// # Check for MSGNW:
// $mwMsgnw = MagicWord::get('msgnw');
// if ($mwMsgnw->matchStartAndRemove($part1)) {
// $nowiki = true;
// } else {
// # Remove obsolete MSG:
// $mwMsg = MagicWord::get('msg');
// $mwMsg->matchStartAndRemove($part1);
// }
//
// # Check for RAW:
// $mwRaw = MagicWord::get('raw');
// if ($mwRaw->matchStartAndRemove($part1)) {
// $forceRawInterwiki = true;
// }
// }
//
// # Parser functions
// if (!$found) {
// $colonPos = strpos($part1, ':');
// if ($colonPos !== false) {
// $func = substr($part1, 0, $colonPos);
// $funcArgs = [ trim(substr($part1, $colonPos + 1)) ];
// $argsLength = $args->getLength();
// for ($i = 0; $i < $argsLength; $i++) {
// $funcArgs[] = $args->item($i);
// }
// try {
// $result = this.callParserFunction($frame, $func, $funcArgs);
// } catch (Exception $ex) {
// throw $ex;
// }
//
// # The interface for parser functions allows for extracting
// # flags into the local scope. Extract any forwarded flags
// # here.
// extract($result);
// }
// }
//
// # Finish mangling title and then check for loops.
// # Set $title to a Title Object and $titleText to the PDBK
// if (!$found) {
// $ns = NS_TEMPLATE;
// # Split the title into page and subpage
// $subpage = '';
// $relative = this.maybeDoSubpageLink($part1, $subpage);
// if ($part1 !== $relative) {
// $part1 = $relative;
// $ns = this.mTitle->getNamespace();
// }
// $title = Title::newFromText($part1, $ns);
// if ($title) {
// $titleText = $title->getPrefixedText();
// # Check for language variants if the template is not found
// if (this.getConverterLanguage()->hasVariants() && $title->getArticleID() == 0) {
// this.getConverterLanguage()->findVariantLink($part1, $title, true);
// }
// # Do recursion depth check
// $limit = this.mOptions->getMaxTemplateDepth();
// if ($frame->depth >= $limit) {
// $found = true;
// $text = ''
// . wfMessage('parser-template-recursion-depth-warning')
// ->numParams($limit)->inContentLanguage()->text()
// . '';
// }
// }
// }
//
// # Load from database
// if (!$found && $title) {
// $profileSection = this.mProfiler->scopedProfileIn($title->getPrefixedDBkey());
// if (!$title->isExternal()) {
// if ($title->isSpecialPage()
// && this.mOptions->getAllowSpecialInclusion()
// && this.ot['html']
// ) {
// $specialPage = SpecialPageFactory::getPage($title->getDBkey());
// // Pass the template arguments as URL parameters.
// // "uselang" will have no effect since the Language Object
// // is forced to the one defined in ParserOptions.
// $pageArgs = [];
// $argsLength = $args->getLength();
// for ($i = 0; $i < $argsLength; $i++) {
// $bits = $args->item($i)->splitArg();
// if (strval($bits['index']) === '') {
// $name = trim($frame->expand($bits['name'], PPFrame::STRIP_COMMENTS));
// $value = trim($frame->expand($bits['value']));
// $pageArgs[$name] = $value;
// }
// }
//
// // Create a new context to execute the special page
// $context = new RequestContext;
// $context->setTitle($title);
// $context->setRequest(new FauxRequest($pageArgs));
// if ($specialPage && $specialPage->maxIncludeCacheTime() === 0) {
// $context->setUser(this.getUser());
// } else {
// // If this page is cached, then we better not be per user.
// $context->setUser(User::newFromName('127.0.0.1', false));
// }
// $context->setLanguage(this.mOptions->getUserLangObj());
// $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->updateRuntimeAdaptiveExpiry(
// $specialPage->maxIncludeCacheTime()
// );
// }
// }
// } elseif (MWNamespace::isNonincludable($title->getNamespace())) {
// $found = false; # access denied
// wfDebug(__METHOD__ . ": template inclusion denied for " .
// $title->getPrefixedDBkey() . "\n");
// } else {
// list($text, $title) = this.getTemplateDom($title);
// if ($text !== false) {
// $found = true;
// $isChildObj = true;
// }
// }
//
// # If the title is valid but undisplayable, make a link to it
// if (!$found && (this.ot['html'] || this.ot['pre'])) {
// $text = "[[:$titleText]]";
// $found = true;
// }
// } elseif ($title->isTrans()) {
// # Interwiki transclusion
// if (this.ot['html'] && !$forceRawInterwiki) {
// $text = this.interwikiTransclude($title, 'render');
// $isHTML = true;
// } else {
// $text = this.interwikiTransclude($title, 'raw');
// # Preprocess it like a template
// $text = this.preprocessToDom($text, self::PTD_FOR_INCLUSION);
// $isChildObj = true;
// }
// $found = true;
// }
//
// # Do infinite loop check
// # This has to be done after redirect resolution to avoid infinite loops via redirects
// if (!$frame->loopCheck($title)) {
// $found = true;
// $text = ''
// . wfMessage('parser-template-loop-warning', $titleText)->inContentLanguage()->text()
// . '';
// wfDebug(__METHOD__ . ": template loop broken at '$titleText'\n");
// }
// }
//
// # If we haven't found text to substitute by now, we're done
// # Recover the source wikitext and return it
// if (!$found) {
// $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
// if ($profileSection) {
// this.mProfiler->scopedProfileOut($profileSection);
// }
// return [ 'Object' => $text ];
// }
//
// # Expand DOM-style return values in a child frame
// if ($isChildObj) {
// # Clean up argument array
// $newFrame = $frame->newChild($args, $title);
//
// if ($nowiki) {
// $text = $newFrame->expand($text, PPFrame::RECOVER_ORIG);
// } elseif ($titleText !== false && $newFrame->isEmpty()) {
// # Expansion is eligible for the empty-frame cache
// $text = $newFrame->cachedExpand($titleText, $text);
// } else {
// # Uncached expansion
// $text = $newFrame->expand($text);
// }
// }
// if ($isLocalObj && $nowiki) {
// $text = $frame->expand($text, PPFrame::RECOVER_ORIG);
// $isLocalObj = false;
// }
//
// if ($profileSection) {
// this.mProfiler->scopedProfileOut($profileSection);
// }
//
// # Replace raw HTML by a placeholder
// if ($isHTML) {
// $text = this.insertStripItem($text);
// } elseif ($nowiki && (this.ot['html'] || this.ot['pre'])) {
// # Escape nowiki-style return values
// $text = wfEscapeWikiText($text);
// } elseif (is_string($text)
// && !$piece['lineStart']
// && preg_match('/^(?:{\\||:|;|#|\*)/', $text)
// ) {
// # T2529: if the template begins with a table or block-level
// # element, it should be treated as beginning a new line.
// # This behavior is somewhat controversial.
// $text = "\n" . $text;
// }
//
// if (is_string($text) && !this.incrementIncludeSize('post-expand', strlen($text))) {
// # Error, oversize inclusion
// if ($titleText !== false) {
// # Make a working, properly escaped link if possible (T25588)
// $text = "[[:$titleText]]";
// } else {
// # This will probably not be a working link, but at least it may
// # provide some hint of where the problem is
// preg_replace('/^:/', '', $originalTitle);
// $text = "[[:$originalTitle]]";
// }
// $text .= this.insertStripItem('');
// this.limitationWarn('post-expand-template-inclusion');
// }
//
// if ($isLocalObj) {
// $ret = [ 'Object' => $text ];
// } else {
// $ret = [ 'text' => $text ];
// }
//
// return $ret;
// }
//
// /**
// * Call a parser function and return an array with text and flags.
// *
// * The returned array will always contain a boolean 'found', indicating
// * whether the parser function was found or not. It may also contain the
// * following:
// * text: String|Object, resulting wikitext or PP DOM Object
// * isHTML: boolean, $text is HTML, armour it against wikitext transformation
// * isChildObj: boolean, $text is a DOM node needing expansion in a child frame
// * isLocalObj: boolean, $text is a DOM node needing expansion in the current frame
// * nowiki: boolean, wiki markup in $text should be escaped
// *
// * @since 1.21
// * @param PPFrame $frame The current frame, contains template arguments
// * @param String $function Function name
// * @param array $args Arguments to the function
// * @throws MWException
// * @return array
// */
// public function callParserFunction($frame, $function, array $args = []) {
// global $wgContLang;
//
// # Case sensitive functions
// if (isset(this.mFunctionSynonyms[1][$function])) {
// $function = this.mFunctionSynonyms[1][$function];
// } else {
// # Case insensitive functions
// $function = $wgContLang->lc($function);
// if (isset(this.mFunctionSynonyms[0][$function])) {
// $function = this.mFunctionSynonyms[0][$function];
// } else {
// return [ 'found' => false ];
// }
// }
//
// list($callback, $flags) = this.mFunctionHooks[$function];
//
// # Workaround for PHP bug 35229 and similar
// if (!is_callable($callback)) {
// throw new MWException("Tag hook for $function is not callable\n");
// }
//
// $allArgs = [ &$this ];
// if ($flags & self::SFH_OBJECT_ARGS) {
// # Convert arguments to PPNodes and collect for appending to $allArgs
// $funcArgs = [];
// foreach ($args as $k => $v) {
// if ($v instanceof PPNode || $k === 0) {
// $funcArgs[] = $v;
// } else {
// $funcArgs[] = this.mPreprocessor->newPartNodeArray([ $k => $v ])->item(0);
// }
// }
//
// # Add a frame parameter, and pass the arguments as an array
// $allArgs[] = $frame;
// $allArgs[] = $funcArgs;
// } else {
// # Convert arguments to plain text and append to $allArgs
// foreach ($args as $k => $v) {
// if ($v instanceof PPNode) {
// $allArgs[] = trim($frame->expand($v));
// } elseif (is_int($k) && $k >= 0) {
// $allArgs[] = trim($v);
// } else {
// $allArgs[] = trim("$k=$v");
// }
// }
// }
//
// $result = call_user_func_array($callback, $allArgs);
//
// # The interface for function hooks allows them to return a wikitext
// # String or an array containing the String and any flags. This mungs
// # things around to match what this method should return.
// if (!is_array($result)) {
// $result =[
// 'found' => true,
// 'text' => $result,
// ];
// } else {
// if (isset($result[0]) && !isset($result['text'])) {
// $result['text'] = $result[0];
// }
// unset($result[0]);
// $result += [
// 'found' => true,
// ];
// }
//
// $noparse = true;
// $preprocessFlags = 0;
// if (isset($result['noparse'])) {
// $noparse = $result['noparse'];
// }
// if (isset($result['preprocessFlags'])) {
// $preprocessFlags = $result['preprocessFlags'];
// }
//
// if (!$noparse) {
// $result['text'] = this.preprocessToDom($result['text'], $preprocessFlags);
// $result['isChildObj'] = true;
// }
//
// return $result;
// }
//
// /**
// * Get the semi-parsed DOM representation of a template with a given title,
// * and its redirect destination title. Cached.
// *
// * @param Title $title
// *
// * @return array
// */
// public function getTemplateDom($title) {
// $cacheTitle = $title;
// $titleText = $title->getPrefixedDBkey();
//
// if (isset(this.mTplRedirCache[$titleText])) {
// list($ns, $dbk) = this.mTplRedirCache[$titleText];
// $title = Title::makeTitle($ns, $dbk);
// $titleText = $title->getPrefixedDBkey();
// }
// if (isset(this.mTplDomCache[$titleText])) {
// return [ this.mTplDomCache[$titleText], $title ];
// }
//
// # Cache miss, go to the database
// list($text, $title) = this.fetchTemplateAndTitle($title);
//
// if ($text === false) {
// this.mTplDomCache[$titleText] = false;
// return [ false, $title ];
// }
//
// $dom = this.preprocessToDom($text, self::PTD_FOR_INCLUSION);
// this.mTplDomCache[$titleText] = $dom;
//
// if (!$title->equals($cacheTitle)) {
// this.mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
// [ $title->getNamespace(), $cdb = $title->getDBkey() ];
// }
//
// return [ $dom, $title ];
// }
//
// /**
// * Fetch the current revision of a given title. Note that the revision
// * (and even the title) may not exist in the database, so everything
// * contributing to the output of the parser should use this method
// * where possible, rather than getting the revisions themselves. This
// * method also caches its results, so using it benefits performance.
// *
// * @since 1.24
// * @param Title $title
// * @return Revision
// */
// public function fetchCurrentRevisionOfTitle($title) {
// $cacheKey = $title->getPrefixedDBkey();
// if (!this.currentRevisionCache) {
// this.currentRevisionCache = new MapCacheLRU(100);
// }
// if (!this.currentRevisionCache->has($cacheKey)) {
// this.currentRevisionCache->set($cacheKey,
// // Defaults to Parser::statelessFetchRevision()
// call_user_func(this.mOptions->getCurrentRevisionCallback(), $title, $this)
// );
// }
// return this.currentRevisionCache->get($cacheKey);
// }
//
// /**
// * Wrapper around Revision::newFromTitle to allow passing additional parameters
// * without passing them on to it.
// *
// * @since 1.24
// * @param Title $title
// * @param Parser|boolean $parser
// * @return Revision|boolean False if missing
// */
// 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;
// }
//
// /**
// * Fetch the unparsed text of a template and register a reference to it.
// * @param Title $title
// * @return array (String or false, Title)
// */
// public function fetchTemplateAndTitle($title) {
// // Defaults to Parser::statelessFetchTemplate()
// $templateCb = this.mOptions->getTemplateCallback();
// $stuff = call_user_func($templateCb, $title, $this);
// // We use U+007F DELETE to distinguish strip markers from regular text.
// $text = $stuff['text'];
// if (is_string($stuff['text'])) {
// $text = strtr($text, "\x7f", "?");
// }
// $finalTitle = isset($stuff['finalTitle']) ? $stuff['finalTitle'] : $title;
// if (isset($stuff['deps'])) {
// foreach ($stuff['deps'] as $dep) {
// this.mOutput->addTemplate($dep['title'], $dep['page_id'], $dep['rev_id']);
// if ($dep['title']->equals(this.getTitle())) {
// // If we transclude ourselves, the final result
// // will change based on the new version of the page
// this.mOutput->setFlag('vary-revision');
// }
// }
// }
// return [ $text, $finalTitle ];
// }
//
// /**
// * Fetch the unparsed text of a template and register a reference to it.
// * @param Title $title
// * @return String|boolean
// */
// public function fetchTemplate($title) {
// return this.fetchTemplateAndTitle($title)[0];
// }
//
// /**
// * Static function to get a template
// * Can be overridden via ParserOptions::setTemplateCallback().
// *
// * @param Title $title
// * @param boolean|Parser $parser
// *
// * @return array
// */
// public static function statelessFetchTemplate($title, $parser = false) {
// $text = $skip = false;
// $finalTitle = $title;
// $deps = [];
//
// # Loop to fetch the article, with up to 1 redirect
// // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
// for ($i = 0; $i < 2 && is_object($title); $i++) {
// // @codingStandardsIgnoreEnd
// # Give extensions a chance to select the revision instead
// $id = false; # Assume current
// Hooks::run('BeforeParserFetchTemplateAndtitle',
// [ $parser, $title, &$skip, &$id ]);
//
// if ($skip) {
// $text = false;
// $deps[] = [
// 'title' => $title,
// 'page_id' => $title->getArticleID(),
// 'rev_id' => null
// ];
// break;
// }
// # Get the revision
// if ($id) {
// $rev = Revision::newFromId($id);
// } elseif ($parser) {
// $rev = $parser->fetchCurrentRevisionOfTitle($title);
// } else {
// $rev = Revision::newFromTitle($title);
// }
// $rev_id = $rev ? $rev->getId() : 0;
// # If there is no current revision, there is no page
// if ($id === false && !$rev) {
// $linkCache = LinkCache::singleton();
// $linkCache->addBadLinkObj($title);
// }
//
// $deps[] = [
// 'title' => $title,
// 'page_id' => $title->getArticleID(),
// 'rev_id' => $rev_id ];
// if ($rev && !$title->equals($rev->getTitle())) {
// # We fetched a rev from a different title; register it too...
// $deps[] = [
// 'title' => $rev->getTitle(),
// 'page_id' => $rev->getPage(),
// 'rev_id' => $rev_id ];
// }
//
// if ($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;
// }
// } elseif ($title->getNamespace() == NS_MEDIAWIKI) {
// global $wgContLang;
// $message = wfMessage($wgContLang->lcfirst($title->getText()))->inContentLanguage();
// if (!$message->exists()) {
// $text = false;
// break;
// }
// $content = $message->content();
// $text = $message->plain();
// } else {
// break;
// }
// if (!$content) {
// break;
// }
// # Redirect?
// $finalTitle = $title;
// $title = $content->getRedirectTarget();
// }
// return [
// 'text' => $text,
// 'finalTitle' => $finalTitle,
// 'deps' => $deps ];
// }
//
// /**
// * Fetch a file and its title and register a reference to it.
// * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
// * @param Title $title
// * @param array $options Array of options to RepoGroup::findFile
// * @return File|boolean
// */
// public function fetchFile($title, $options = []) {
// return this.fetchFileAndTitle($title, $options)[0];
// }
//
// /**
// * Fetch a file and its title and register a reference to it.
// * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
// * @param Title $title
// * @param array $options Array of options to RepoGroup::findFile
// * @return array (File or false, Title of file)
// */
// public function fetchFileAndTitle($title, $options = []) {
// $file = this.fetchFileNoRegister($title, $options);
//
// $time = $file ? $file->getTimestamp() : false;
// $sha1 = $file ? $file->getSha1() : false;
// # Register the file as a dependency...
// this.mOutput->addImage($title->getDBkey(), $time, $sha1);
// if ($file && !$title->equals($file->getTitle())) {
// # Update fetched file title
// $title = $file->getTitle();
// this.mOutput->addImage($title->getDBkey(), $time, $sha1);
// }
// return [ $file, $title ];
// }
//
// /**
// * Helper function for fetchFileAndTitle.
// *
// * Also useful if you need to fetch a file but not use it yet,
// * for example to get the file's handler.
// *
// * @param Title $title
// * @param array $options Array of options to RepoGroup::findFile
// * @return File|boolean
// */
// protected function fetchFileNoRegister($title, $options = []) {
// if (isset($options['broken'])) {
// $file = false; // broken thumbnail forced by hook
// } elseif (isset($options['sha1'])) { // get by (sha1,timestamp)
// $file = RepoGroup::singleton()->findFileFromKey($options['sha1'], $options);
// } else { // get by (name,timestamp)
// $file = wfFindFile($title, $options);
// }
// return $file;
// }
//
// /**
// * Transclude an interwiki link.
// *
// * @param Title $title
// * @param String $action
// *
// * @return String
// */
// public function interwikiTransclude($title, $action) {
// global $wgEnableScaryTranscluding;
//
// if (!$wgEnableScaryTranscluding) {
// return wfMessage('scarytranscludedisabled')->inContentLanguage()->text();
// }
//
// $url = $title->getFullURL([ 'action' => $action ]);
//
// if (strlen($url) > 255) {
// return wfMessage('scarytranscludetoolong')->inContentLanguage()->text();
// }
// return this.fetchScaryTemplateMaybeFromCache($url);
// }
//
// /**
// * @param String $url
// * @return mixed|String
// */
// public function fetchScaryTemplateMaybeFromCache($url) {
// global $wgTranscludeCacheExpiry;
// $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) ]);
// if ($obj) {
// return $obj->tc_contents;
// }
//
// $req = MWHttpRequest::factory($url, [], __METHOD__);
// $status = $req->execute(); // Status Object
// if ($status->isOK()) {
// $text = $req->getContent();
// } elseif ($req->getStatus() != 200) {
// // Though we failed to fetch the content, this status is useless.
// return wfMessage('scarytranscludefailed-httpstatus')
// ->params($url, $req->getStatus() /* HTTP status */)->inContentLanguage()->text();
// } else {
// return wfMessage('scarytranscludefailed', $url)->inContentLanguage()->text();
// }
//
// $dbw = wfGetDB(DB_MASTER);
// $dbw->replace('transcache', [ 'tc_url' ], [
// 'tc_url' => $url,
// 'tc_time' => $dbw->timestamp(time()),
// 'tc_contents' => $text
// ]);
// return $text;
// }
//
// /**
// * Triple brace replacement -- used for template arguments
// * @private
// *
// * @param array $piece
// * @param PPFrame $frame
// *
// * @return array
// */
// public function argSubstitution($piece, $frame) {
//
// $error = false;
// $parts = $piece['parts'];
// $nameWithSpaces = $frame->expand($piece['title']);
// $argName = trim($nameWithSpaces);
// $Object = false;
// $text = $frame->getArgument($argName);
// if ($text === false && $parts->getLength() > 0
// && (this.ot['html']
// || this.ot['pre']
// || (this.ot['wiki'] && $frame->isTemplate())
// )
// ) {
// # No match in frame, use the supplied default
// $Object = $parts->item(0)->getChildren();
// }
// if (!this.incrementIncludeSize('arg', strlen($text))) {
// $error = '';
// this.limitationWarn('post-expand-template-argument');
// }
//
// if ($text === false && $Object === false) {
// # No match anywhere
// $Object = $frame->virtualBracketedImplode('{{{', '|', '}}}', $nameWithSpaces, $parts);
// }
// if ($error !== false) {
// $text .= $error;
// }
// if ($Object !== false) {
// $ret = [ 'Object' => $Object ];
// } else {
// $ret = [ 'text' => $text ];
// }
//
// return $ret;
// }
//
// /**
// * Return the text to be used for a given extension tag.
// * This is the ghost of strip().
// *
// * @param array $params Associative array of parameters:
// * name PPNode for the tag name
// * attr PPNode for unparsed text where tag attributes are thought to be
// * attributes Optional associative array of parsed attributes
// * inner Contents of extension element
// * noClose Original text did not have a close tag
// * @param PPFrame $frame
// *
// * @throws MWException
// * @return String
// */
// public function extensionSubstitution($params, $frame) {
// static $errorStr = '';
// 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;
//
// $isFunctionTag = isset(this.mFunctionTagHooks[strtolower($name)]) &&
// (this.ot['html'] || this.ot['pre']);
// if ($isFunctionTag) {
// $markerType = 'none';
// } else {
// $markerType = 'general';
// }
// if (this.ot['html'] || $isFunctionTag) {
// $name = strtolower($name);
// $attributes = Sanitizer::decodeTagAttributes($attrText);
// if (isset($params['attributes'])) {
// $attributes = $attributes + $params['attributes'];
// }
//
// if (isset(this.mTagHooks[$name])) {
// # Workaround for PHP bug 35229 and similar
// if (!is_callable(this.mTagHooks[$name])) {
// throw new MWException("Tag hook for $name is not callable\n");
// }
// $output = call_user_func_array(this.mTagHooks[$name],
// [ $content, $attributes, $this, $frame ]);
// } elseif (isset(this.mFunctionTagHooks[$name])) {
// list($callback,) = this.mFunctionTagHooks[$name];
// if (!is_callable($callback)) {
// throw new MWException("Tag hook for $name is not callable\n");
// }
//
// $output = call_user_func_array($callback, [ &$this, $frame, $content, $attributes ]);
// } else {
// $output = 'Invalid tag extension name: ' .
// htmlspecialchars($name) . '';
// }
//
// if (is_array($output)) {
// # Extract flags to local scope (to override $markerType)
// $flags = $output;
// $output = $flags[0];
// unset($flags[0]);
// extract($flags);
// }
// } else {
// if (is_null($attrText)) {
// $attrText = '';
// }
// if (isset($params['attributes'])) {
// foreach ($params['attributes'] as $attrName => $attrValue) {
// $attrText .= ' ' . htmlspecialchars($attrName) . '="' .
// htmlspecialchars($attrValue) . '"';
// }
// }
// if ($content === null) {
// $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";
// }
// }
//
// if ($markerType === 'none') {
// return $output;
// } elseif ($markerType === 'nowiki') {
// this.mStripState->addNoWiki($marker, $output);
// } elseif ($markerType === 'general') {
// this.mStripState->addGeneral($marker, $output);
// } else {
// throw new MWException(__METHOD__ . ': invalid marker type');
// }
// return $marker;
// }
//
// /**
// * Increment an include size counter
// *
// * @param String $type The type of expansion
// * @param int $size The size of the text
// * @return boolean False if this inclusion would take it over the maximum, true otherwise
// */
// public function incrementIncludeSize($type, $size) {
// if (this.mIncludeSizes[$type] + $size > this.mOptions->getMaxIncludeSize()) {
// return false;
// } else {
// this.mIncludeSizes[$type] += $size;
// return true;
// }
// }
//
// /**
// * Increment the expensive function count
// *
// * @return boolean False if the limit has been exceeded
// */
// public function incrementExpensiveFunctionCount() {
// this.mExpensiveFunctionCount++;
// return this.mExpensiveFunctionCount <= this.mOptions->getExpensiveParserFunctionLimit();
// }
//
// /**
// * Strip double-underscore items like __NOGALLERY__ and __NOTOC__
// * Fills this.mDoubleUnderscores, returns the modified text
// *
// * @param String $text
// *
// * @return String
// */
// public function doDoubleUnderscore($text) {
//
// # The position of __TOC__ needs to be recorded
// $mw = MagicWord::get('toc');
// if ($mw->match($text)) {
// this.mShowToc = true;
// this.mForceTocPosition = true;
//
// # Set a placeholder. At the end we'll fill it in with the TOC.
// $text = $mw->replace('', $text, 1);
//
// # Only keep the first one.
// $text = $mw->replace('', $text);
// }
//
// # Now match and remove the rest of them
// $mwa = MagicWord::getDoubleUnderscoreArray();
// this.mDoubleUnderscores = $mwa->matchAndRemove($text);
//
// if (isset(this.mDoubleUnderscores['nogallery'])) {
// this.mOutput->mNoGallery = true;
// }
// if (isset(this.mDoubleUnderscores['notoc']) && !this.mForceTocPosition) {
// this.mShowToc = false;
// }
// if (isset(this.mDoubleUnderscores['hiddencat'])
// && this.mTitle->getNamespace() == NS_CATEGORY
// ) {
// this.addTrackingCategory('hidden-category-category');
// }
// # (T10068) Allow control over whether robots index a page.
// # __INDEX__ always overrides __NOINDEX__, see T16899
// if (isset(this.mDoubleUnderscores['noindex']) && this.mTitle->canUseNoindex()) {
// this.mOutput->setIndexPolicy('noindex');
// this.addTrackingCategory('noindex-category');
// }
// if (isset(this.mDoubleUnderscores['index']) && this.mTitle->canUseNoindex()) {
// this.mOutput->setIndexPolicy('index');
// this.addTrackingCategory('index-category');
// }
//
// # Cache all double underscores in the database
// foreach (this.mDoubleUnderscores as $key => $val) {
// this.mOutput->setProperty($key, '');
// }
//
// return $text;
// }
//
// /**
// * @see ParserOutput::addTrackingCategory()
// * @param String $msg Message key
// * @return boolean Whether the addition was successful
// */
// public function addTrackingCategory($msg) {
// return this.mOutput->addTrackingCategory($msg, this.mTitle);
// }
//
// /**
// * This function accomplishes several tasks:
// * 1) Auto-number headings if that option is enabled
// * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page
// * 3) Add a Table of contents on the top for users who have enabled the option
// * 4) Auto-anchor headings
// *
// * It loops through all headlines, collects the necessary data, then splits up the
// * String and re-inserts the newly formatted headlines.
// *
// * @param String $text
// * @param String $origText Original, untouched wikitext
// * @param boolean $isMain
// * @return mixed|String
// * @private
// */
// public function formatHeadings($text, $origText, $isMain = true) {
// global $wgMaxTocLevel, $wgExperimentalHtmlIds;
//
// # Inhibit editsection links if requested in the page
// if (isset(this.mDoubleUnderscores['noeditsection'])) {
// $maybeShowEditLink = $showEditLink = false;
// } else {
// $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
// $showEditLink = this.mOptions->getEditSection();
// }
// if ($showEditLink) {
// this.mOutput->setEditSectionTokens(true);
// }
//
// # Get all headlines for numbering them and adding funky stuff like [edit]
// # links - this is for later, but we need the number of headlines right now
// $matches = [];
// $numMatches = preg_match_all(
// '/[1-6])(?P.*?>)\s*(?P[\s\S]*?)\s*<\/H[1-6] *>/i',
// $text,
// $matches
// );
//
// # if there are fewer than 4 headlines in the article, do not show TOC
// # unless it's been explicitly enabled.
// $enoughToc = this.mShowToc &&
// (($numMatches >= 4) || this.mForceTocPosition);
//
// # Allow user to stipulate that a page should have a "new section"
// # link added via __NEWSECTIONLINK__
// if (isset(this.mDoubleUnderscores['newsectionlink'])) {
// this.mOutput->setNewSection(true);
// }
//
// # Allow user to remove the "new section"
// # link via __NONEWSECTIONLINK__
// if (isset(this.mDoubleUnderscores['nonewsectionlink'])) {
// this.mOutput->hideNewSection(true);
// }
//
// # if the String __FORCETOC__ (not case-sensitive) occurs in the HTML,
// # override above conditions and always show TOC above first header
// if (isset(this.mDoubleUnderscores['forcetoc'])) {
// this.mShowToc = true;
// $enoughToc = true;
// }
//
// # headline counter
// $headlineCount = 0;
// $numVisible = 0;
//
// # Ugh .. the TOC should have neat indentation levels which can be
// # passed to the skin functions. These are determined here
// $toc = '';
// $full = '';
// $head = [];
// $sublevelCount = [];
// $levelCount = [];
// $level = 0;
// $prevlevel = 0;
// $toclevel = 0;
// $prevtoclevel = 0;
// $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
// $baseTitleText = this.mTitle->getPrefixedDBkey();
// $oldType = this.mOutputType;
// this.setOutputType(self::OT_WIKI);
// $frame = this.getPreprocessor()->newFrame();
// $root = this.preprocessToDom($origText);
// $node = $root->getFirstChild();
// $byteOffset = 0;
// $tocraw = [];
// $refers = [];
//
// $headlines = $numMatches !== false ? $matches[3] : [];
//
// foreach ($headlines as $headline) {
// $isTemplate = false;
// $titleText = false;
// $sectionIndex = false;
// $numbering = '';
// $markerMatches = [];
// if (preg_match("/^$markerRegex/", $headline, $markerMatches)) {
// $serial = $markerMatches[1];
// list($titleText, $sectionIndex) = this.mHeadings[$serial];
// $isTemplate = ($titleText != $baseTitleText);
// $headline = preg_replace("/^$markerRegex\\s*/", "", $headline);
// }
//
// if ($toclevel) {
// $prevlevel = $level;
// }
// $level = $matches[1][$headlineCount];
//
// if ($level > $prevlevel) {
// # Increase TOC level
// $toclevel++;
// $sublevelCount[$toclevel] = 0;
// if ($toclevel < $wgMaxTocLevel) {
// $prevtoclevel = $toclevel;
// $toc .= Linker::tocIndent();
// $numVisible++;
// }
// } elseif ($level < $prevlevel && $toclevel > 1) {
// # Decrease TOC level, find level to jump to
//
// for ($i = $toclevel; $i > 0; $i--) {
// if ($levelCount[$i] == $level) {
// # Found last matching level
// $toclevel = $i;
// break;
// } elseif ($levelCount[$i] < $level) {
// # Found first matching level below current level
// $toclevel = $i + 1;
// break;
// }
// }
// if ($i == 0) {
// $toclevel = 1;
// }
// if ($toclevel < $wgMaxTocLevel) {
// if ($prevtoclevel < $wgMaxTocLevel) {
// # Unindent only if the previous toc level was shown :p
// $toc .= Linker::tocUnindent($prevtoclevel - $toclevel);
// $prevtoclevel = $toclevel;
// } else {
// $toc .= Linker::tocLineEnd();
// }
// }
// } else {
// # No change in level, end TOC line
// if ($toclevel < $wgMaxTocLevel) {
// $toc .= Linker::tocLineEnd();
// }
// }
//
// $levelCount[$toclevel] = $level;
//
// # count number of headlines for each level
// $sublevelCount[$toclevel]++;
// $dot = 0;
// for ($i = 1; $i <= $toclevel; $i++) {
// if (!empty($sublevelCount[$i])) {
// if ($dot) {
// $numbering .= '.';
// }
// $numbering .= this.getTargetLanguage()->formatNum($sublevelCount[$i]);
// $dot = 1;
// }
// }
//
// # The safe header is a version of the header text safe to use for links
//
// # Remove link placeholders by the link text.
// #
// # turns into
// # link text with suffix
// # Do this before unstrip since link text can contain strip markers
// $safeHeadline = this.replaceLinkHoldersText($headline);
//
// # Avoid insertion of weird stuff like