13define(
'MW_PARSER_VERSION', 
'1.6.1');
 
   15define(
'RLH_FOR_UPDATE', 1);
 
   17# Allowed values for $mOutputType 
   21define(
'OT_PREPROCESS', 4);
 
   23# Flags for setFunctionHook 
   24define(
'SFH_NO_HASH', 1);
 
   26# string parameter for extractTags which will cause it 
   27# to strip HTML comments in addition to regular 
   28# <XML>-style tags. This should not be anything we 
   29# may want to use in wikisyntax 
   30define(
'STRIP_COMMENTS', 
'HTMLCommentStrip');
 
   32# Constants needed for external link processing 
   33define(
'HTTP_PROTOCOLS', 
'http:\/\/|https:\/\/');
 
   34# Everything except bracket, space, or control characters 
   35define(
'EXT_LINK_URL_CLASS', 
'[^][<>"\\x00-\\x20\\x7F]');
 
   36# Including space, but excluding newlines 
   37define(
'EXT_LINK_TEXT_CLASS', 
'[^\]\\x0a\\x0d]');
 
   38define(
'EXT_IMAGE_FNAME_CLASS', 
'[A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]');
 
   39define(
'EXT_IMAGE_EXTENSIONS', 
'gif|png|jpg|jpeg');
 
   50define(
'MW_COLON_STATE_TEXT', 0);
 
   51define(
'MW_COLON_STATE_TAG', 1);
 
   52define(
'MW_COLON_STATE_TAGSTART', 2);
 
   53define(
'MW_COLON_STATE_CLOSETAG', 3);
 
   54define(
'MW_COLON_STATE_TAGSLASH', 4);
 
   55define(
'MW_COLON_STATE_COMMENT', 5);
 
   56define(
'MW_COLON_STATE_COMMENTDASH', 6);
 
   57define(
'MW_COLON_STATE_COMMENTDASHDASH', 7);
 
  104    # Cleared with clearState(): 
  125    # These are variables reset at least once per parse regardless of $clearState 
  149        $this->mTagHooks = array();
 
  150        $this->mFunctionHooks = array();
 
  151        $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
 
  152        $this->mFirstCall = 
true;
 
  160        if (!$this->mFirstCall) {
 
  164        wfProfileIn(__METHOD__);
 
  165        global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
 
  167        $this->
setHook(
'pre', array( $this, 
'renderPreTag' ));
 
  193        $this->
setFunctionHook(
'special', array( 
'CoreParserFunctions', 
'special' ));
 
  196        if ($wgAllowDisplayTitle) {
 
  199        if ($wgAllowSlowParserFunctions) {
 
  204        $this->mFirstCall = 
false;
 
  205        wfProfileOut(__METHOD__);
 
  215        wfProfileIn(__METHOD__);
 
  216        if ($this->mFirstCall) {
 
  219        $this->mOutput = 
new ParserOutput;
 
  220        $this->mAutonumber = 0;
 
  221        $this->mLastSection = 
'';
 
  222        $this->mDTopen = 
false;
 
  223        $this->mIncludeCount = array();
 
  225        $this->mArgStack = array();
 
  226        $this->mInPre = 
false;
 
  227        $this->mInterwikiLinkHolders = array(
 
  231        $this->mLinkHolders = array(
 
  232            'namespaces' => array(),
 
  234            'queries' => array(),
 
  238        $this->mRevisionTimestamp = $this->mRevisionId = 
null;
 
  249        # Clear these on every parse, bug 4549 
  250        $this->mTemplates = array();
 
  251        $this->mTemplatePath = array();
 
  253        $this->mShowToc = 
true;
 
  254        $this->mForceTocPosition = 
false;
 
  255        $this->mIncludeSizes = array(
 
  260        $this->mDefaultSort = 
false;
 
  262        wfRunHooks(
'ParserClearState', array( &$this ));
 
  263        wfProfileOut(__METHOD__);
 
  268        $this->mOutputType = 
$ot;
 
  307        global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang;
 
  308        $fname = 
'Parser::parse-' . wfGetCaller();
 
  309        wfProfileIn(__METHOD__);
 
  320        if ($revid !== 
null) {
 
  321            $this->mRevisionId = $revid;
 
  322            $this->mRevisionTimestamp = 
null;
 
  325        wfRunHooks(
'ParserBeforeStrip', array( &$this, &
$text, &$this->mStripState ));
 
  327        wfRunHooks(
'ParserAfterStrip', array( &$this, &
$text, &$this->mStripState ));
 
  329        $text = $this->mStripState->unstripGeneral(
$text);
 
  331        # Clean up special characters, only run once, next-to-last before doBlockLevels 
  333            # french spaces, last one Guillemet-
left 
  334            # only 
if there is something before the space
 
  335            '/(.) (?=\\?|:|;|!|\\302\\273)/' => 
'\\1 \\2',
 
  336            # french spaces, Guillemet-right
 
  337            '/(\\302\\253) /' => 
'\\1 ',
 
  339        $text = preg_replace(array_keys($fixtags), array_values($fixtags), 
$text);
 
  346        # the position of the parserConvert() call should not be changed. it 
  347        # assumes that the links are all replaced and the only thing left 
  348        # is the <nowiki> mark. 
  349        # Side-effects: this calls $this->mOutput->setTitleText() 
  350        $text = $wgContLang->parserConvert(
$text, $this);
 
  352        $text = $this->mStripState->unstripNoWiki(
$text);
 
  354        wfRunHooks(
'ParserBeforeTidy', array( &$this, &
$text ));
 
  358        if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) {
 
  361            # attempt to sanitize at least some nesting problems 
  362            # (bug #2702 and quite a few others) 
  365                # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
 
  366                '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
 
  367                '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
 
  368                # fix 
up an anchor inside another anchor, only
 
  369                # at least 
for a single single nested link (bug 3695)
 
  370                '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
 
  371                '\\1\\2</a>\\3</a>\\1\\4</a>',
 
  372                # fix div inside 
inline elements- 
doBlockLevels won
't wrap a line which 
  373                # contains a div, so fix it up here; replace 
  374                # div with escaped text 
  375                '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/
' => 
  376                '\\1\\3<div\\5>\\6</div>\\8\\9
', 
  377                # remove empty italic or bold tag pairs, some 
  378                # introduced by rules above 
  379                '/<([bi])><\/\\1>/
' => '', 
  382            $text = preg_replace( 
  383                array_keys($tidyregs), 
  384                array_values($tidyregs), 
  389        wfRunHooks('ParserAfterTidy
', array( &$this, &$text )); 
  391        # Information on include size limits, for the benefit of users who try to skirt them 
  392        if (max($this->mIncludeSizes) > 1000) { 
  393            $max = $this->mOptions->getMaxIncludeSize(); 
  395                "Pre-expand include size: {$this->mIncludeSizes['pre-
expand']} bytes\n" . 
  396                "Post-expand include size: {$this->mIncludeSizes['post-
expand']} bytes\n" . 
  397                "Template argument size: {$this->mIncludeSizes['arg
']} bytes\n" . 
  398                "Maximum: $max bytes\n" . 
  401        $this->mOutput->setText($text); 
  402        $this->mRevisionId = $oldRevisionId; 
  403        $this->mRevisionTimestamp = $oldRevisionTimestamp; 
  404        wfProfileOut($fname); 
  405        wfProfileOut(__METHOD__); 
  407        return $this->mOutput; 
  414    public function recursiveTagParse($text) 
  416        wfProfileIn(__METHOD__); 
  417        wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState )); 
  418        $text = $this->strip($text, $this->mStripState); 
  419        wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState )); 
  420        $text = $this->internalParse($text); 
  421        wfProfileOut(__METHOD__); 
  429    public function preprocess($text, $title, $options) 
  431        wfProfileIn(__METHOD__); 
  433        $this->setOutputType(OT_PREPROCESS); 
  434        $this->mOptions = $options; 
  435        $this->mTitle = $title; 
  436        wfRunHooks('ParserBeforeStrip
', array( &$this, &$text, &$this->mStripState )); 
  437        $text = $this->strip($text, $this->mStripState); 
  438        wfRunHooks('ParserAfterStrip
', array( &$this, &$text, &$this->mStripState )); 
  439        if ($this->mOptions->getRemoveComments()) { 
  440            $text = Sanitizer::removeHTMLcomments($text); 
  442        $text = $this->replaceVariables($text); 
  443        $text = $this->mStripState->unstripBoth($text); 
  444        wfProfileOut(__METHOD__); 
  454    public function getRandomString() 
  456        return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)); 
  459    public function &getTitle() 
  461        return $this->mTitle; 
  463    public function getOptions() 
  465        return $this->mOptions; 
  468    public function getFunctionLang() 
  470        global $wgLang, $wgContLang; 
  471        return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang; 
  492    public function extractTagsAndParams($elements, $text, &$matches, $uniq_prefix = '') 
  498        $taglist = implode('|
', $elements); 
  499        $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?>)|<(!--)/i"; 
  501        while ('' != $text) { 
  502            $p = preg_split($start, $text, 2, PREG_SPLIT_DELIM_CAPTURE); 
  521            $marker = "$uniq_prefix-$element-" . sprintf('%08X
', $n++) . '-QINU
'; 
  522            $stripped .= $marker; 
  524            if ($close === '/>
') { 
  525                // Empty element tag, <tag /> 
  530                if ($element == '!--
') { 
  533                    $end = "/(<\\/$element\\s*>)/i"; 
  535                $q = preg_split($end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE); 
  538                    # No end tag -- let it run out to the end of the text. 
  547            $matches[$marker] = array( $element, 
  549                Sanitizer::decodeTagAttributes($attributes), 
  550                "<$element$attributes$close$content$tail" ); 
  572    public function strip($text, $state, $stripcomments = false, $dontstrip = array()) 
  575        wfProfileIn(__METHOD__); 
  576        $render = ($this->mOutputType == OT_HTML); 
  578        $uniq_prefix = $this->mUniqPrefix; 
  579        $commentState = new ReplacementArray; 
  580        $nowikiItems = array(); 
  581        $generalItems = array(); 
  583        $elements = array_merge( 
  584            array( 'nowiki
', 'gallery
' ), 
  585            array_keys($this->mTagHooks) 
  589            $elements[] = 'html'; 
  591        if ($this->mOptions->getUseTeX()) { 
  592            $elements[] = 'math
'; 
  595        # Removing $dontstrip tags from $elements list (currently only 'gallery
', fixing bug 2700) 
  596        foreach ($elements as $k => $v) { 
  597            if (!in_array($v, $dontstrip)) { 
  600            unset($elements[$k]); 
  604        $text = Parser::extractTagsAndParams($elements, $text, $matches, $uniq_prefix); 
  606        foreach ($matches as $marker => $data) { 
  607            list($element, $content, $params, $tag) = $data; 
  609                $tagName = strtolower($element); 
  610                wfProfileIn(__METHOD__ . "-render-$tagName"); 
  614                    if (substr($tag, -3) == '-->
') { 
  617                        // Unclosed comment in input. 
  618                        // Close it so later stripping can remove it 
  627                    // Shouldn't happen otherwise. :)
 
  630                    $output = Xml::escapeTagsOnly($content);
 
  633                    $output = $wgContLang->armourMath(MathRenderer::renderMath($content));
 
  639                    if (isset($this->mTagHooks[$tagName])) {
 
  640                        $output = call_user_func_array(
 
  641                            $this->mTagHooks[$tagName],
 
  642                            array( $content, 
$params, $this )
 
  645                        throw new MWException(
"Invalid call hook $element");
 
  648                wfProfileOut(__METHOD__ . 
"-render-$tagName");
 
  657            if (!$stripcomments && $element == 
'!--') {
 
  658                $commentState->setPair($marker, 
$output);
 
  659            } elseif ($element == 
'html' || $element == 
'nowiki') {
 
  660                $nowikiItems[$marker] = 
$output;
 
  662                $generalItems[$marker] = 
$output;
 
  665        # Add the new items to the state 
  666        # We do this after the loop instead of during it to avoid slowing 
  667        # down the recursive unstrip 
  668        $state->nowiki->mergeArray($nowikiItems);
 
  669        $state->general->mergeArray($generalItems);
 
  671        # Unstrip comments unless explicitly told otherwise. 
  672        # (The comments are always stripped prior to this point, so as to 
  673        # not invoke any extension tags / parser hooks contained within 
  675        if (!$stripcomments) {
 
  680        wfProfileOut(__METHOD__);
 
  710    public function unstripForHTML(
$text)
 
  712        return $this->mStripState->unstripBoth(
$text);
 
  743    public function tidy(
$text)
 
  745        global $wgTidyInternal;
 
  746        $wrappedtext = 
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' .
 
  747' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>' .
 
  748'<head><title>test</title></head><body>' . 
$text . 
'</body></html>';
 
  749        if ($wgTidyInternal) {
 
  754        if (is_null($correctedtext)) {
 
  755            wfDebug(
"Tidy error detected!\n");
 
  756            return $text . 
"\n<!-- Tidy found serious XHTML errors -->\n";
 
  758        return $correctedtext;
 
  767    public function externalTidy(
$text)
 
  769        global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
 
  770        $fname = 
'Parser::externalTidy';
 
  776        $descriptorspec = array(
 
  777            0 => array(
'pipe', 
'r'),
 
  778            1 => array(
'pipe', 
'w'),
 
  779            2 => array(
'file', 
'/dev/null', 
'a')  
 
  782        $process = proc_open(
"$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes);
 
  783        if (is_resource($process)) {
 
  789            fwrite($pipes[0], 
$text);
 
  791            while (!feof($pipes[1])) {
 
  792                $cleansource .= fgets($pipes[1], 1024);
 
  795            proc_close($process);
 
  798        wfProfileOut($fname);
 
  800        if ($cleansource == 
'' && 
$text != 
'') {
 
  819    public function internalTidy(
$text)
 
  822        $fname = 
'Parser::internalTidy';
 
  825        tidy_load_config($wgTidyConf);
 
  826        tidy_set_encoding(
'utf8');
 
  827        tidy_parse_string(
$text);
 
  829        if (tidy_get_status() == 2) {
 
  834            $cleansource = tidy_get_output();
 
  836        wfProfileOut($fname);
 
  845    public function doTableStuff(
$text)
 
  847        $fname = 
'Parser::doTableStuff';
 
  850        $lines = explode(
"\n", 
$text);
 
  851        $td_history = array(); 
 
  852        $last_tag_history = array(); 
 
  853        $tr_history = array(); 
 
  854        $tr_attributes = array(); 
 
  855        $has_opened_tr = array(); 
 
  857        foreach ($lines as 
$key => $line) {
 
  863            $first_character = $line[0];
 
  866            if (preg_match(
'/^(:*)\{\|(.*)$/', $line, $matches)) {
 
  868                $indent_level = strlen($matches[1]);
 
  870                $attributes = $this->mStripState->unstripBoth($matches[2]);
 
  873                $lines[
$key] = str_repeat(
'<dl><dd>', $indent_level) . 
"<table{$attributes}>";
 
  874                array_push($td_history, 
false);
 
  875                array_push($last_tag_history, 
'');
 
  876                array_push($tr_history, 
false);
 
  877                array_push($tr_attributes, 
'');
 
  878                array_push($has_opened_tr, 
false);
 
  879            } elseif (count($td_history) == 0) {
 
  882            } elseif (substr($line, 0, 2) == 
'|}') {
 
  884                $line = 
'</table>' . substr($line, 2);
 
  885                $last_tag = array_pop($last_tag_history);
 
  887                if (!array_pop($has_opened_tr)) {
 
  888                    $line = 
"<tr><td></td></tr>{$line}";
 
  891                if (array_pop($tr_history)) {
 
  892                    $line = 
"</tr>{$line}";
 
  895                if (array_pop($td_history)) {
 
  896                    $line = 
"</{$last_tag}>{$line}";
 
  898                array_pop($tr_attributes);
 
  899                $lines[
$key] = $line . str_repeat(
'</dd></dl>', $indent_level);
 
  900            } elseif (substr($line, 0, 2) == 
'|-') {
 
  902                $line = preg_replace(
'#^\|-+#', 
'', $line);
 
  905                $attributes = $this->mStripState->unstripBoth($line);
 
  907                array_pop($tr_attributes);
 
  911                $last_tag = array_pop($last_tag_history);
 
  912                array_pop($has_opened_tr);
 
  913                array_push($has_opened_tr, 
true);
 
  915                if (array_pop($tr_history)) {
 
  919                if (array_pop($td_history)) {
 
  920                    $line = 
"</{$last_tag}>{$line}";
 
  923                $lines[
$key] = $line;
 
  924                array_push($tr_history, 
false);
 
  925                array_push($td_history, 
false);
 
  926                array_push($last_tag_history, 
'');
 
  927            } elseif ($first_character == 
'|' || $first_character == 
'!' || substr($line, 0, 2) == 
'|+') {
 
  929                if (substr($line, 0, 2) == 
'|+') {
 
  930                    $first_character = 
'+';
 
  931                    $line = substr($line, 1);
 
  934                $line = substr($line, 1);
 
  936                if ($first_character == 
'!') {
 
  937                    $line = str_replace(
'!!', 
'||', $line);
 
  944                $cells = StringUtils::explodeMarkup(
'||', $line);
 
  949                foreach ($cells as $cell) {
 
  951                    if ($first_character != 
'+') {
 
  952                        $tr_after = array_pop($tr_attributes);
 
  953                        if (!array_pop($tr_history)) {
 
  954                            $previous = 
"<tr{$tr_after}>\n";
 
  956                        array_push($tr_history, 
true);
 
  957                        array_push($tr_attributes, 
'');
 
  958                        array_pop($has_opened_tr);
 
  959                        array_push($has_opened_tr, 
true);
 
  962                    $last_tag = array_pop($last_tag_history);
 
  964                    if (array_pop($td_history)) {
 
  965                        $previous = 
"</{$last_tag}>{$previous}";
 
  968                    if ($first_character == 
'|') {
 
  970                    } elseif ($first_character == 
'!') {
 
  972                    } elseif ($first_character == 
'+') {
 
  973                        $last_tag = 
'caption';
 
  978                    array_push($last_tag_history, $last_tag);
 
  981                    $cell_data = explode(
'|', $cell, 2);
 
  985                    if (strpos($cell_data[0], 
'[[') !== 
false) {
 
  986                        $cell = 
"{$previous}<{$last_tag}>{$cell}";
 
  987                    } elseif (count($cell_data) == 1) {
 
  988                        $cell = 
"{$previous}<{$last_tag}>{$cell_data[0]}";
 
  990                        $attributes = $this->mStripState->unstripBoth($cell_data[0]);
 
  992                        $cell = 
"{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
 
  995                    $lines[
$key] .= $cell;
 
  996                    array_push($td_history, 
true);
 
 1002        while (count($td_history) > 0) {
 
 1003            if (array_pop($td_history)) {
 
 1004                $lines[] = 
'</td>' ;
 
 1006            if (array_pop($tr_history)) {
 
 1007                $lines[] = 
'</tr>' ;
 
 1009            if (!array_pop($has_opened_tr)) {
 
 1010                $lines[] = 
"<tr><td></td></tr>" ;
 
 1013            $lines[] = 
'</table>' ;
 
 1016        $output = implode(
"\n", $lines) ;
 
 1019        if (
$output == 
"<table>\n<tr><td></td></tr>\n</table>") {
 
 1023        wfProfileOut($fname);
 
 1034    public function internalParse(
$text)
 
 1038        $fname = 
'Parser::internalParse';
 
 1039        wfProfileIn($fname);
 
 1041        # Hook to suspend the parser in this state 
 1042        if (!wfRunHooks(
'ParserBeforeInternalParse', array( &$this, &
$text, &$this->mStripState ))) {
 
 1043            wfProfileOut($fname);
 
 1047        # Remove <noinclude> tags and <includeonly> sections 
 1048        $text = strtr(
$text, array( 
'<onlyinclude>' => 
'' , 
'</onlyinclude>' => 
'' ));
 
 1049        $text = strtr(
$text, array( 
'<noinclude>' => 
'', 
'</noinclude>' => 
''));
 
 1050        $text = StringUtils::delimiterReplace(
'<includeonly>', 
'</includeonly>', 
'', 
$text);
 
 1054        $text = $this->replaceVariables(
$text, $args);
 
 1055        wfRunHooks(
'InternalParseBeforeLinks', array( &$this, &
$text, &$this->mStripState ));
 
 1063        $text = preg_replace(
'/(^|\n)-----*/', 
'\\1<hr />', 
$text);
 
 1066        $this->stripNoGallery(
$text);
 
 1068        if ($this->mOptions->getUseDynamicDates()) {
 
 1069            $df = &DateFormatter::getInstance();
 
 1070            $text = $df->reformat($this->mOptions->getDateFormat(), 
$text);
 
 1076        # replaceInternalLinks may sometimes leave behind 
 1077        # absolute URLs, which have to be masked to hide them from replaceExternalLinks 
 1078        $text = str_replace($this->mUniqPrefix . 
"NOPARSE", 
"", 
$text);
 
 1081        $text = $this->formatHeadings(
$text, $isMain);
 
 1083        wfProfileOut($fname);
 
 1093    public function &doMagicLinks(&
$text)
 
 1095        wfProfileIn(__METHOD__);
 
 1096        $text = preg_replace_callback(
 
 1098                            <a.*?</a> |                 # Skip link text 
 1099                            <.*?> |                     # Skip stuff inside HTML elements 
 1100                            (?:RFC|PMID)\s+([0-9]+) |   # RFC or PMID, capture number as m[1] 
 1101                            ISBN\s+(\b                  # ISBN, capture number as m[2] 
 1102                                     (?: 97[89] [\ \-]? )?   # optional 13-digit ISBN prefix 
 1103                                     (?: [0-9]  [\ \-]? ){9} # 9 digits with opt. delimiters 
 1104                                     [0-9Xx]                 # check digit 
 1107            array( &$this, 
'magicLinkCallback' ),
 
 1110        wfProfileOut(__METHOD__);
 
 1114    public function magicLinkCallback(
$m)
 
 1116        if (substr(
$m[0], 0, 1) == 
'<') {
 
 1119        } elseif (substr(
$m[0], 0, 4) == 
'ISBN') {
 
 1121            $num = strtr($isbn, array(
 
 1126            $titleObj = SpecialPage::getTitleFor(
'Booksources');
 
 1127            $text = 
'<a href="' .
 
 1128                $titleObj->escapeLocalUrl(
"isbn=$num") .
 
 1129                "\" class=\"internal\">ISBN $isbn</a>";
 
 1131            if (substr(
$m[0], 0, 3) == 
'RFC') {
 
 1135            } elseif (substr(
$m[0], 0, 4) == 
'PMID') {
 
 1137                $urlmsg = 
'pubmedurl';
 
 1140                throw new MWException(__METHOD__ . 
': unrecognised match type "' .
 
 1141                    substr(
$m[0], 0, 20) . 
'"');
 
 1145            $sk = $this->mOptions->getSkin();
 
 1146            $la = $sk->getExternalLinkAttributes(
$url, $keyword . 
$id);
 
 1147            $text = 
"<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
 
 1157    public function doHeadings(
$text)
 
 1159        $fname = 
'Parser::doHeadings';
 
 1160        wfProfileIn($fname);
 
 1161        for (
$i = 6; 
$i >= 1; --
$i) {
 
 1162            $h = str_repeat(
'=', 
$i);
 
 1163            $text = preg_replace(
 
 1164                "/^{$h}(.+){$h}\\s*$/m",
 
 1165                "<h{$i}>\\1</h{$i}>\\2",
 
 1169        wfProfileOut($fname);
 
 1178    public function doAllQuotes(
$text)
 
 1180        $fname = 
'Parser::doAllQuotes';
 
 1181        wfProfileIn($fname);
 
 1183        $lines = explode(
"\n", 
$text);
 
 1184        foreach ($lines as $line) {
 
 1185            $outtext .= $this->doQuotes($line) . 
"\n";
 
 1187        $outtext = substr($outtext, 0, -1);
 
 1188        wfProfileOut($fname);
 
 1196    public function doQuotes(
$text)
 
 1199        if (count($arr) == 1) {
 
 1202            # First, do some preliminary work. This may shift some apostrophes from 
 1203            # being mark-up to being text. It also counts the number of occurrences 
 1204            # of bold and italics mark-ups. 
 1208            foreach ($arr as 
$r) {
 
 1209                if ((
$i % 2) == 1) {
 
 1210                    # If there are ever four apostrophes, assume the first is supposed to 
 1211                    # be text, and the remaining three constitute mark-up for bold text. 
 1212                    if (strlen($arr[
$i]) == 4) {
 
 1213                        $arr[
$i - 1] .= 
"'";
 
 1216                    # If there are more than 5 apostrophes in a row, assume they're all 
 1217                    # text except for the last 5. 
 1218                    elseif (strlen($arr[
$i]) > 5) {
 
 1219                        $arr[
$i - 1] .= str_repeat(
"'", strlen($arr[
$i]) - 5);
 
 1222                    # Count the number of occurrences of bold and italics mark-ups. 
 1223                    # We are not counting sequences of five apostrophes. 
 1224                    if (strlen($arr[
$i]) == 2) {
 
 1226                    } elseif (strlen($arr[
$i]) == 3) {
 
 1228                    } elseif (strlen($arr[
$i]) == 5) {
 
 1236            # If there is an odd number of both bold and italics, it is likely 
 1237            # that one of the bold ones was meant to be an apostrophe followed 
 1238            # by italics. Which one we cannot know for certain, but it is more 
 1239            # likely to be one that has a single-letter word before it. 
 1240            if (($numbold % 2 == 1) && ($numitalics % 2 == 1)) {
 
 1242                $firstsingleletterword = -1;
 
 1243                $firstmultiletterword = -1;
 
 1245                foreach ($arr as 
$r) {
 
 1246                    if ((
$i % 2 == 1) and (strlen(
$r) == 3)) {
 
 1247                        $x1 = substr($arr[
$i - 1], -1);
 
 1248                        $x2 = substr($arr[
$i - 1], -2, 1);
 
 1250                            if ($firstspace == -1) {
 
 1253                        } elseif ($x2 == 
' ') {
 
 1254                            if ($firstsingleletterword == -1) {
 
 1255                                $firstsingleletterword = 
$i;
 
 1258                            if ($firstmultiletterword == -1) {
 
 1259                                $firstmultiletterword = 
$i;
 
 1266                # If there is a single-letter word, use it! 
 1267                if ($firstsingleletterword > -1) {
 
 1268                    $arr [ $firstsingleletterword ] = 
"''";
 
 1269                    $arr [ $firstsingleletterword - 1 ] .= 
"'";
 
 1271                # If not, but there's a multi-letter word, use that one. 
 1272                elseif ($firstmultiletterword > -1) {
 
 1273                    $arr [ $firstmultiletterword ] = 
"''";
 
 1274                    $arr [ $firstmultiletterword - 1 ] .= 
"'";
 
 1276                # ... otherwise use the first one that has neither. 
 1277                # (notice that it is possible for all three to be -1 if, for example, 
 1278                # there is only one pentuple-apostrophe in the line) 
 1279                elseif ($firstspace > -1) {
 
 1280                    $arr [ $firstspace ] = 
"''";
 
 1281                    $arr [ $firstspace - 1 ] .= 
"'";
 
 1285            # Now let's actually convert our apostrophic mush to HTML! 
 1290            foreach ($arr as 
$r) {
 
 1291                if ((
$i % 2) == 0) {
 
 1298                    if (strlen(
$r) == 2) {
 
 1302                        } elseif (
$state == 
'bi') {
 
 1305                        } elseif (
$state == 
'ib') {
 
 1308                        } elseif (
$state == 
'both') {
 
 1309                            $output .= 
'<b><i>' . $buffer . 
'</i>';
 
 1311                        } 
else { # 
$state can be 
'b' or 
'' 
 1315                    } elseif (strlen(
$r) == 3) {
 
 1319                        } elseif (
$state == 
'bi') {
 
 1322                        } elseif (
$state == 
'ib') {
 
 1325                        } elseif (
$state == 
'both') {
 
 1326                            $output .= 
'<i><b>' . $buffer . 
'</b>';
 
 1328                        } 
else { # 
$state can be 
'i' or 
'' 
 1332                    } elseif (strlen(
$r) == 5) {
 
 1336                        } elseif (
$state == 
'i') {
 
 1339                        } elseif (
$state == 
'bi') {
 
 1342                        } elseif (
$state == 
'ib') {
 
 1345                        } elseif (
$state == 
'both') {
 
 1346                            $output .= 
'<i><b>' . $buffer . 
'</b></i>';
 
 1348                        } 
else { # (
$state == 
'')
 
 1356            # Now close all remaining tags.  Notice that the order is important. 
 1366            # There might be lonely ''''', so make sure we have a buffer 
 1367            if (
$state == 
'both' && $buffer) {
 
 1368                $output .= 
'<b><i>' . $buffer . 
'</i></b>';
 
 1382    public function replaceExternalLinks(
$text)
 
 1385        $fname = 
'Parser::replaceExternalLinks';
 
 1386        wfProfileIn($fname);
 
 1388        $sk = $this->mOptions->getSkin();
 
 1392        $s = $this->replaceFreeExternalLinks(array_shift($bits));
 
 1395        while (
$i < count($bits)) {
 
 1399            $trail = $bits[
$i++];
 
 1401            # The characters '<' and '>' (which were escaped by 
 1402            # removeHTMLtags()) should not be included in 
 1403            # URLs, per RFC 2396. 
 1405            if (preg_match(
'/&(lt|gt);/', 
$url, $m2, PREG_OFFSET_CAPTURE)) {
 
 1410            # If the link text is an image URL, replace it with an <img> tag 
 1411            # This happened by accident in the original parser, but some people used it extensively 
 1412            $img = $this->maybeMakeExternalImage(
$text);
 
 1413            if (
$img !== 
false) {
 
 1419            # Set linktype for CSS - if URL==text, link is essentially free 
 1420            $linktype = (
$text == 
$url) ? 
'free' : 
'text';
 
 1422            # No link text, e.g. [http: 
 1424                # Autonumber if allowed. See bug #5918 
 1426                    $text = 
'[' . ++$this->mAutonumber . 
']';
 
 1427                    $linktype = 
'autonumber';
 
 1429                    # Otherwise just use the URL 
 1434                # Have link text, e.g. [http: 
 1436                list($dtrail, $trail) = Linker::splitTrail($trail);
 
 1439            $text = $wgContLang->markNoConversion(
$text);
 
 1443            # Process the trail (i.e. everything after this link up until start of the next link), 
 1444            # replacing any non-bracketed links 
 1445            $trail = $this->replaceFreeExternalLinks($trail);
 
 1447            # Use the encoded URL 
 1448            # This means that users can paste URLs directly into the text 
 1449            # Funny characters like ö aren't valid in URLs anyway 
 1450            # This was changed in August 2004 
 1451            $s .= $sk->makeExternalLink(
$url, 
$text, 
false, $linktype, $this->mTitle->getNamespace()) . $dtrail . $trail;
 
 1453            # Register link in the output object. 
 1454            # Replace unnecessary URL escape codes with the referenced character 
 1455            # This prevents spammers from hiding links from the filters 
 1457            $this->mOutput->addExternalLink($pasteurized);
 
 1460        wfProfileOut($fname);
 
 1468    public function replaceFreeExternalLinks(
$text)
 
 1472        $fname = 
'Parser::replaceFreeExternalLinks';
 
 1473        wfProfileIn($fname);
 
 1476        $s = array_shift($bits);
 
 1479        $sk = $this->mOptions->getSkin();
 
 1481        while (
$i < count($bits)) {
 
 1483            $remainder = $bits[
$i++];
 
 1487                # Found some characters after the protocol that look promising 
 1491                # special case: handle urls as url args: 
 1493                if (strlen($trail) == 0 &&
 
 1498                    $url .= $bits[
$i] . 
$m[1]; # protocol, url as arg to previous link
 
 1503                # The characters '<' and '>' (which were escaped by 
 1504                # removeHTMLtags()) should not be included in 
 1505                # URLs, per RFC 2396. 
 1507                if (preg_match(
'/&(lt|gt);/', 
$url, $m2, PREG_OFFSET_CAPTURE)) {
 
 1508                    $trail = substr(
$url, $m2[0][1]) . $trail;
 
 1512                # Move trailing punctuation to $trail 
 1514                # If there is no left bracket, then consider right brackets fair game too 
 1515                if (strpos(
$url, 
'(') === 
false) {
 
 1519                $numSepChars = strspn(strrev(
$url), $sep);
 
 1521                    $trail = substr(
$url, -$numSepChars) . $trail;
 
 1522                    $url = substr(
$url, 0, -$numSepChars);
 
 1526                # Is this an external image? 
 1527                $text = $this->maybeMakeExternalImage(
$url);
 
 1528                if (
$text === 
false) {
 
 1529                    # Not an image, make a link 
 1530                    $text = $sk->makeExternalLink(
$url, $wgContLang->markNoConversion(
$url), 
true, 
'free', $this->mTitle->getNamespace());
 
 1531                    # Register it in the output object... 
 1532                    # Replace unnecessary URL escape codes with their equivalent characters 
 1535                    $this->mOutput->addExternalLink($pasteurized);
 
 1542        wfProfileOut($fname);
 
 1556    public static function replaceUnusualEscapes(
$url)
 
 1558        return preg_replace_callback(
 
 1559            '/%[0-9A-Fa-f]{2}/',
 
 1560            array( 
'Parser', 
'replaceUnusualEscapesCallback' ),
 
 1571    private static function replaceUnusualEscapesCallback($matches)
 
 1573        $char = urldecode($matches[0]);
 
 1577        if ($ord > 32 && $ord < 127 && strpos(
'<>"#{}|\^~[]`;/?', $char) === 
false) {
 
 1591    public function maybeMakeExternalImage(
$url)
 
 1593        $sk = $this->mOptions->getSkin();
 
 1594        $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
 
 1595        $imagesexception = !empty($imagesfrom);
 
 1597        if ($this->mOptions->getAllowExternalImages()
 
 1598             || ($imagesexception && strpos(
$url, $imagesfrom) === 0)) {
 
 1601                $text = $sk->makeExternalImage(htmlspecialchars(
$url));
 
 1612    public function replaceInternalLinks(
$s)
 
 1615        static $fname = 
'Parser::replaceInternalLinks' ;
 
 1617        wfProfileIn($fname);
 
 1619        wfProfileIn($fname . 
'-setup');
 
 1621        # the % is needed to support urlencoded titles as well 
 1626        $sk = $this->mOptions->getSkin();
 
 1628        #split the entire text string on occurences of [[ 
 1629        $a = explode(
'[[', 
' ' . 
$s);
 
 1630        #get the first element (all text up to first [[), and remove the space we added 
 1631        $s = array_shift($a);
 
 1634        # Match a link having the form [[namespace:link|alternate]]trail 
 1637            $e1 = 
"/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
 
 1639        # Match cases where there is no "]]", which might still be images
 
 1640        static $e1_img = 
false;
 
 1642            $e1_img = 
"/^([{$tc}]+)\\|(.*)\$/sD";
 
 1644        # Match the end of a line for a word that's not followed by whitespace, 
 1645        # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched 
 1646        $e2 = wfMsgForContent(
'linkprefix');
 
 1648        $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
 
 1649        if (is_null($this->mTitle)) {
 
 1650            throw new MWException(__METHOD__ . 
": \$this->mTitle is null\n");
 
 1652        $nottalk = !$this->mTitle->isTalkPage();
 
 1654        if ($useLinkPrefixExtension) {
 
 1656            if (preg_match($e2, 
$s, 
$m)) {
 
 1657                $first_prefix = 
$m[2];
 
 1659                $first_prefix = 
false;
 
 1665        if ($wgContLang->hasVariants()) {
 
 1666            $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
 
 1668            $selflink = array($this->mTitle->getPrefixedText());
 
 1670        $useSubpages = $this->areSubpagesAllowed();
 
 1671        wfProfileOut($fname . 
'-setup');
 
 1673        # Loop for each link 
 1674        for ($k = 0; isset($a[$k]); $k++) {
 
 1676            if ($useLinkPrefixExtension) {
 
 1677                wfProfileIn($fname . 
'-prefixhandling');
 
 1678                if (preg_match($e2, 
$s, 
$m)) {
 
 1685                if ($first_prefix) {
 
 1686                    $prefix = $first_prefix;
 
 1687                    $first_prefix = 
false;
 
 1689                wfProfileOut($fname . 
'-prefixhandling');
 
 1692            $might_be_img = 
false;
 
 1694            wfProfileIn(
"$fname-e1");
 
 1695            if (preg_match($e1, $line, 
$m)) { # page with normal text or alt
 
 1697                # If we get a ] at the beginning of $m[3] that means we have a link that's something like: 
 1698                # [[Image:Foo.jpg|[http: 
 1699                # the real problem is with the $e1 regex 
 1702                # Still some problems for cases where the ] is meant to be outside punctuation, 
 1703                # and no image is in sight. See bug 2095. 
 1706                    substr(
$m[3], 0, 1) === 
']' &&
 
 1707                    strpos(
$text, 
'[') !== 
false 
 1709                    $text .= 
']'; # so that replaceExternalLinks(
$text) works later
 
 1710                    $m[3] = substr(
$m[3], 1);
 
 1712                # fix up urlencoded title texts 
 1713                if (strpos(
$m[1], 
'%') !== 
false) {
 
 1714                    # Should anchors '#' also be rejected? 
 1715                    $m[1] = str_replace(array(
'<', 
'>'), array(
'<', 
'>'), urldecode(
$m[1]));
 
 1718            } elseif (preg_match($e1_img, $line, 
$m)) { # Invalid, but might be an image with a link in its caption
 
 1719                $might_be_img = 
true;
 
 1721                if (strpos(
$m[1], 
'%') !== 
false) {
 
 1722                    $m[1] = urldecode(
$m[1]);
 
 1725            } 
else { # Invalid form; output directly
 
 1726                $s .= $prefix . 
'[[' . $line ;
 
 1727                wfProfileOut(
"$fname-e1");
 
 1730            wfProfileOut(
"$fname-e1");
 
 1731            wfProfileIn(
"$fname-misc");
 
 1733            # Don't allow internal links to pages containing 
 1734            # PROTO: where PROTO is a valid URL protocol; these 
 1735            # should be external links. 
 1737                $s .= $prefix . 
'[[' . $line ;
 
 1741            # Make subpage if necessary 
 1743                $link = $this->maybeDoSubpageLink(
$m[1], 
$text);
 
 1748            $noforce = (substr(
$m[1], 0, 1) != 
':');
 
 1750                # Strip off leading ':' 
 1751                $link = substr($link, 1);
 
 1754            wfProfileOut(
"$fname-misc");
 
 1755            wfProfileIn(
"$fname-title");
 
 1758                $s .= $prefix . 
'[[' . $line;
 
 1759                wfProfileOut(
"$fname-title");
 
 1763            $ns = $nt->getNamespace();
 
 1764            $iw = $nt->getInterWiki();
 
 1765            wfProfileOut(
"$fname-title");
 
 1767            if ($might_be_img) { # 
if this is actually an invalid link
 
 1768                wfProfileIn(
"$fname-might_be_img");
 
 1769                if ($ns == NS_IMAGE && $noforce) { #but might be an image
 
 1771                    while (isset($a[$k + 1])) {
 
 1772                        #look at the next 'line' to see if we can close it there 
 1773                        $spliced = array_splice($a, $k + 1, 1);
 
 1774                        $next_line = array_shift($spliced);
 
 1775                        $m = explode(
']]', $next_line, 3);
 
 1776                        if (count(
$m) == 3) {
 
 1777                            # the first ]] closes the inner link, the second the image 
 1779                            $text .= 
"[[{$m[0]}]]{$m[1]}";
 
 1782                        } elseif (count(
$m) == 2) {
 
 1783                            #if there's exactly one ]] that's fine, we'll keep looking 
 1784                            $text .= 
"[[{$m[0]}]]{$m[1]}";
 
 1786                            #if $next_line is invalid too, we need look no further 
 1787                            $text .= 
'[[' . $next_line;
 
 1792                        # we couldn't find the end of this imageLink, so output it raw 
 1793                        #but don't ignore what might be perfectly normal links in the text we've examined 
 1795                        $s .= 
"{$prefix}[[$link|$text";
 
 1796                        # note: no $trail, because without an end, there *is* no trail 
 1797                        wfProfileOut(
"$fname-might_be_img");
 
 1800                } 
else { #it
's not an image, so output it raw 
 1801                    $s .= "{$prefix}[[$link|$text"; 
 1802                    # note: no $trail, because without an end, there *is* no trail 
 1803                    wfProfileOut("$fname-might_be_img"); 
 1806                wfProfileOut("$fname-might_be_img"); 
 1809            $wasblank = ('' == $text); 
 1814            # Link not escaped by : , create the various objects 
 1818                wfProfileIn("$fname-interwiki"); 
 1819                if ($iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName($iw)) { 
 1820                    $this->mOutput->addLanguageLink($nt->getFullText()); 
 1821                    $s = rtrim($s . $prefix); 
 1822                    $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; 
 1823                    wfProfileOut("$fname-interwiki"); 
 1826                wfProfileOut("$fname-interwiki"); 
 1828                if ($ns == NS_IMAGE) { 
 1829                    wfProfileIn("$fname-image"); 
 1830                    if (!wfIsBadImage($nt->getDBkey(), $this->mTitle)) { 
 1831                        # recursively parse links inside the image caption 
 1832                        # actually, this will parse them in any other parameters, too, 
 1833                        # but it might be hard to fix that, and it doesn't matter ATM
 
 1837                        # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them 
 1838                        $s .= $prefix . $this->armorLinks($this->makeImage($nt, 
$text)) . $trail;
 
 1839                        $this->mOutput->addImage($nt->getDBkey());
 
 1841                        wfProfileOut(
"$fname-image");
 
 1844                        # We still need to record the image's presence on the page 
 1845                        $this->mOutput->addImage($nt->getDBkey());
 
 1847                    wfProfileOut(
"$fname-image");
 
 1850                if ($ns == NS_CATEGORY) {
 
 1851                    wfProfileIn(
"$fname-category");
 
 1852                    $s = rtrim(
$s . 
"\n"); # bug 87
 
 1855                        $sortkey = $this->getDefaultSort();
 
 1860                    $sortkey = str_replace(
"\n", 
'', $sortkey);
 
 1861                    $sortkey = $wgContLang->convertCategoryKey($sortkey);
 
 1862                    $this->mOutput->addCategory($nt->getDBkey(), $sortkey);
 
 1868                    $s .= trim($prefix . $trail, 
"\n") == 
'' ? 
'': $prefix . $trail;
 
 1870                    wfProfileOut(
"$fname-category");
 
 1875            # Self-link checking 
 1876            if ($nt->getFragment() === 
'') {
 
 1877                if (in_array($nt->getPrefixedText(), $selflink, 
true)) {
 
 1878                    $s .= $prefix . $sk->makeSelfLinkObj($nt, 
$text, 
'', $trail);
 
 1883            # Special and Media are pseudo-namespaces; no pages actually exist in them 
 1884            if ($ns == NS_MEDIA) {
 
 1885                $link = $sk->makeMediaLinkObj($nt, 
$text);
 
 1886                # Cloak with NOPARSE to avoid replacement in replaceExternalLinks 
 1887                $s .= $prefix . $this->armorLinks($link) . $trail;
 
 1888                $this->mOutput->addImage($nt->getDBkey());
 
 1891                $s .= $this->makeKnownLinkHolder($nt, 
$text, 
'', $trail, $prefix);
 
 1893            } elseif ($ns == NS_IMAGE) {
 
 1894                $img = 
new Image($nt);
 
 1895                if (
$img->exists()) {
 
 1899                    $s .= $this->makeKnownLinkHolder($nt, 
$text, 
'', $trail, $prefix);
 
 1900                    $this->mOutput->addLink($nt);
 
 1904            $s .= $this->makeLinkHolder($nt, 
$text, 
'', $trail, $prefix);
 
 1906        wfProfileOut($fname);
 
 1917    public function makeLinkHolder(&$nt, 
$text = 
'', 
$query = 
'', $trail = 
'', $prefix = 
'')
 
 1919        wfProfileIn(__METHOD__);
 
 1920        if (!is_object($nt)) {
 
 1922            $retVal = 
"<!-- ERROR -->{$prefix}{$text}{$trail}";
 
 1924            # Separate the link trail from the rest of the link 
 1925            list($inside, $trail) = Linker::splitTrail($trail);
 
 1927            if ($nt->isExternal()) {
 
 1928                $nr = array_push($this->mInterwikiLinkHolders[
'texts'], $prefix . 
$text . $inside);
 
 1929                $this->mInterwikiLinkHolders[
'titles'][] = $nt;
 
 1930                $retVal = 
'<!--IWLINK ' . ($nr - 1) . 
"-->{$trail}";
 
 1932                $nr = array_push($this->mLinkHolders[
'namespaces'], $nt->getNamespace());
 
 1933                $this->mLinkHolders[
'dbkeys'][] = $nt->getDBkey();
 
 1934                $this->mLinkHolders[
'queries'][] = 
$query;
 
 1935                $this->mLinkHolders[
'texts'][] = $prefix . 
$text . $inside;
 
 1936                $this->mLinkHolders[
'titles'][] = $nt;
 
 1938                $retVal = 
'<!--LINK ' . ($nr - 1) . 
"-->{$trail}";
 
 1941        wfProfileOut(__METHOD__);
 
 1959    public function makeKnownLinkHolder($nt, 
$text = 
'', 
$query = 
'', $trail = 
'', $prefix = 
'')
 
 1961        list($inside, $trail) = Linker::splitTrail($trail);
 
 1962        $sk = $this->mOptions->getSkin();
 
 1963        $link = $sk->makeKnownLinkObj($nt, 
$text, 
$query, $inside, $prefix);
 
 1964        return $this->armorLinks($link) . $trail;
 
 1979    public function armorLinks(
$text)
 
 1981        return preg_replace(
 
 1983            "{$this->mUniqPrefix}NOPARSE$1",
 
 1992    public function areSubpagesAllowed()
 
 1994        # Some namespaces don't allow subpages 
 1995        global $wgNamespacesWithSubpages;
 
 1996        return !empty($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]);
 
 2010        # :Foobar -- override special treatment of prefix (images, language links) 
 2011        # /Foobar -- convert to CurrentPage/Foobar 
 2012        # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text 
 2013        # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage 
 2014        # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage 
 2016        $fname = 
'Parser::maybeDoSubpageLink';
 
 2017        wfProfileIn($fname);
 
 2018        $ret = 
$target; # 
default return value is no change
 
 2023        # Some namespaces don't allow subpages, 
 2024        # so only perform processing if subpages are allowed 
 2025        if ($this->areSubpagesAllowed()) {
 
 2026            # Look at the first character 
 2028                # / at end means we don't want the slash to be shown 
 2029                $trailingSlashes = preg_match_all(
'%(/+)$%', 
$target, 
$m);
 
 2030                if ($trailingSlashes) {
 
 2033                    $noslash = substr(
$target, 1);
 
 2036                $ret = $this->mTitle->getPrefixedText() . 
'/' . trim($noslash);
 
 2039                } # 
this might be changed 
for ugliness reasons
 
 2041                # check for .. subpage backlinks 
 2044                while (strncmp($nodotdot, 
"../", 3) == 0) {
 
 2046                    $nodotdot = substr($nodotdot, 3);
 
 2048                if ($dotdotcount > 0) {
 
 2049                    $exploded = explode(
'/', $this->mTitle->GetPrefixedText());
 
 2050                    if (count($exploded) > $dotdotcount) { # not allowed to go below top level page
 
 2051                        $ret = implode(
'/', array_slice($exploded, 0, -$dotdotcount));
 
 2052                        # / at the end means don't show full path 
 2053                        if (substr($nodotdot, -1, 1) == 
'/') {
 
 2054                            $nodotdot = substr($nodotdot, 0, -1);
 
 2059                        $nodotdot = trim($nodotdot);
 
 2060                        if ($nodotdot != 
'') {
 
 2061                            $ret .= 
'/' . $nodotdot;
 
 2068        wfProfileOut($fname);
 
 2076     public function closeParagraph()
 
 2079        if (
'' != $this->mLastSection) {
 
 2080            $result = 
'</' . $this->mLastSection . 
">\n";
 
 2082        $this->mInPre = 
false;
 
 2083        $this->mLastSection = 
'';
 
 2086    # getCommon() returns the length of the longest common substring 
 2087    # of both arguments, starting at the beginning of both. 
 2089     public function getCommon($st1, $st2)
 
 2092        $shorter = strlen($st2);
 
 2093        if ($fl < $shorter) {
 
 2097        for (
$i = 0; 
$i < $shorter; ++
$i) {
 
 2098            if ($st1[
$i] != $st2[
$i]) {
 
 2104    # These next three functions open, continue, and close the list 
 2105    # element appropriate to the prefix character passed into them. 
 2107     public function openList($char)
 
 2109        $result = $this->closeParagraph();
 
 2113        } elseif (
'#' == $char) {
 
 2115        } elseif (
':' == $char) {
 
 2117        } elseif (
';' == $char) {
 
 2119            $this->mDTopen = 
true;
 
 2127     public function nextItem($char)
 
 2129        if (
'*' == $char || 
'#' == $char) {
 
 2131        } elseif (
':' == $char || 
';' == $char) {
 
 2133            if ($this->mDTopen) {
 
 2137                $this->mDTopen = 
true;
 
 2138                return $close . 
'<dt>';
 
 2140                $this->mDTopen = 
false;
 
 2141                return $close . 
'<dd>';
 
 2144        return '<!-- ERR 2 -->';
 
 2147     public function closeList($char)
 
 2150            $text = 
'</li></ul>';
 
 2151        } elseif (
'#' == $char) {
 
 2152            $text = 
'</li></ol>';
 
 2153        } elseif (
':' == $char) {
 
 2154            if ($this->mDTopen) {
 
 2155                $this->mDTopen = 
false;
 
 2156                $text = 
'</dt></dl>';
 
 2158                $text = 
'</dd></dl>';
 
 2161            return '<!-- ERR 3 -->';
 
 2163        return $text . 
"\n";
 
 2173    public function doBlockLevels(
$text, $linestart)
 
 2175        $fname = 
'Parser::doBlockLevels';
 
 2176        wfProfileIn($fname);
 
 2178        # Parsing through the text line by line.  The main thing 
 2179        # happening here is handling of block-level elements p, pre, 
 2180        # and making lists from lines starting with * # : etc. 
 2182        $textLines = explode(
"\n", 
$text);
 
 2185        $this->mDTopen = $inBlockElem = 
false;
 
 2187        $paragraphStack = 
false;
 
 2190            $output .= array_shift($textLines);
 
 2192        foreach ($textLines as $oLine) {
 
 2193            $lastPrefixLength = strlen($lastPrefix);
 
 2194            $preCloseMatch = preg_match(
'/<\\/pre/i', $oLine);
 
 2195            $preOpenMatch = preg_match(
'/<pre/i', $oLine);
 
 2196            if (!$this->mInPre) {
 
 2197                # Multiple prefixes may abut each other for nested lists. 
 2198                $prefixLength = strspn($oLine, 
'*#:;');
 
 2199                $pref = substr($oLine, 0, $prefixLength);
 
 2202                $pref2 = str_replace(
';', 
':', $pref);
 
 2203                $t = substr($oLine, $prefixLength);
 
 2204                $this->mInPre = !empty($preOpenMatch);
 
 2206                # Don't interpret any other prefixes in preformatted text 
 2208                $pref = $pref2 = 
'';
 
 2213            if ($prefixLength && 0 == strcmp($lastPrefix, $pref2)) {
 
 2214                # Same as the last item, so no need to deal with nesting or opening stuff 
 2215                $output .= $this->nextItem(substr($pref, -1));
 
 2216                $paragraphStack = 
false;
 
 2218                if (substr($pref, -1) == 
';') {
 
 2219                    # The one nasty exception: definition lists work like this: 
 2220                    # ; title : definition text 
 2221                    # So we check for : in the remainder text to split up the 
 2222                    # title and definition, without b0rking links. 
 2224                    if ($this->findColonNoLinks(
$t, $term, $t2) !== 
false) {
 
 2226                        $output .= $term . $this->nextItem(
':');
 
 2229            } elseif ($prefixLength || $lastPrefixLength) {
 
 2230                # Either open or close a level... 
 2231                $commonPrefixLength = $this->getCommon($pref, $lastPrefix);
 
 2232                $paragraphStack = 
false;
 
 2234                while ($commonPrefixLength < $lastPrefixLength) {
 
 2235                    $output .= $this->closeList($lastPrefix[$lastPrefixLength - 1]);
 
 2236                    --$lastPrefixLength;
 
 2238                if ($prefixLength <= $commonPrefixLength && $commonPrefixLength > 0) {
 
 2239                    $output .= $this->nextItem($pref[$commonPrefixLength - 1]);
 
 2241                while ($prefixLength > $commonPrefixLength) {
 
 2242                    $char = substr($pref, $commonPrefixLength, 1);
 
 2243                    $output .= $this->openList($char);
 
 2246                        # FIXME: This is dupe of code above 
 2247                        if ($this->findColonNoLinks(
$t, $term, $t2) !== 
false) {
 
 2249                            $output .= $term . $this->nextItem(
':');
 
 2252                    ++$commonPrefixLength;
 
 2254                $lastPrefix = $pref2;
 
 2256            if (0 == $prefixLength) {
 
 2257                wfProfileIn(
"$fname-paragraph");
 
 2258                # No prefix (not in list)--go to paragraph mode 
 2260                $openmatch = preg_match(
'/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', 
$t);
 
 2261                $closematch = preg_match(
 
 2262                    '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
 
 2263                    '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|' . $this->mUniqPrefix . 
'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS',
 
 2266                if ($openmatch or $closematch) {
 
 2267                    $paragraphStack = 
false;
 
 2268                    # TODO bug 5718: paragraph closed 
 2269                    $output .= $this->closeParagraph();
 
 2270                    if ($preOpenMatch and !$preCloseMatch) {
 
 2271                        $this->mInPre = 
true;
 
 2274                        $inBlockElem = 
false;
 
 2276                        $inBlockElem = 
true;
 
 2278                } elseif (!$inBlockElem && !$this->mInPre) {
 
 2279                    if (
' ' == 
$t[0] and ($this->mLastSection == 
'pre' or trim(
$t) != 
'')) {
 
 2281                        if ($this->mLastSection != 
'pre') {
 
 2282                            $paragraphStack = 
false;
 
 2283                            $output .= $this->closeParagraph() . 
'<pre>';
 
 2284                            $this->mLastSection = 
'pre';
 
 2289                        if (
'' == trim(
$t)) {
 
 2290                            if ($paragraphStack) {
 
 2291                                $output .= $paragraphStack . 
'<br />';
 
 2292                                $paragraphStack = 
false;
 
 2293                                $this->mLastSection = 
'p';
 
 2295                                if ($this->mLastSection != 
'p') {
 
 2296                                    $output .= $this->closeParagraph();
 
 2297                                    $this->mLastSection = 
'';
 
 2298                                    $paragraphStack = 
'<p>';
 
 2300                                    $paragraphStack = 
'</p><p>';
 
 2304                            if ($paragraphStack) {
 
 2306                                $paragraphStack = 
false;
 
 2307                                $this->mLastSection = 
'p';
 
 2308                            } elseif ($this->mLastSection != 
'p') {
 
 2309                                $output .= $this->closeParagraph() . 
'<p>';
 
 2310                                $this->mLastSection = 
'p';
 
 2315                wfProfileOut(
"$fname-paragraph");
 
 2318            if ($preCloseMatch && $this->mInPre) {
 
 2319                $this->mInPre = 
false;
 
 2321            if ($paragraphStack === 
false) {
 
 2325        while ($prefixLength) {
 
 2326            $output .= $this->closeList($pref2[$prefixLength - 1]);
 
 2329        if (
'' != $this->mLastSection) {
 
 2330            $output .= 
'</' . $this->mLastSection . 
'>';
 
 2331            $this->mLastSection = 
'';
 
 2334        wfProfileOut($fname);
 
 2346    public function findColonNoLinks($str, &$before, &$after)
 
 2348        $fname = 
'Parser::findColonNoLinks';
 
 2349        wfProfileIn($fname);
 
 2351        $pos = strpos($str, 
':');
 
 2352        if ($pos === 
false) {
 
 2354            wfProfileOut($fname);
 
 2358        $lt = strpos($str, 
'<');
 
 2359        if ($lt === 
false || $lt > $pos) {
 
 2361            $before = substr($str, 0, $pos);
 
 2362            $after = substr($str, $pos + 1);
 
 2363            wfProfileOut($fname);
 
 2370        $len = strlen($str);
 
 2371        for (
$i = 0; 
$i < $len; 
$i++) {
 
 2385                        $before = substr($str, 0, 
$i);
 
 2386                        $after = substr($str, 
$i + 1);
 
 2387                        wfProfileOut($fname);
 
 2394                    $colon = strpos($str, 
':', 
$i);
 
 2395                    if ($colon === 
false) {
 
 2397                        wfProfileOut($fname);
 
 2400                    $lt = strpos($str, 
'<', 
$i);
 
 2402                        if ($lt === 
false || $colon < $lt) {
 
 2404                            $before = substr($str, 0, $colon);
 
 2405                            $after = substr($str, $colon + 1);
 
 2406                            wfProfileOut($fname);
 
 2410                    if ($lt === 
false) {
 
 2456                        wfDebug(
"Invalid input in $fname; too many close tags\n");
 
 2457                        wfProfileOut($fname);
 
 2492                throw new MWException(
"State machine error in $fname");
 
 2496            wfDebug(
"Invalid input in $fname; not enough close tags (stack $stack, state $state)\n");
 
 2499        wfProfileOut($fname);
 
 2508    public function getVariableValue(
$index)
 
 2510        global $wgContLang, $wgSitename, $wgServer, $wgServerName, $wgScriptPath;
 
 2516        static $varCache = array();
 
 2517        if (wfRunHooks(
'ParserGetVariableValueVarCache', array( &$this, &$varCache ))) {
 
 2518            if (isset($varCache[
$index])) {
 
 2519                return $varCache[
$index];
 
 2524        wfRunHooks(
'ParserGetVariableValueTs', array( &$this, &$ts ));
 
 2527        global $wgLocaltimezone;
 
 2528        if (isset($wgLocaltimezone)) {
 
 2529            $oldtz = getenv(
'TZ');
 
 2530            putenv(
'TZ=' . $wgLocaltimezone);
 
 2532        $localTimestamp = date(
'YmdHis', $ts);
 
 2533        $localMonth = date(
'm', $ts);
 
 2534        $localMonthName = date(
'n', $ts);
 
 2535        $localDay = date(
'j', $ts);
 
 2536        $localDay2 = date(
'd', $ts);
 
 2537        $localDayOfWeek = date(
'w', $ts);
 
 2538        $localWeek = date(
'W', $ts);
 
 2539        $localYear = date(
'Y', $ts);
 
 2540        $localHour = date(
'H', $ts);
 
 2541        if (isset($wgLocaltimezone)) {
 
 2542            putenv(
'TZ=' . $oldtz);
 
 2546            case 'currentmonth':
 
 2547                return $varCache[
$index] = $wgContLang->formatNum(date(
'm', $ts));
 
 2548            case 'currentmonthname':
 
 2549                return $varCache[
$index] = $wgContLang->getMonthName(date(
'n', $ts));
 
 2550            case 'currentmonthnamegen':
 
 2551                return $varCache[
$index] = $wgContLang->getMonthNameGen(date(
'n', $ts));
 
 2552            case 'currentmonthabbrev':
 
 2553                return $varCache[
$index] = $wgContLang->getMonthAbbreviation(date(
'n', $ts));
 
 2555                return $varCache[
$index] = $wgContLang->formatNum(date(
'j', $ts));
 
 2557                return $varCache[
$index] = $wgContLang->formatNum(date(
'd', $ts));
 
 2559                return $varCache[
$index] = $wgContLang->formatNum($localMonth);
 
 2560            case 'localmonthname':
 
 2561                return $varCache[
$index] = $wgContLang->getMonthName($localMonthName);
 
 2562            case 'localmonthnamegen':
 
 2563                return $varCache[
$index] = $wgContLang->getMonthNameGen($localMonthName);
 
 2564            case 'localmonthabbrev':
 
 2565                return $varCache[
$index] = $wgContLang->getMonthAbbreviation($localMonthName);
 
 2567                return $varCache[
$index] = $wgContLang->formatNum($localDay);
 
 2569                return $varCache[
$index] = $wgContLang->formatNum($localDay2);
 
 2571                return $this->mTitle->getText();
 
 2573                return $this->mTitle->getPartialURL();
 
 2574            case 'fullpagename':
 
 2575                return $this->mTitle->getPrefixedText();
 
 2576            case 'fullpagenamee':
 
 2577                return $this->mTitle->getPrefixedURL();
 
 2579                return $this->mTitle->getSubpageText();
 
 2580            case 'subpagenamee':
 
 2581                return $this->mTitle->getSubpageUrlForm();
 
 2582            case 'basepagename':
 
 2583                return $this->mTitle->getBaseText();
 
 2584            case 'basepagenamee':
 
 2585                return wfUrlEncode(str_replace(
' ', 
'_', $this->mTitle->getBaseText()));
 
 2586            case 'talkpagename':
 
 2587                if ($this->mTitle->canTalk()) {
 
 2588                    $talkPage = $this->mTitle->getTalkPage();
 
 2589                    return $talkPage->getPrefixedText();
 
 2594            case 'talkpagenamee':
 
 2595                if ($this->mTitle->canTalk()) {
 
 2596                    $talkPage = $this->mTitle->getTalkPage();
 
 2597                    return $talkPage->getPrefixedUrl();
 
 2602            case 'subjectpagename':
 
 2603                $subjPage = $this->mTitle->getSubjectPage();
 
 2604                return $subjPage->getPrefixedText();
 
 2605            case 'subjectpagenamee':
 
 2606                $subjPage = $this->mTitle->getSubjectPage();
 
 2607                return $subjPage->getPrefixedUrl();
 
 2609                return $this->mRevisionId;
 
 2611                return intval(substr($this->getRevisionTimestamp(), 6, 2));
 
 2612            case 'revisionday2':
 
 2613                return substr($this->getRevisionTimestamp(), 6, 2);
 
 2614            case 'revisionmonth':
 
 2615                return intval(substr($this->getRevisionTimestamp(), 4, 2));
 
 2616            case 'revisionyear':
 
 2617                return substr($this->getRevisionTimestamp(), 0, 4);
 
 2618            case 'revisiontimestamp':
 
 2619                return $this->getRevisionTimestamp();
 
 2621                return str_replace(
'_', 
' ', $wgContLang->getNsText($this->mTitle->getNamespace()));
 
 2623                return wfUrlencode($wgContLang->getNsText($this->mTitle->getNamespace()));
 
 2625                return $this->mTitle->canTalk() ? str_replace(
'_', 
' ', $this->mTitle->getTalkNsText()) : 
'';
 
 2627                return $this->mTitle->canTalk() ? wfUrlencode($this->mTitle->getTalkNsText()) : 
'';
 
 2628            case 'subjectspace':
 
 2629                return $this->mTitle->getSubjectNsText();
 
 2630            case 'subjectspacee':
 
 2631                return(wfUrlencode($this->mTitle->getSubjectNsText()));
 
 2632            case 'currentdayname':
 
 2633                return $varCache[
$index] = $wgContLang->getWeekdayName(date(
'w', $ts) + 1);
 
 2635                return $varCache[
$index] = $wgContLang->formatNum(date(
'Y', $ts), 
true);
 
 2637                return $varCache[
$index] = $wgContLang->time(wfTimestamp(TS_MW, $ts), 
false, 
false);
 
 2639                return $varCache[
$index] = $wgContLang->formatNum(date(
'H', $ts), 
true);
 
 2643                return $varCache[
$index] = $wgContLang->formatNum((
int) date(
'W', $ts));
 
 2645                return $varCache[
$index] = $wgContLang->formatNum(date(
'w', $ts));
 
 2646            case 'localdayname':
 
 2647                return $varCache[
$index] = $wgContLang->getWeekdayName($localDayOfWeek + 1);
 
 2649                return $varCache[
$index] = $wgContLang->formatNum($localYear, 
true);
 
 2651                return $varCache[
$index] = $wgContLang->time($localTimestamp, 
false, 
false);
 
 2653                return $varCache[
$index] = $wgContLang->formatNum($localHour, 
true);
 
 2657                return $varCache[
$index] = $wgContLang->formatNum((
int) $localWeek);
 
 2659                return $varCache[
$index] = $wgContLang->formatNum($localDayOfWeek);
 
 2660            case 'numberofarticles':
 
 2661                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::articles());
 
 2662            case 'numberoffiles':
 
 2663                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::images());
 
 2664            case 'numberofusers':
 
 2665                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::users());
 
 2666            case 'numberofpages':
 
 2667                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::pages());
 
 2668            case 'numberofadmins':
 
 2669                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::admins());
 
 2670            case 'numberofedits':
 
 2671                return $varCache[
$index] = $wgContLang->formatNum(SiteStats::edits());
 
 2672            case 'currenttimestamp':
 
 2673                return $varCache[
$index] = wfTimestampNow();
 
 2674            case 'localtimestamp':
 
 2675                return $varCache[
$index] = $localTimestamp;
 
 2676            case 'currentversion':
 
 2677                return $varCache[
$index] = SpecialVersion::getVersion();
 
 2683                return $wgServerName;
 
 2685                return $wgScriptPath;
 
 2686            case 'directionmark':
 
 2687                return $wgContLang->getDirMark();
 
 2688            case 'contentlanguage':
 
 2689                global $wgContLanguageCode;
 
 2690                return $wgContLanguageCode;
 
 2693                if (wfRunHooks(
'ParserGetVariableValueSwitch', array( &$this, &$varCache, &
$index, &
$ret ))) {
 
 2706    public function initialiseVariables()
 
 2708        $fname = 
'Parser::initialiseVariables';
 
 2709        wfProfileIn($fname);
 
 2710        $variableIDs = MagicWord::getVariableIDs();
 
 2712        $this->mVariables = array();
 
 2713        foreach ($variableIDs as 
$id) {
 
 2715            $mw->addToArray($this->mVariables, 
$id);
 
 2717        wfProfileOut($fname);
 
 2736    public function replace_callback(
$text, $callbacks)
 
 2738        wfProfileIn(__METHOD__);
 
 2739        $openingBraceStack = array();   # 
this array will hold a stack of parentheses which are not closed yet
 
 2740        $lastOpeningBrace = -1;                 # last not closed parentheses
 
 2742        $validOpeningBraces = implode(
'', array_keys($callbacks));
 
 2746            # Find next opening brace, closing brace or pipe 
 2747            if ($lastOpeningBrace == -1) {
 
 2748                $currentClosing = 
'';
 
 2749                $search = $validOpeningBraces;
 
 2751                $currentClosing = $openingBraceStack[$lastOpeningBrace][
'braceEnd'];
 
 2752                $search = $validOpeningBraces . 
'|' . $currentClosing;
 
 2759                } elseif (
$text[
$i] == $currentClosing) {
 
 2761                } elseif (isset($callbacks[
$text[
$i]])) {
 
 2765                    # Some versions of PHP have a strcspn which stops on null characters 
 2766                    # Ignore and continue 
 2775            if ($found == 
'open') {
 
 2776                # found opening brace, let's add it to parentheses stack 
 2777                $piece = array(
'brace' => 
$text[
$i],
 
 2778                               'braceEnd' => 
$rule[
'end'],
 
 2782                # count opening brace characters 
 2783                $piece[
'count'] = strspn(
$text, $piece[
'brace'], 
$i);
 
 2784                $piece[
'startAt'] = $piece[
'partStart'] = 
$i + $piece[
'count'];
 
 2785                $i += $piece[
'count'];
 
 2787                # we need to add to stack only if opening brace count is enough for one of the rules 
 2788                if ($piece[
'count'] >= 
$rule[
'min']) {
 
 2789                    $lastOpeningBrace++;
 
 2790                    $openingBraceStack[$lastOpeningBrace] = $piece;
 
 2792            } elseif ($found == 
'close') {
 
 2793                # lets check if it is enough characters for closing brace 
 2794                $maxCount = $openingBraceStack[$lastOpeningBrace][
'count'];
 
 2797                # check for maximum matching characters (if there are 5 closing 
 2798                # characters, we will probably need only 3 - depending on the rules) 
 2800                $matchingCallback = 
null;
 
 2801                $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace][
'brace']];
 
 2802                if ($count > $cbType[
'max']) {
 
 2803                    # The specified maximum exists in the callback array, unless the caller 
 2805                    $matchingCount = $cbType[
'max'];
 
 2807                    # Count is less than the maximum 
 2808                    # Skip any gaps in the callback array to find the true largest match 
 2809                    # Need to use array_key_exists not isset because the callback can be null 
 2810                    $matchingCount = $count;
 
 2811                    while ($matchingCount > 0 && !array_key_exists($matchingCount, $cbType[
'cb'])) {
 
 2816                if ($matchingCount <= 0) {
 
 2820                $matchingCallback = $cbType[
'cb'][$matchingCount];
 
 2822                # let's set a title or last part (if '|' was found) 
 2823                if (
null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
 
 2824                    $openingBraceStack[$lastOpeningBrace][
'title'] =
 
 2827                            $openingBraceStack[$lastOpeningBrace][
'partStart'],
 
 2828                            $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
 
 2831                    $openingBraceStack[$lastOpeningBrace][
'parts'][] =
 
 2834                            $openingBraceStack[$lastOpeningBrace][
'partStart'],
 
 2835                            $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
 
 2839                $pieceStart = $openingBraceStack[$lastOpeningBrace][
'startAt'] - $matchingCount;
 
 2840                $pieceEnd = 
$i + $matchingCount;
 
 2842                if (is_callable($matchingCallback)) {
 
 2844                                     'text' => substr(
$text, $pieceStart, $pieceEnd - $pieceStart),
 
 2845                                     'title' => trim($openingBraceStack[$lastOpeningBrace][
'title']),
 
 2846                                     'parts' => $openingBraceStack[$lastOpeningBrace][
'parts'],
 
 2847                                     'lineStart' => (($pieceStart > 0) && (
$text[$pieceStart - 1] == 
"\n")),
 
 2849                    # finally we can call a user callback and replace piece of text 
 2850                    $replaceWith = call_user_func($matchingCallback, $cbArgs);
 
 2851                    $text = substr(
$text, 0, $pieceStart) . $replaceWith . substr(
$text, $pieceEnd);
 
 2852                    $i = $pieceStart + strlen($replaceWith);
 
 2854                    # null value for callback means that parentheses should be parsed, but not replaced 
 2855                    $i += $matchingCount;
 
 2858                # reset last opening parentheses, but keep it in case there are unused characters 
 2859                $piece = array(
'brace' => $openingBraceStack[$lastOpeningBrace][
'brace'],
 
 2860                               'braceEnd' => $openingBraceStack[$lastOpeningBrace][
'braceEnd'],
 
 2861                               'count' => $openingBraceStack[$lastOpeningBrace][
'count'],
 
 2864                               'startAt' => $openingBraceStack[$lastOpeningBrace][
'startAt']);
 
 2865                $openingBraceStack[$lastOpeningBrace--] = 
null;
 
 2867                if ($matchingCount < $piece[
'count']) {
 
 2868                    $piece[
'count'] -= $matchingCount;
 
 2869                    $piece[
'startAt'] -= $matchingCount;
 
 2870                    $piece[
'partStart'] = $piece[
'startAt'];
 
 2871                    # do we still qualify for any callback with remaining count? 
 2872                    $currentCbList = $callbacks[$piece[
'brace']][
'cb'];
 
 2873                    while ($piece[
'count']) {
 
 2874                        if (array_key_exists($piece[
'count'], $currentCbList)) {
 
 2875                            $lastOpeningBrace++;
 
 2876                            $openingBraceStack[$lastOpeningBrace] = $piece;
 
 2882            } elseif ($found == 
'pipe') {
 
 2883                # lets set a title if it is a first separator, or next part otherwise 
 2884                if (
null === $openingBraceStack[$lastOpeningBrace][
'parts']) {
 
 2885                    $openingBraceStack[$lastOpeningBrace][
'title'] =
 
 2888                            $openingBraceStack[$lastOpeningBrace][
'partStart'],
 
 2889                            $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
 
 2891                    $openingBraceStack[$lastOpeningBrace][
'parts'] = array();
 
 2893                    $openingBraceStack[$lastOpeningBrace][
'parts'][] =
 
 2896                            $openingBraceStack[$lastOpeningBrace][
'partStart'],
 
 2897                            $i - $openingBraceStack[$lastOpeningBrace][
'partStart']
 
 2900                $openingBraceStack[$lastOpeningBrace][
'partStart'] = ++
$i;
 
 2904        wfProfileOut(__METHOD__);
 
 2923    public function replaceVariables(
$text, $args = array(), $argsOnly = 
false)
 
 2925        # Prevent too big inclusions 
 2926        if (strlen(
$text) > $this->mOptions->getMaxIncludeSize()) {
 
 2930        $fname = __METHOD__ ;
 
 2931        wfProfileIn($fname);
 
 2933        # This function is called recursively. To keep track of arguments we need a stack: 
 2934        array_push($this->mArgStack, $args);
 
 2936        $braceCallbacks = array();
 
 2938            $braceCallbacks[2] = array( &$this, 
'braceSubstitution' );
 
 2940        if ($this->mOutputType != 
OT_MSG) {
 
 2941            $braceCallbacks[3] = array( &$this, 
'argSubstitution' );
 
 2943        if ($braceCallbacks) {
 
 2947                    'cb' => $braceCallbacks,
 
 2948                    'min' => $argsOnly ? 3 : 2,
 
 2949                    'max' => isset($braceCallbacks[3]) ? 3 : 2,
 
 2953                    'cb' => array(2 => 
null),
 
 2958            $text = $this->replace_callback(
$text, $callbacks);
 
 2960            array_pop($this->mArgStack);
 
 2962        wfProfileOut($fname);
 
 2970    public function variableSubstitution($matches)
 
 2973        $fname = 
'Parser::variableSubstitution';
 
 2974        $varname = $wgContLang->lc($matches[1]);
 
 2975        wfProfileIn($fname);
 
 2977        if ($this->mOutputType == 
OT_WIKI) {
 
 2978            # Do only magic variables prefixed by SUBST 
 2980            if (!$mwSubst->matchStartAndRemove($varname)) {
 
 2983            # Note that if we don't substitute the variable below, 
 2984            # we don't remove the {{subst:}} magic word, in case 
 2985            # it is a template rather than a magic variable. 
 2987        if (!$skip && array_key_exists($varname, $this->mVariables)) {
 
 2988            $id = $this->mVariables[$varname];
 
 2989            # Now check if we did really match, case sensitive or not 
 2991            if ($mw->match($matches[1])) {
 
 2992                $text = $this->getVariableValue(
$id);
 
 2993                $this->mOutput->mContainsOldMagic = 
true;
 
 2995                $text = $matches[0];
 
 2998            $text = $matches[0];
 
 3000        wfProfileOut($fname);
 
 3006    public static function createAssocArgs($args)
 
 3008        $assocArgs = array();
 
 3010        foreach ($args as $arg) {
 
 3011            $eqpos = strpos($arg, 
'=');
 
 3012            if ($eqpos === 
false) {
 
 3013                $assocArgs[
$index++] = $arg;
 
 3015                $name = trim(substr($arg, 0, $eqpos));
 
 3016                $value = trim(substr($arg, $eqpos + 1));
 
 3017                if ($value === 
false) {
 
 3020                if (
$name !== 
false) {
 
 3021                    $assocArgs[
$name] = $value;
 
 3040    public function braceSubstitution($piece)
 
 3042        global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces;
 
 3043        $fname = __METHOD__ ;
 
 3044        wfProfileIn($fname);
 
 3045        wfProfileIn(__METHOD__ . 
'-setup');
 
 3048        $found = 
false;             # 
$text has been filled
 
 3049        $nowiki = 
false;            # wiki markup in 
$text should be escaped
 
 3050        $noparse = 
false;           # Unsafe HTML tags should not be stripped, etc.
 
 3051        $noargs = 
false;            # Don
't replace triple-brace arguments in $text 
 3052        $replaceHeadings = false;   # Make the edit section links go to the template not the article 
 3053                $headingOffset = 0;         # Skip headings when number, to account for those that weren't transcluded.
 
 3054        $isHTML = 
false;            # 
$text is HTML, armour it against wikitext transformation
 
 3055        $forceRawInterwiki = 
false; # Force interwiki transclusion to be done in raw mode not rendered
 
 3057        # Title object, where $text came from 
 3063        # $part1 is the bit before the first |, and must contain only title characters 
 3064        # $args is a list of arguments, starting from index 0, not including $part1 
 3066        $titleText = $part1 = $piece[
'title'];
 
 3067        # If the third subpattern matched anything, it will start with | 
 3069        if (
null == $piece[
'parts']) {
 
 3070            $replaceWith = $this->variableSubstitution(array($piece[
'text'], $piece[
'title']));
 
 3071            if ($replaceWith != $piece[
'text']) {
 
 3072                $text = $replaceWith;
 
 3079        $args = (
null == $piece[
'parts']) ? array() : $piece[
'parts'];
 
 3080        wfProfileOut(__METHOD__ . 
'-setup');
 
 3083        wfProfileIn(__METHOD__ . 
'-modifiers');
 
 3086            if ($mwSubst->matchStartAndRemove($part1) xor $this->ot[
'wiki']) {
 
 3087                # One of two possibilities is true: 
 3088                # 1) Found SUBST but not in the PST phase 
 3089                # 2) Didn't find SUBST and in the PST phase 
 3090                # In either case, return without further processing 
 3091                $text = $piece[
'text'];
 
 3098        # MSG, MSGNW and RAW 
 3102            if ($mwMsgnw->matchStartAndRemove($part1)) {
 
 3105                # Remove obsolete MSG: 
 3107                $mwMsg->matchStartAndRemove($part1);
 
 3112            if ($mwRaw->matchStartAndRemove($part1)) {
 
 3113                $forceRawInterwiki = 
true;
 
 3116        wfProfileOut(__METHOD__ . 
'-modifiers');
 
 3119        $lastPathLevel = $this->mTemplatePath;
 
 3123            wfProfileIn(__METHOD__ . 
'-pfunc');
 
 3125            $colonPos = strpos($part1, 
':');
 
 3126            if ($colonPos !== 
false) {
 
 3127                # Case sensitive functions 
 3128                $function = substr($part1, 0, $colonPos);
 
 3129                if (isset($this->mFunctionSynonyms[1][$function])) {
 
 3130                    $function = $this->mFunctionSynonyms[1][$function];
 
 3132                    # Case insensitive functions 
 3133                    $function = strtolower($function);
 
 3134                    if (isset($this->mFunctionSynonyms[0][$function])) {
 
 3135                        $function = $this->mFunctionSynonyms[0][$function];
 
 3141                    $funcArgs = array_map(
'trim', $args);
 
 3142                    $funcArgs = array_merge(array( &$this, trim(substr($part1, $colonPos + 1)) ), $funcArgs);
 
 3143                    $result = call_user_func_array($this->mFunctionHooks[$function], $funcArgs);
 
 3164            wfProfileOut(__METHOD__ . 
'-pfunc');
 
 3167        # Template table test 
 3169        # Did we encounter this template already? If yes, it is in the cache 
 3170        # and we need to check for loops. 
 3171        if (!$found && isset($this->mTemplates[$piece[
'title']])) {
 
 3174            # Infinite loop test 
 3175            if (isset($this->mTemplatePath[$part1])) {
 
 3179                $text = $linestart .
 
 3180                    "[[$part1]]<!-- WARNING: template loop detected -->";
 
 3181                wfDebug(__METHOD__ . 
": template loop broken at '$part1'\n");
 
 3183                # set $text to cached message. 
 3184                $text = $linestart . $this->mTemplates[$piece[
'title']];
 
 3185                #treat title for cached page the same as others 
 3188                $part1 = $this->maybeDoSubpageLink($part1, $subpage);
 
 3189                if ($subpage !== 
'') {
 
 3190                    $ns = $this->mTitle->getNamespace();
 
 3194                $titleText = 
$title->getPrefixedText();
 
 3196                $replaceHeadings = 
true;
 
 3200        # Load from database 
 3202            wfProfileIn(__METHOD__ . 
'-loadtpl');
 
 3204            # declaring $subpage directly in the function call 
 3205            # does not work correctly with references and breaks 
 3206            # {{/subpage}}-style inclusions 
 3208            $part1 = $this->maybeDoSubpageLink($part1, $subpage);
 
 3209            if ($subpage !== 
'') {
 
 3210                $ns = $this->mTitle->getNamespace();
 
 3216                $titleText = 
$title->getPrefixedText();
 
 3217                # Check for language variants if the template is not found 
 3218                if ($wgContLang->hasVariants() && 
$title->getArticleID() == 0) {
 
 3219                    $wgContLang->findVariantLink($part1, 
$title);
 
 3222                if (!
$title->isExternal()) {
 
 3223                    if (
$title->getNamespace() == 
NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot[
'html']) {
 
 3225                        if (is_string(
$text)) {
 
 3230                            $this->disableCache();
 
 3232                    } elseif ($wgNonincludableNamespaces && in_array(
$title->getNamespace(), $wgNonincludableNamespaces)) {
 
 3234                        wfDebug(
"$fname: template inclusion denied for " . 
$title->getPrefixedDBkey());
 
 3236                        $articleContent = $this->fetchTemplate(
$title);
 
 3237                        if ($articleContent !== 
false) {
 
 3239                            $text = $articleContent;
 
 3240                            $replaceHeadings = 
true;
 
 3244                    # If the title is valid but undisplayable, make a link to it 
 3245                    if (!$found && ($this->ot[
'html'] || $this->ot[
'pre'])) {
 
 3246                        $text = 
"[[:$titleText]]";
 
 3249                } elseif (
$title->isTrans()) {
 
 3251                    if ($this->ot[
'html'] && !$forceRawInterwiki) {
 
 3252                        $text = $this->interwikiTransclude(
$title, 
'render');
 
 3256                        $text = $this->interwikiTransclude(
$title, 
'raw');
 
 3257                        $replaceHeadings = 
true;
 
 3262                # Template cache array insertion 
 3263                # Use the original $piece['title'] not the mangled $part1, so that 
 3264                # modifiers such as RAW: produce separate cache entries 
 3269                        $this->mTemplates[$piece[
'title']] = 
$text;
 
 3274            wfProfileOut(__METHOD__ . 
'-loadtpl');
 
 3277        if ($found && !$this->incrementIncludeSize(
'pre-expand', strlen(
$text))) {
 
 3278            # Error, oversize inclusion 
 3279            $text = $linestart .
 
 3280                "[[$titleText]]<!-- WARNING: template omitted, pre-expand include size too large -->";
 
 3285        # Recursive parsing, escaping and link table handling 
 3286        # Only for HTML output 
 3287        if ($nowiki && $found && ($this->ot[
'html'] || $this->ot[
'pre'])) {
 
 3289        } elseif (!$this->ot[
'msg'] && $found) {
 
 3291                $assocArgs = array();
 
 3293                # Clean up argument array 
 3294                $assocArgs = self::createAssocArgs($args);
 
 3295                # Add a new element to the templace recursion path 
 3296                $this->mTemplatePath[$part1] = 1;
 
 3300                # If there are any <onlyinclude> tags, only include them 
 3301                if (in_string(
'<onlyinclude>', 
$text) && in_string(
'</onlyinclude>', 
$text)) {
 
 3303                    StringUtils::delimiterReplaceCallback(
 
 3306                        array( &$replacer, 
'replace' ),
 
 3309                    $text = $replacer->output;
 
 3311                # Remove <noinclude> sections and <includeonly> tags 
 3312                $text = StringUtils::delimiterReplace(
'<noinclude>', 
'</noinclude>', 
'', 
$text);
 
 3313                $text = strtr(
$text, array( 
'<includeonly>' => 
'' , 
'</includeonly>' => 
'' ));
 
 3315                if ($this->ot[
'html'] || $this->ot[
'pre']) {
 
 3316                    # Strip <nowiki>, <pre>, etc. 
 3317                    $text = $this->strip(
$text, $this->mStripState);
 
 3318                    if ($this->ot[
'html']) {
 
 3320                    } elseif ($this->ot[
'pre'] && $this->mOptions->getRemoveComments()) {
 
 3324                $text = $this->replaceVariables(
$text, $assocArgs);
 
 3326                # If the template begins with a table or block-level 
 3327                # element, it should be treated as beginning a new line. 
 3328                if (!$piece[
'lineStart'] && preg_match(
'/^(?:{\\||:|;|#|\*)/', 
$text)) {
 
 3331            } elseif (!$noargs) {
 
 3332                # $noparse and !$noargs 
 3333                # Just replace the arguments, not any double-brace items 
 3334                # This is used for rendered interwiki transclusion 
 3335                $text = $this->replaceVariables(
$text, $assocArgs, 
true);
 
 3338        # Prune lower levels off the recursion check path 
 3339        $this->mTemplatePath = $lastPathLevel;
 
 3341        if ($found && !$this->incrementIncludeSize(
'post-expand', strlen(
$text))) {
 
 3342            # Error, oversize inclusion 
 3343            $text = $linestart .
 
 3344                "[[$titleText]]<!-- WARNING: template omitted, post-expand include size too large -->";
 
 3350            wfProfileOut($fname);
 
 3351            return $piece[
'text'];
 
 3353            wfProfileIn(__METHOD__ . 
'-placeholders');
 
 3355                # Replace raw HTML by a placeholder 
 3356                # Add a blank line preceding, to prevent it from mucking up 
 3357                # immediately preceding headings 
 3358                $text = 
"\n\n" . $this->insertStripItem(
$text, $this->mStripState);
 
 3360                # replace ==section headers== 
 3361                # XXX this needs to go away once we have a better parser. 
 3362                if (!$this->ot[
'wiki'] && !$this->ot[
'pre'] && $replaceHeadings) {
 
 3364                        $encodedname = base64_encode(
$title->getPrefixedDBkey());
 
 3366                        $encodedname = base64_encode(
"");
 
 3369                        '/(^={1,6}.*?={1,6}\s*?$)/m',
 
 3375                    $nsec = $headingOffset;
 
 3376                    for (
$i = 0; 
$i < count(
$m); 
$i += 2) {
 
 3378                        if (!isset(
$m[
$i + 1]) || 
$m[
$i + 1] == 
"") {
 
 3382                        if (strstr($hl, 
"<!--MWTEMPLATESECTION")) {
 
 3387                        preg_match(
'/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2);
 
 3388                        $text .= $m2[1] . $m2[2] . 
"<!--MWTEMPLATESECTION=" 
 3389                            . $encodedname . 
"&" . base64_encode(
"$nsec") . 
"-->" . $m2[3];
 
 3395            wfProfileOut(__METHOD__ . 
'-placeholders');
 
 3398        # Prune lower levels off the recursion check path 
 3399        $this->mTemplatePath = $lastPathLevel;
 
 3402            wfProfileOut($fname);
 
 3403            return $piece[
'text'];
 
 3405            wfProfileOut($fname);
 
 3413    public function fetchTemplate(
$title)
 
 3418            $rev = Revision::newFromTitle(
$title);
 
 3419            $this->mOutput->addTemplate(
$title, 
$title->getArticleID());
 
 3421                $text = $rev->getText();
 
 3422            } elseif (
$title->getNamespace() == NS_MEDIAWIKI) {
 
 3433            if (
$text === 
false) {
 
 3447        global $wgEnableScaryTranscluding;
 
 3449        if (!$wgEnableScaryTranscluding) {
 
 3450            return wfMsg(
'scarytranscludedisabled');
 
 3455        if (strlen(
$url) > 255) {
 
 3456            return wfMsg(
'scarytranscludetoolong');
 
 3458        return $this->fetchScaryTemplateMaybeFromCache(
$url);
 
 3461    public function fetchScaryTemplateMaybeFromCache(
$url)
 
 3463        global $wgTranscludeCacheExpiry;
 
 3464        $dbr = wfGetDB(DB_SLAVE);
 
 3465        $obj = $dbr->selectRow(
 
 3467            array(
'tc_time', 
'tc_contents'),
 
 3468            array(
'tc_url' => 
$url)
 
 3471            $time = $obj->tc_time;
 
 3472            $text = $obj->tc_contents;
 
 3473            if (
$time && time() < 
$time + $wgTranscludeCacheExpiry) {
 
 3480            return wfMsg(
'scarytranscludefailed', 
$url);
 
 3483        $dbw = wfGetDB(DB_MASTER);
 
 3484        $dbw->replace(
'transcache', array(
'tc_url'), array(
 
 3486            'tc_time' => time(),
 
 3487            'tc_contents' => 
$text));
 
 3496    public function argSubstitution($matches)
 
 3498        $arg = trim($matches[
'title']);
 
 3499        $text = $matches[
'text'];
 
 3500        $inputArgs = end($this->mArgStack);
 
 3502        if (array_key_exists($arg, $inputArgs)) {
 
 3503            $text = $inputArgs[$arg];
 
 3505        null != $matches[
'parts'] && count($matches[
'parts']) > 0) {
 
 3506            $text = $matches[
'parts'][0];
 
 3508        if (!$this->incrementIncludeSize(
'arg', strlen(
$text))) {
 
 3509            $text = $matches[
'text'] .
 
 3510                '<!-- WARNING: argument omitted, expansion size too large -->';
 
 3523    public function incrementIncludeSize(
$type, 
$size)
 
 3525        if ($this->mIncludeSizes[
$type] + 
$size > $this->mOptions->getMaxIncludeSize()) {
 
 3536    public function stripNoGallery(&
$text)
 
 3538        # if the string __NOGALLERY__ (not case-sensitive) occurs in the HTML, 
 3541        $this->mOutput->mNoGallery = $mw->matchAndRemove(
$text) ;
 
 3547    public function stripToc(
$text)
 
 3549        # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, 
 3552        if ($mw->matchAndRemove(
$text)) {
 
 3553            $this->mShowToc = 
false;
 
 3557        if ($mw->match(
$text)) {
 
 3558            $this->mShowToc = 
true;
 
 3559            $this->mForceTocPosition = 
true;
 
 3562            $text = $mw->replace(
'<!--MWTOC-->', 
$text, 1);
 
 3584    public function formatHeadings(
$text, $isMain = 
true)
 
 3586        global $wgMaxTocLevel, $wgContLang;
 
 3588        $doNumberHeadings = $this->mOptions->getNumberHeadings();
 
 3589        if (!$this->mTitle->quickUserCan(
'edit')) {
 
 3592            $showEditLink = $this->mOptions->getEditSection();
 
 3595        # Inhibit editsection links if requested in the page 
 3597        if ($esw->matchAndRemove(
$text)) {
 
 3601        # Get all headlines for numbering them and adding funky stuff like [edit] 
 3602        # links - this is for later, but we need the number of headlines right now 
 3604        $numMatches = preg_match_all(
'/<H(?P<level>[1-6])(?P<attrib>.*?' . 
'>)(?P<header>.*?)<\/H[1-6] *>/i', 
$text, $matches);
 
 3606        # if there are fewer than 4 headlines in the article, do not show TOC 
 3607        # unless it's been explicitly enabled. 
 3608        $enoughToc = $this->mShowToc &&
 
 3609            (($numMatches >= 4) || $this->mForceTocPosition);
 
 3611        # Allow user to stipulate that a page should have a "new section" 
 3612        # link added via __NEWSECTIONLINK__ 
 3614        if ($mw->matchAndRemove(
$text)) {
 
 3615            $this->mOutput->setNewSection(
true);
 
 3618        # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, 
 3619        # override above conditions and always show TOC above first header 
 3621        if ($mw->matchAndRemove(
$text)) {
 
 3622            $this->mShowToc = 
true;
 
 3626        # Never ever show TOC if no headers 
 3627        if ($numMatches < 1) {
 
 3631        # We need this to perform operations on the HTML 
 3632        $sk = $this->mOptions->getSkin();
 
 3636        $sectionCount = 0; # headlineCount excluding 
template sections
 
 3638        # Ugh .. the TOC should have neat indentation levels which can be 
 3639        # passed to the skin functions. These are determined here 
 3643        $sublevelCount = array();
 
 3644        $levelCount = array();
 
 3651        foreach ($matches[3] as $headline) {
 
 3653            $templatetitle = 
'';
 
 3654            $templatesection = 0;
 
 3657            if (preg_match(
"/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) {
 
 3659                $templatetitle = base64_decode($mat[1]);
 
 3660                $templatesection = 1 + (int) base64_decode($mat[2]);
 
 3661                $headline = preg_replace(
"/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", 
"", $headline);
 
 3665                $prevlevel = $level;
 
 3666                $prevtoclevel = $toclevel;
 
 3668            $level = $matches[1][$headlineCount];
 
 3670            if ($doNumberHeadings || $enoughToc) {
 
 3671                if ($level > $prevlevel) {
 
 3672                    # Increase TOC level 
 3674                    $sublevelCount[$toclevel] = 0;
 
 3675                    if ($toclevel < $wgMaxTocLevel) {
 
 3676                        $toc .= $sk->tocIndent();
 
 3678                } elseif ($level < $prevlevel && $toclevel > 1) {
 
 3679                    # Decrease TOC level, find level to jump to 
 3681                    if ($toclevel == 2 && $level <= $levelCount[1]) {
 
 3682                        # Can only go down to level 1 
 3685                        for (
$i = $toclevel; 
$i > 0; 
$i--) {
 
 3686                            if ($levelCount[
$i] == $level) {
 
 3687                                # Found last matching level 
 3690                            } elseif ($levelCount[
$i] < $level) {
 
 3691                                # Found first matching level below current level 
 3697                    if ($toclevel < $wgMaxTocLevel) {
 
 3698                        $toc .= $sk->tocUnindent($prevtoclevel - $toclevel);
 
 3701                    # No change in level, end TOC line 
 3702                    if ($toclevel < $wgMaxTocLevel) {
 
 3703                        $toc .= $sk->tocLineEnd();
 
 3707                $levelCount[$toclevel] = $level;
 
 3709                # count number of headlines for each level 
 3710                @$sublevelCount[$toclevel]++;
 
 3712                for (
$i = 1; 
$i <= $toclevel; 
$i++) {
 
 3713                    if (!empty($sublevelCount[
$i])) {
 
 3717                        $numbering .= $wgContLang->formatNum($sublevelCount[
$i]);
 
 3723            # The canonized header is a version of the header text safe to use for links 
 3724            # Avoid insertion of weird stuff like <math> by expanding the relevant sections 
 3725            $canonized_headline = $this->mStripState->unstripBoth($headline);
 
 3727            # Remove link placeholders by the link text. 
 3728            #     <!--LINK number--> 
 3730            #     link text with suffix 
 3731            $canonized_headline = preg_replace_callback(
 
 3732                '/<!--LINK ([0-9]*)-->/',
 
 3734                    return $this->mLinkHolders[
'texts'][$hit[1]];
 
 3738            $canonized_headline = preg_replace_callback(
 
 3739                '/<!--IWLINK ([0-9]*)-->/',
 
 3741                    return $this->mInterwikiLinkHolders[
'texts'][$hit[1]];
 
 3747            $canonized_headline = preg_replace(
'/<.*?' . 
'>/', 
'', $canonized_headline);
 
 3748            $tocline = trim($canonized_headline);
 
 3749            # Save headline for section edit hint before it's escaped 
 3750            $headline_hint = trim($canonized_headline);
 
 3752            $refers[$headlineCount] = $canonized_headline;
 
 3754            # count how many in assoc. array so we can track dupes in anchors 
 3755            isset($refers[$canonized_headline]) ? $refers[$canonized_headline]++ : $refers[$canonized_headline] = 1;
 
 3756            $refcount[$headlineCount] = $refers[$canonized_headline];
 
 3758            # Don't number the heading if it is the only one (looks silly) 
 3759            if ($doNumberHeadings && count($matches[3]) > 1) {
 
 3760                # the two are different if the line contains a link 
 3761                $headline = $numbering . 
' ' . $headline;
 
 3764            # Create the anchor for linking from the TOC to the section 
 3765            $anchor = $canonized_headline;
 
 3766            if ($refcount[$headlineCount] > 1) {
 
 3767                $anchor .= 
'_' . $refcount[$headlineCount];
 
 3769            if ($enoughToc && (!isset($wgMaxTocLevel) || $toclevel < $wgMaxTocLevel)) {
 
 3770                $toc .= $sk->tocLine($anchor, $tocline, $numbering, $toclevel);
 
 3772            # give headline the correct <h#> tag 
 3773            if ($showEditLink && (!$istemplate || $templatetitle !== 
"")) {
 
 3775                    $editlink = $sk->editSectionLinkForOther($templatetitle, $templatesection);
 
 3777                    $editlink = $sk->editSectionLink($this->mTitle, $sectionCount + 1, $headline_hint);
 
 3782            $head[$headlineCount] = $sk->makeHeadline($level, $matches[
'attrib'][$headlineCount], $anchor, $headline, $editlink);
 
 3791            if ($toclevel < $wgMaxTocLevel) {
 
 3792                $toc .= $sk->tocUnindent($toclevel - 1);
 
 3794            $toc = $sk->tocList($toc);
 
 3797        # split up and insert constructed headlines 
 3799        $blocks = preg_split(
'/<H[1-6].*?' . 
'>.*?<\/H[1-6]>/i', 
$text);
 
 3802        foreach ($blocks as $block) {
 
 3803            if ($showEditLink && $headlineCount > 0 && 
$i == 0 && $block != 
"\n") {
 
 3804                # This is the [edit] link that appears for the top block of text when 
 3805                # section editing is enabled 
 3807                # Disabled because it broke block formatting 
 3808                # For example, a bullet point in the top line 
 3809                # $full .= $sk->editSectionLink(0); 
 3812            if ($enoughToc && !
$i && $isMain && !$this->mForceTocPosition) {
 
 3813                # Top anchor now in skin 
 3814                $full = $full . $toc;
 
 3817            if (!empty($head[
$i])) {
 
 3822        if ($this->mForceTocPosition) {
 
 3823            return str_replace(
'<!--MWTOC-->', $toc, $full);
 
 3845        $this->setOutputType(
OT_WIKI);
 
 3848            $this->clearState();
 
 3855        $text = str_replace(array_keys($pairs), array_values($pairs), 
$text);
 
 3856        $text = $this->strip(
$text, $stripState, 
true, array( 
'gallery' ));
 
 3866    public function pstPass2(
$text, &$stripState, 
$user)
 
 3868        global $wgContLang, $wgLocaltimezone;
 
 3875        if (isset($wgLocaltimezone)) {
 
 3876            $oldtz = getenv(
'TZ');
 
 3877            putenv(
'TZ=' . $wgLocaltimezone);
 
 3879        $d = $wgContLang->timeanddate(date(
'YmdHis'), 
false, 
false) .
 
 3880          ' (' . date(
'T') . 
')';
 
 3881        if (isset($wgLocaltimezone)) {
 
 3882            putenv(
'TZ=' . $oldtz);
 
 3885        # Variable replacement 
 3886        # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags 
 3889        # Strip out <nowiki> etc. added via replaceVariables 
 3890        $text = $this->strip(
$text, $stripState, 
false, array( 
'gallery' ));
 
 3893        $sigText = $this->getUserSig(
$user);
 
 3896            '~~~~' => 
"$sigText $d",
 
 3900        # Context links: [[|name]] and [[name (context)|]] 
 3902        global $wgLegalTitleChars;
 
 3903        $tc = 
"[$wgLegalTitleChars]";
 
 3904        $nc = 
'[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
 
 3906        $p1 = 
"/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/";            # [[ns:page (context)|]]
 
 3907        $p3 = 
"/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/";  # [[ns:page (context), context|]]
 
 3908        $p2 = 
"/\[\[\\|($tc+)]]/";                                      # [[|page]]
 
 3910        # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
 
 3911        $text = preg_replace($p1, 
'[[\\1\\2\\3|\\2]]', 
$text);
 
 3912        $text = preg_replace($p3, 
'[[\\1\\2\\3\\4|\\2]]', 
$text);
 
 3914        $t = $this->mTitle->getText();
 
 3916        if (preg_match(
"/^($nc+:|)$tc+?( \\($tc+\\))$/", 
$t, 
$m)) {
 
 3917            $text = preg_replace($p2, 
"[[$m[1]\\1$m[2]|\\1]]", 
$text);
 
 3918        } elseif (preg_match(
"/^($nc+:|)$tc+?(, $tc+|)$/", 
$t, 
$m) && 
'' != 
"$m[1]$m[2]") {
 
 3919            $text = preg_replace($p2, 
"[[$m[1]\\1$m[2]|\\1]]", 
$text);
 
 3921            # if there's no context, don't bother duplicating the title 
 3925        # Trim trailing whitespace 
 3939    public function getUserSig(&
$user)
 
 3941        $username = 
$user->getName();
 
 3942        $nickname = 
$user->getOption(
'nickname');
 
 3943        $nickname = $nickname === 
'' ? $username : $nickname;
 
 3945        if (
$user->getBoolOption(
'fancysig') !== 
false) {
 
 3946            # Sig. might contain markup; validate this 
 3947            if ($this->validateSig($nickname) !== 
false) {
 
 3948                # Validated; clean up (if needed) and return it 
 3949                return $this->cleanSig($nickname, 
true);
 
 3951                # Failed to validate; fall back to the default 
 3952                $nickname = $username;
 
 3953                wfDebug(
"Parser::getUserSig: $username has bad XML tags in signature.\n");
 
 3958        $nickname = $this->cleanSigInSig($nickname);
 
 3960        # If we're still here, make it a link to the user page 
 3961        $userpage = 
$user->getUserPage();
 
 3962        return(
'[[' . $userpage->getPrefixedText() . 
'|' . wfEscapeWikiText($nickname) . 
']]');
 
 3971    public function validateSig(
$text)
 
 3973        return(wfIsWellFormedXmlFragment(
$text) ? 
$text : 
false);
 
 3986    public function cleanSig(
$text, $parsing = 
false)
 
 3989        $this->startExternalParse($wgTitle, 
new ParserOptions(), $parsing ? 
OT_WIKI : 
OT_MSG);
 
 3992        $substRegex = 
'/\{\{(?!(?:' . $substWord->getBaseRegex() . 
'))/x' . $substWord->getRegexCase();
 
 3993        $substText = 
'{{' . $substWord->getSynonym(0);
 
 3995        $text = preg_replace($substRegex, $substText, 
$text);
 
 3999        $this->clearState();
 
 4008    public function cleanSigInSig(
$text)
 
 4019    public function startExternalParse(&
$title, 
$options, $outputType, $clearState = 
true)
 
 4023        $this->setOutputType($outputType);
 
 4025            $this->clearState();
 
 4040        static $executing = 
false;
 
 4042        $fname = 
"Parser::transformMsg";
 
 4044        # Guard against infinite recursion 
 4050        wfProfileIn($fname);
 
 4052        if ($wgTitle && !($wgTitle instanceof FakeTitle)) {
 
 4053            $this->mTitle = $wgTitle;
 
 4058        $this->setOutputType(
OT_MSG);
 
 4059        $this->clearState();
 
 4063        wfProfileOut($fname);
 
 4082    public function setHook(
$tag, $callback)
 
 4085        $oldVal = isset($this->mTagHooks[
$tag]) ? $this->mTagHooks[
$tag] : 
null;
 
 4086        $this->mTagHooks[
$tag] = $callback;
 
 4115    public function setFunctionHook(
$id, $callback, $flags = 0)
 
 4117        $oldVal = isset($this->mFunctionHooks[
$id]) ? $this->mFunctionHooks[
$id] : 
null;
 
 4118        $this->mFunctionHooks[
$id] = $callback;
 
 4120        # Add to function cache 
 4123            throw new MWException(
'Parser::setFunctionHook() expecting a magic word identifier.');
 
 4126        $synonyms = $mw->getSynonyms();
 
 4127        $sensitive = intval($mw->isCaseSensitive());
 
 4129        foreach ($synonyms as $syn) {
 
 4132                $syn = strtolower($syn);
 
 4138            # Remove trailing colon 
 4139            if (substr($syn, -1, 1) == 
':') {
 
 4140                $syn = substr($syn, 0, -1);
 
 4142            $this->mFunctionSynonyms[$sensitive][$syn] = 
$id;
 
 4152    public function getFunctionHooks()
 
 4154        return array_keys($this->mFunctionHooks);
 
 4171        $fname = 
'Parser::replaceLinkHolders';
 
 4172        wfProfileIn($fname);
 
 4176        $sk = $this->mOptions->getSkin();
 
 4177        $linkCache = &LinkCache::singleton();
 
 4179        if (!empty($this->mLinkHolders[
'namespaces'])) {
 
 4180            wfProfileIn($fname . 
'-check');
 
 4181            $dbr = wfGetDB(DB_SLAVE);
 
 4182            $page = $dbr->tableName(
'page');
 
 4183            $threshold = $wgUser->getOption(
'stubthreshold');
 
 4186            asort($this->mLinkHolders[
'namespaces']);
 
 4191            foreach ($this->mLinkHolders[
'namespaces'] as 
$key => $ns) {
 
 4193                $title = $this->mLinkHolders[
'titles'][
$key];
 
 4195                # Skip invalid entries. 
 4196                # Result will be ugly, but prevents crash. 
 4200                $pdbk = $pdbks[
$key] = 
$title->getPrefixedDBkey();
 
 4202                # Check if it's a static known link, e.g. interwiki 
 4203                if (
$title->isAlwaysKnown()) {
 
 4204                    $colours[$pdbk] = 1;
 
 4205                } elseif ((
$id = $linkCache->getGoodLinkID($pdbk)) != 0) {
 
 4206                    $colours[$pdbk] = 1;
 
 4208                } elseif ($linkCache->isBadLink($pdbk)) {
 
 4209                    $colours[$pdbk] = 0;
 
 4211                    # Not in the link cache, add it to the query 
 4214                        $query = 
"SELECT page_id, page_namespace, page_title";
 
 4215                        if ($threshold > 0) {
 
 4216                            $query .= 
', page_len, page_is_redirect';
 
 4218                        $query .= 
" FROM $page WHERE (page_namespace=$ns AND page_title IN(";
 
 4221                        $query .= 
")) OR (page_namespace=$ns AND page_title IN(";
 
 4226                    $query .= $dbr->addQuotes($this->mLinkHolders[
'dbkeys'][
$key]);
 
 4237                # Fetch data and form into an associative array 
 4238                # non-existent = broken 
 4241                while (
$s = $dbr->fetchObject(
$res)) {
 
 4243                    $pdbk = 
$title->getPrefixedDBkey();
 
 4244                    $linkCache->addGoodLinkObj(
$s->page_id, 
$title);
 
 4245                    $this->mOutput->addLink(
$title, 
$s->page_id);
 
 4247                    if ($threshold > 0) {
 
 4249                        if (
$s->page_is_redirect || 
$s->page_namespace != 0 || 
$size >= $threshold) {
 
 4250                            $colours[$pdbk] = 1;
 
 4252                            $colours[$pdbk] = 2;
 
 4255                        $colours[$pdbk] = 1;
 
 4259            wfProfileOut($fname . 
'-check');
 
 4261            # Do a second query for different language variants of links and categories 
 4262            if ($wgContLang->hasVariants()) {
 
 4263                $linkBatch = 
new LinkBatch();
 
 4264                $variantMap = array(); 
 
 4265                $categoryMap = array(); 
 
 4266                $varCategories = array(); 
 
 4268                $categories = $this->mOutput->getCategoryLinks();
 
 4271                foreach ($this->mLinkHolders[
'namespaces'] as 
$key => $ns) {
 
 4272                    $title = $this->mLinkHolders[
'titles'][
$key];
 
 4277                    $pdbk = 
$title->getPrefixedDBkey();
 
 4278                    $titleText = 
$title->getText();
 
 4281                    $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText);
 
 4284                    if (!isset($colours[$pdbk])) {
 
 4285                        foreach ($allTextVariants as $textVariant) {
 
 4286                            if ($textVariant != $titleText) {
 
 4288                                if (is_null($variantTitle)) {
 
 4291                                $linkBatch->addObj($variantTitle);
 
 4292                                $variantMap[$variantTitle->getPrefixedDBkey()][] = 
$key;
 
 4299                foreach ($categories as $category) {
 
 4300                    $variants = $wgContLang->convertLinkToAllVariants($category);
 
 4301                    foreach ($variants as $variant) {
 
 4302                        if ($variant != $category) {
 
 4304                            if (is_null($variantTitle)) {
 
 4307                            $linkBatch->addObj($variantTitle);
 
 4308                            $categoryMap[$variant] = $category;
 
 4314                if (!$linkBatch->isEmpty()) {
 
 4316                    $titleClause = $linkBatch->constructSet(
'page', $dbr);
 
 4318                    $variantQuery = 
"SELECT page_id, page_namespace, page_title";
 
 4319                    if ($threshold > 0) {
 
 4320                        $variantQuery .= 
', page_len, page_is_redirect';
 
 4323                    $variantQuery .= 
" FROM $page WHERE $titleClause";
 
 4325                        $variantQuery .= 
' FOR UPDATE';
 
 4328                    $varRes = $dbr->query($variantQuery, $fname);
 
 4331                    while (
$s = $dbr->fetchObject($varRes)) {
 
 4333                        $varPdbk = $variantTitle->getPrefixedDBkey();
 
 4334                        $vardbk = $variantTitle->getDBkey();
 
 4336                        $holderKeys = array();
 
 4337                        if (isset($variantMap[$varPdbk])) {
 
 4338                            $holderKeys = $variantMap[$varPdbk];
 
 4339                            $linkCache->addGoodLinkObj(
$s->page_id, $variantTitle);
 
 4340                            $this->mOutput->addLink($variantTitle, 
$s->page_id);
 
 4344                        foreach ($holderKeys as 
$key) {
 
 4345                            $title = $this->mLinkHolders[
'titles'][
$key];
 
 4350                            $pdbk = 
$title->getPrefixedDBkey();
 
 4352                            if (!isset($colours[$pdbk])) {
 
 4354                                $this->mLinkHolders[
'titles'][
$key] = $variantTitle;
 
 4355                                $this->mLinkHolders[
'dbkeys'][
$key] = $variantTitle->getDBkey();
 
 4358                                $pdbks[
$key] = $varPdbk;
 
 4359                                if ($threshold > 0) {
 
 4361                                    if (
$s->page_is_redirect || 
$s->page_namespace != 0 || 
$size >= $threshold) {
 
 4362                                        $colours[$varPdbk] = 1;
 
 4364                                        $colours[$varPdbk] = 2;
 
 4367                                    $colours[$varPdbk] = 1;
 
 4373                        if (isset($categoryMap[$vardbk])) {
 
 4374                            $oldkey = $categoryMap[$vardbk];
 
 4375                            if ($oldkey != $vardbk) {
 
 4376                                $varCategories[$oldkey] = $vardbk;
 
 4382                    if (count($varCategories) > 0) {
 
 4384                        $originalCats = $this->mOutput->getCategories();
 
 4385                        foreach ($originalCats as $cat => $sortkey) {
 
 4387                            if (array_key_exists($cat, $varCategories)) {
 
 4388                                $newCats[$varCategories[$cat]] = $sortkey;
 
 4390                                $newCats[$cat] = $sortkey;
 
 4393                        $this->mOutput->setCategoryLinks($newCats);
 
 4398            # Construct search and replace arrays 
 4399            wfProfileIn($fname . 
'-construct');
 
 4400            $replacePairs = array();
 
 4401            foreach ($this->mLinkHolders[
'namespaces'] as 
$key => $ns) {
 
 4402                $pdbk = $pdbks[
$key];
 
 4403                $searchkey = 
"<!--LINK $key-->";
 
 4404                $title = $this->mLinkHolders[
'titles'][
$key];
 
 4405                if (empty($colours[$pdbk])) {
 
 4406                    $linkCache->addBadLinkObj(
$title);
 
 4407                    $colours[$pdbk] = 0;
 
 4408                    $this->mOutput->addLink(
$title, 0);
 
 4409                    $replacePairs[$searchkey] = $sk->makeBrokenLinkObj(
 
 4411                        $this->mLinkHolders[
'texts'][
$key],
 
 4412                        $this->mLinkHolders[
'queries'][
$key]
 
 4414                } elseif ($colours[$pdbk] == 1) {
 
 4415                    $replacePairs[$searchkey] = $sk->makeKnownLinkObj(
 
 4417                        $this->mLinkHolders[
'texts'][
$key],
 
 4418                        $this->mLinkHolders[
'queries'][
$key]
 
 4420                } elseif ($colours[$pdbk] == 2) {
 
 4421                    $replacePairs[$searchkey] = $sk->makeStubLinkObj(
 
 4423                        $this->mLinkHolders[
'texts'][
$key],
 
 4424                        $this->mLinkHolders[
'queries'][
$key]
 
 4428            $replacer = 
new HashtableReplacer($replacePairs, 1);
 
 4429            wfProfileOut($fname . 
'-construct');
 
 4432            wfProfileIn($fname . 
'-replace');
 
 4433            $text = preg_replace_callback(
 
 4434                '/(<!--LINK .*?-->)/',
 
 4439            wfProfileOut($fname . 
'-replace');
 
 4442        # Now process interwiki link holders 
 4443        # This is quite a bit simpler than internal links 
 4444        if (!empty($this->mInterwikiLinkHolders[
'texts'])) {
 
 4445            wfProfileIn($fname . 
'-interwiki');
 
 4446            # Make interwiki link HTML 
 4447            $replacePairs = array();
 
 4448            foreach ($this->mInterwikiLinkHolders[
'texts'] as 
$key => $link) {
 
 4449                $title = $this->mInterwikiLinkHolders[
'titles'][
$key];
 
 4450                $replacePairs[
$key] = $sk->makeLinkObj(
$title, $link);
 
 4452            $replacer = 
new HashtableReplacer($replacePairs, 1);
 
 4454            $text = preg_replace_callback(
 
 4455                '/<!--IWLINK (.*?)-->/',
 
 4459            wfProfileOut($fname . 
'-interwiki');
 
 4462        wfProfileOut($fname);
 
 4472    public function replaceLinkHoldersText(
$text)
 
 4474        $fname = 
'Parser::replaceLinkHoldersText';
 
 4475        wfProfileIn($fname);
 
 4477        $text = preg_replace_callback(
 
 4478            '/<!--(LINK|IWLINK) (.*?)-->/',
 
 4479            array( &$this, 
'replaceLinkHoldersTextCallback' ),
 
 4483        wfProfileOut($fname);
 
 4492    public function replaceLinkHoldersTextCallback($matches)
 
 4494        $type = $matches[1];
 
 4496        if (
$type == 
'LINK') {
 
 4497            if (isset($this->mLinkHolders[
'texts'][
$key])) {
 
 4498                return $this->mLinkHolders[
'texts'][
$key];
 
 4500        } elseif (
$type == 
'IWLINK') {
 
 4501            if (isset($this->mInterwikiLinkHolders[
'texts'][
$key])) {
 
 4502                return $this->mInterwikiLinkHolders[
'texts'][
$key];
 
 4511    public function renderPreTag(
$text, $attribs)
 
 4514        $content = StringUtils::delimiterReplace(
'<nowiki>', 
'</nowiki>', 
'$1', 
$text, 
'i');
 
 4517        return wfOpenElement(
'pre', $attribs) .
 
 4518            Xml::escapeTagsOnly($content) .
 
 4533        $ig = 
new ImageGallery();
 
 4534        $ig->setContextTitle($this->mTitle);
 
 4535        $ig->setShowBytes(
false);
 
 4536        $ig->setShowFilename(
false);
 
 4538        $ig->useSkin($this->mOptions->getSkin());
 
 4540        if (isset(
$params[
'caption'])) {
 
 4541            $caption = 
$params[
'caption'];
 
 4542            $caption = htmlspecialchars($caption);
 
 4543            $caption = $this->replaceInternalLinks($caption);
 
 4544            $ig->setCaptionHtml($caption);
 
 4546        if (isset(
$params[
'perrow'])) {
 
 4547            $ig->setPerRow(
$params[
'perrow']);
 
 4549        if (isset(
$params[
'widths'])) {
 
 4550            $ig->setWidths(
$params[
'widths']);
 
 4552        if (isset(
$params[
'heights'])) {
 
 4553            $ig->setHeights(
$params[
'heights']);
 
 4556        $lines = explode(
"\n", 
$text);
 
 4557        foreach ($lines as $line) {
 
 4558            # match lines like these: 
 4559            # Image:someimage.jpg|This is some image 
 4561            preg_match(
"/^([^|]+)(\\|(.*))?$/", $line, $matches);
 
 4563            if (count($matches) == 0) {
 
 4569                # Bogus title. Ignore these so we don't bomb out later. 
 4572            if (isset($matches[3])) {
 
 4573                $label = $matches[3];
 
 4578            $pout = $this->
parse(
 
 4585            $html = $pout->getText();
 
 4587            $ig->add(
new Image($nt), 
$html);
 
 4589            # Only add real images (bug #5586) 
 4590            if ($nt->getNamespace() == NS_IMAGE) {
 
 4591                $this->mOutput->addImage($nt->getDBkey());
 
 4594        return $ig->toHTML();
 
 4600    public function makeImage($nt, 
$options)
 
 4602        # @TODO: let the MediaHandler specify its transform parameters 
 4604        # Check if the options text is of the form "options|alt text" 
 4606        #  * thumbnail          make a thumbnail with enlarge-icon and caption, alignment depends on lang 
 4607        #  * left               no resizing, just left align. label is used for alt= only 
 4608        #  * right              same, but right aligned 
 4609        #  * none               same, but not aligned 
 4610        #  * ___px              scale to ___ pixels width, no aligning. e.g. use in taxobox 
 4611        #  * center             center the image 
 4612        #  * framed             Keep original image size, no magnify-button. 
 4613        # vertical-align values (no % or length right now): 
 4624        $part = array_map(
'trim', explode(
'|', 
$options));
 
 4627        $alignments = array( 
'left', 
'right', 
'center', 
'none', 
'baseline', 
'sub', 
'super', 
'top', 
'text-top', 
'middle', 
'bottom', 
'text-bottom' );
 
 4639        $framed = $thumb = 
false;
 
 4640        $manual_thumb = 
'' ;
 
 4641        $align = $valign = 
'';
 
 4642        $sk = $this->mOptions->getSkin();
 
 4644        foreach ($part as $val) {
 
 4645            if (!is_null($mwThumb->matchVariableStartToEnd($val))) {
 
 4647            } elseif (!is_null($match = $mwManualThumb->matchVariableStartToEnd($val))) {
 
 4648                # use manually specified thumbnail 
 4650                $manual_thumb = $match;
 
 4653                    if (!is_null($mwAlign[$alignment]->matchVariableStartToEnd($val))) {
 
 4654                        switch ($alignment) {
 
 4655                            case 'left': 
case 'right': 
case 'center': 
case 'none':
 
 4656                                $align = $alignment; 
break;
 
 4658                                $valign = $alignment;
 
 4663                if (!is_null($match = $mwPage->matchVariableStartToEnd($val))) {
 
 4664                    # Select a page in a multipage document 
 4666                } elseif (!isset(
$params[
'width']) && !is_null($match = $mwWidth->matchVariableStartToEnd($val))) {
 
 4667                    wfDebug(
"img_width match: $match\n");
 
 4668                    # $match is the image width in pixels 
 4670                    if (preg_match(
'/^([0-9]*)x([0-9]*)$/', $match, 
$m)) {
 
 4674                        $params[
'width'] = intval($match);
 
 4676                } elseif (!is_null($mwFramed->matchVariableStartToEnd($val))) {
 
 4683        # Strip bad stuff out of the alt text 
 4684        $alt = $this->replaceLinkHoldersText($caption);
 
 4686        # make sure there are no placeholders in thumbnail attributes 
 4687        # that are later expanded to html- so expand them now and 
 4689        $alt = $this->mStripState->unstripBoth($alt);
 
 4692        # Linker does the rest 
 4693        return $sk->makeImageLinkObj($nt, $caption, $alt, $align, 
$params, $framed, $thumb, $manual_thumb, $valign);
 
 4700    public function disableCache()
 
 4702        wfDebug(
"Parser output marked as uncacheable.\n");
 
 4703        $this->mOutput->mCacheTime = -1;
 
 4714    public function attributeStripCallback(&
$text, $args)
 
 4716        $text = $this->replaceVariables(
$text, $args);
 
 4717        $text = $this->mStripState->unstripBoth(
$text);
 
 4726    public function Title(
$x = 
null)
 
 4728        return wfSetVar($this->mTitle, 
$x);
 
 4730    public function Options(
$x = 
null)
 
 4732        return wfSetVar($this->mOptions, 
$x);
 
 4734    public function OutputType(
$x = 
null)
 
 4736        return wfSetVar($this->mOutputType, 
$x);
 
 4743    public function getTags()
 
 4745        return array_keys($this->mTagHooks);
 
 4765    private function extractSections(
$text, 
$section, $mode, $newtext = 
'')
 
 4767        # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML 
 4768        # comments to be stripped as well) 
 4771        $oldOutputType = $this->mOutputType;
 
 4772        $oldOptions = $this->mOptions;
 
 4773        $this->mOptions = 
new ParserOptions();
 
 4774        $this->setOutputType(
OT_WIKI);
 
 4776        $striptext = $this->strip(
$text, $stripState, 
true);
 
 4778        $this->setOutputType($oldOutputType);
 
 4779        $this->mOptions = $oldOptions;
 
 4781        # now that we can be sure that no pseudo-sections are in the source, 
 4782        # split it up by section 
 4783        $uniq = preg_quote($this->uniqPrefix(), 
'/');
 
 4789                                (?:$comment|<\/?noinclude>)* # Initial comments will be stripped 
 4790                                (=+) # Should this be limited to 6? 
 4791                                .+?  # Section title... 
 4792                                \\2  # Ending = count must match start 
 4793                                (?:$comment|<\/?noinclude>|[ \\t]+)* # Trailing whitespace ok 
 4806        if ($mode == 
"get") {
 
 4814        } elseif ($mode == 
"replace") {
 
 4816                $rv = $newtext . 
"\n\n";
 
 4826            $headerLine = $secs[
$index++];
 
 4829                $headerLevel = strlen($secs[
$index++]);
 
 4833                $headerLevel = intval($secs[
$index++]);
 
 4835            $content = $secs[
$index++];
 
 4838            if ($mode == 
"get") {
 
 4840                    $rv = $headerLine . $content;
 
 4841                    $sectionLevel = $headerLevel;
 
 4843                    if ($sectionLevel && $headerLevel > $sectionLevel) {
 
 4844                        $rv .= $headerLine . $content;
 
 4850            } elseif ($mode == 
"replace") {
 
 4852                    $rv .= $headerLine . $content;
 
 4854                    $rv .= $newtext . 
"\n\n";
 
 4855                    $sectionLevel = $headerLevel;
 
 4857                    if ($headerLevel <= $sectionLevel) {
 
 4862                        $rv .= $headerLine . $content;
 
 4867        if (is_string($rv)) {
 
 4868            # reinsert stripped tags 
 4869            $rv = trim($stripState->unstripBoth($rv));
 
 4889        return $this->extractSections(
$text, 
$section, 
"get", $deftext);
 
 4894        return $this->extractSections($oldtext, 
$section, 
"replace", 
$text);
 
 4901    public function getRevisionTimestamp()
 
 4903        if (is_null($this->mRevisionTimestamp)) {
 
 4904            wfProfileIn(__METHOD__);
 
 4906            $dbr = wfGetDB(DB_SLAVE);
 
 4910                array( 
'rev_id' => $this->mRevisionId ),
 
 4926            $this->mRevisionTimestamp = $wgContLang->userAdjust(
$timestamp, 
'');
 
 4928            wfProfileOut(__METHOD__);
 
 4930        return $this->mRevisionTimestamp;
 
 4938    public function setDefaultSort($sort)
 
 4940        $this->mDefaultSort = $sort;
 
 4949    public function getDefaultSort()
 
 4951        if ($this->mDefaultSort !== 
false) {
 
 4952            return $this->mDefaultSort;
 
 4954            return $this->mTitle->getNamespace() == NS_CATEGORY
 
 4955                    ? $this->mTitle->getText()
 
 4956                    : $this->mTitle->getPrefixedText();
 
 4969    public function replace($matches)
 
 4971        if (substr($matches[1], -1) == 
"\n") {
 
 4972            $this->output .= substr($matches[1], 0, -1);
 
 4974            $this->output .= $matches[1];
 
 4990        $this->
general = 
new ReplacementArray;
 
 4991        $this->nowiki = 
new ReplacementArray;
 
 4996        wfProfileIn(__METHOD__);
 
 4998        wfProfileOut(__METHOD__);
 
 5004        wfProfileIn(__METHOD__);
 
 5006        wfProfileOut(__METHOD__);
 
 5012        wfProfileIn(__METHOD__);
 
 5015        wfProfileOut(__METHOD__);
 
const MW_COLON_STATE_TAGSTART
const MW_COLON_STATE_COMMENTDASHDASH
const MW_COLON_STATE_CLOSETAG
const EXT_IMAGE_FNAME_CLASS
const EXT_IMAGE_EXTENSIONS
const MW_COLON_STATE_TEXT
const EXT_LINK_TEXT_CLASS
const MW_COLON_STATE_COMMENTDASH
const MW_COLON_STATE_TAGSLASH
const MW_PARSER_VERSION
Update this version number when the ParserOutput format changes in an incompatible way,...
const MW_COLON_STATE_COMMENT
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
foreach($mandatory_scripts as $file) $timestamp
An exception for terminatinating execution or to throw for unit testing.
externalTidy($text)
Spawn an external HTML tidy process and get corrected markup back from it.
uniqPrefix()
Accessor for mUniqPrefix.
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
static replaceUnusualEscapes($url)
Replace unusual URL escape codes with their equivalent characters.
setHook($tag, $callback)
Create an HTML-style tag, e.g.
strip($text, $state, $stripcomments=false, $dontstrip=array())
Strips and renders nowiki, pre, math, hiero If $render is set, performs necessary rendering operation...
internalParse($text)
Helper function for parse() that transforms wiki markup into HTML.
getRandomString()
Get a random string.
internalTidy($text)
Use the HTML tidy PECL extension to use the tidy library in-process, saving the overhead of spawning ...
tidy($text)
Interface with html tidy, used if $wgUseTidy = true.
doBlockLevels($text, $linestart)
#-
replaceLinkHolders(&$text, $options=0)
Replace link placeholders with actual links, in the buffer Placeholders created in Skin::makeLinkObj(...
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
clearState()
Clear Parser state.
parse($text, &$title, $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME)
static removeHTMLtags($text, $processCallback=null, $args=array())
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments.
static escapeId($id)
Given a value escape it so that it can be used in an id attribute and return it, this does not valida...
static removeHTMLcomments($text)
Remove '', and everything between.
static cleanUrl($url, $hostname=true)
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
static validateTagAttributes($attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
static fixTagAttributes($text, $element)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
static newFromRedirect($text)
Create a new Title for a redirect.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
static legalChars()
Get a regex character class describing the legal characters in a link.
static makeName($ns, $title)
static & makeTitle($ns, $title)
Create a new Title from a namespace index and a DB key.
static newFromDBkey($key)
Create a new Title from a prefixed DB key.
wfUrlProtocols()
Returns a regular expression of url protocols.
for( $i=6;$i< 13;$i++) for($i=1; $i< 13; $i++) $d
if(!array_key_exists('StateId', $_REQUEST)) $id
if(array_key_exists('yes', $_REQUEST)) $attributes
if(function_exists( 'posix_getuid') &&posix_getuid()===0) if(!array_key_exists('t', $options)) $tag
catch(Exception $e) $message
static http()
Fetches the global http state from ILIAS.
parse($uri)
Parses a URI and returns its individual components.
foreach($_POST as $key=> $value) $res
echo;exit;}function LogoutNotification($SessionID){ global $ilDB;$q="SELECT session_id, data FROM usr_session WHERE expires > (\w+)\|/" PREG_SPLIT_NO_EMPTY PREG_SPLIT_DELIM_CAPTURE