Esempio n. 1
0
 /**
  * Return the text of a template, after recursively
  * replacing any variables or templates within the template.
  *
  * @param array $piece The parts of the template
  *  $piece['title']: the title, i.e. the part before the |
  *  $piece['parts']: the parameter array
  *  $piece['lineStart']: whether the brace was at the start of a line
  * @param PPFrame The current frame, contains template arguments
  * @return string the text of the template
  * @private
  */
 function braceSubstitution($piece, $frame)
 {
     global $wgContLang, $wgNonincludableNamespaces;
     wfProfileIn(__METHOD__);
     wfProfileIn(__METHOD__ . '-setup');
     # Flags
     $found = false;
     # $text has been filled
     $nowiki = false;
     # wiki markup in $text should be escaped
     $isHTML = false;
     # $text is HTML, armour it against wikitext transformation
     $forceRawInterwiki = false;
     # Force interwiki transclusion to be done in raw mode not rendered
     $isChildObj = false;
     # $text is a DOM node needing expansion in a child frame
     $isLocalObj = false;
     # $text is a DOM node needing expansion in the current frame
     # Title object, where $text came from
     $title = NULL;
     # $part1 is the bit before the first |, and must contain only title characters.
     # Various prefixes will be stripped from it later.
     $titleWithSpaces = $frame->expand($piece['title']);
     $part1 = trim($titleWithSpaces);
     $titleText = false;
     # Original title text preserved for various purposes
     $originalTitle = $part1;
     # $args is a list of argument nodes, starting from index 0, not including $part1
     $args = null == $piece['parts'] ? array() : $piece['parts'];
     wfProfileOut(__METHOD__ . '-setup');
     # SUBST
     wfProfileIn(__METHOD__ . '-modifiers');
     if (!$found) {
         $mwSubst = MagicWord::get('subst');
         if ($mwSubst->matchStartAndRemove($part1) xor $this->ot['wiki']) {
             # One of two possibilities is true:
             # 1) Found SUBST but not in the PST phase
             # 2) Didn't find SUBST and in the PST phase
             # In either case, return without further processing
             $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
             $isLocalObj = true;
             $found = true;
         }
     }
     # Variables
     if (!$found && $args->getLength() == 0) {
         $id = $this->mVariables->matchStartToEnd($part1);
         if ($id !== false) {
             $text = $this->getVariableValue($id);
             if (MagicWord::getCacheTTL($id) > -1) {
                 $this->mOutput->mContainsOldMagic = true;
             }
             $found = true;
         }
     }
     # MSG, MSGNW and RAW
     if (!$found) {
         # Check for MSGNW:
         $mwMsgnw = MagicWord::get('msgnw');
         if ($mwMsgnw->matchStartAndRemove($part1)) {
             $nowiki = true;
         } else {
             # Remove obsolete MSG:
             $mwMsg = MagicWord::get('msg');
             $mwMsg->matchStartAndRemove($part1);
         }
         # Check for RAW:
         $mwRaw = MagicWord::get('raw');
         if ($mwRaw->matchStartAndRemove($part1)) {
             $forceRawInterwiki = true;
         }
     }
     wfProfileOut(__METHOD__ . '-modifiers');
     # Parser functions
     if (!$found) {
         wfProfileIn(__METHOD__ . '-pfunc');
         $colonPos = strpos($part1, ':');
         if ($colonPos !== false) {
             # Case sensitive functions
             $function = substr($part1, 0, $colonPos);
             if (isset($this->mFunctionSynonyms[1][$function])) {
                 $function = $this->mFunctionSynonyms[1][$function];
             } else {
                 # Case insensitive functions
                 $function = strtolower($function);
                 if (isset($this->mFunctionSynonyms[0][$function])) {
                     $function = $this->mFunctionSynonyms[0][$function];
                 } else {
                     $function = false;
                 }
             }
             if ($function) {
                 list($callback, $flags) = $this->mFunctionHooks[$function];
                 $initialArgs = array(&$this);
                 $funcArgs = array(trim(substr($part1, $colonPos + 1)));
                 if ($flags & SFH_OBJECT_ARGS) {
                     # Add a frame parameter, and pass the arguments as an array
                     $allArgs = $initialArgs;
                     $allArgs[] = $frame;
                     for ($i = 0; $i < $args->getLength(); $i++) {
                         $funcArgs[] = $args->item($i);
                     }
                     $allArgs[] = $funcArgs;
                 } else {
                     # Convert arguments to plain text
                     for ($i = 0; $i < $args->getLength(); $i++) {
                         $funcArgs[] = trim($frame->expand($args->item($i)));
                     }
                     $allArgs = array_merge($initialArgs, $funcArgs);
                 }
                 # Workaround for PHP bug 35229 and similar
                 if (!is_callable($callback)) {
                     throw new MWException("Tag hook for {$function} is not callable\n");
                 }
                 $result = call_user_func_array($callback, $allArgs);
                 $found = true;
                 $noparse = true;
                 $preprocessFlags = 0;
                 if (is_array($result)) {
                     if (isset($result[0])) {
                         $text = $result[0];
                         unset($result[0]);
                     }
                     // Extract flags into the local scope
                     // This allows callers to set flags such as nowiki, found, etc.
                     extract($result);
                 } else {
                     $text = $result;
                 }
                 if (!$noparse) {
                     $text = $this->preprocessToDom($text, $preprocessFlags);
                     $isChildObj = true;
                 }
             }
         }
         wfProfileOut(__METHOD__ . '-pfunc');
     }
     # Finish mangling title and then check for loops.
     # Set $title to a Title object and $titleText to the PDBK
     if (!$found) {
         $ns = NS_TEMPLATE;
         # Split the title into page and subpage
         $subpage = '';
         $part1 = $this->maybeDoSubpageLink($part1, $subpage);
         if ($subpage !== '') {
             $ns = $this->mTitle->getNamespace();
         }
         $title = Title::newFromText($part1, $ns);
         if ($title) {
             $titleText = $title->getPrefixedText();
             # Check for language variants if the template is not found
             if ($wgContLang->hasVariants() && $title->getArticleID() == 0) {
                 $wgContLang->findVariantLink($part1, $title, true);
             }
             # Do recursion depth check
             $limit = $this->mOptions->getMaxTemplateDepth();
             if ($frame->depth >= $limit) {
                 $found = true;
                 $text = '<span class="error">' . wfMsgForContent('parser-template-recursion-depth-warning', $limit) . '</span>';
             }
         }
     }
     # Load from database
     if (!$found && $title) {
         wfProfileIn(__METHOD__ . '-loadtpl');
         if (!$title->isExternal()) {
             if ($title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html']) {
                 $text = SpecialPage::capturePath($title);
                 if (is_string($text)) {
                     $found = true;
                     $isHTML = true;
                     $this->disableCache();
                 }
             } else {
                 if ($wgNonincludableNamespaces && in_array($title->getNamespace(), $wgNonincludableNamespaces)) {
                     $found = false;
                     //access denied
                     wfDebug(__METHOD__ . ": template inclusion denied for " . $title->getPrefixedDBkey());
                 } else {
                     list($text, $title) = $this->getTemplateDom($title);
                     if ($text !== false) {
                         $found = true;
                         $isChildObj = true;
                     }
                 }
             }
             # If the title is valid but undisplayable, make a link to it
             if (!$found && ($this->ot['html'] || $this->ot['pre'])) {
                 $text = "[[:{$titleText}]]";
                 $found = true;
             }
         } elseif ($title->isTrans()) {
             // Interwiki transclusion
             if ($this->ot['html'] && !$forceRawInterwiki) {
                 $text = $this->interwikiTransclude($title, 'render');
                 $isHTML = true;
             } else {
                 $text = $this->interwikiTransclude($title, 'raw');
                 // Preprocess it like a template
                 $text = $this->preprocessToDom($text, self::PTD_FOR_INCLUSION);
                 $isChildObj = true;
             }
             $found = true;
         }
         # Do infinite loop check
         # This has to be done after redirect resolution to avoid infinite loops via redirects
         if (!$frame->loopCheck($title)) {
             $found = true;
             $text = '<span class="error">' . wfMsgForContent('parser-template-loop-warning', $titleText) . '</span>';
             wfDebug(__METHOD__ . ": template loop broken at '{$titleText}'\n");
         }
         wfProfileOut(__METHOD__ . '-loadtpl');
     }
     # If we haven't found text to substitute by now, we're done
     # Recover the source wikitext and return it
     if (!$found) {
         $text = $frame->virtualBracketedImplode('{{', '|', '}}', $titleWithSpaces, $args);
         wfProfileOut(__METHOD__);
         return array('object' => $text);
     }
     # Expand DOM-style return values in a child frame
     if ($isChildObj) {
         # Clean up argument array
         $newFrame = $frame->newChild($args, $title);
         if ($nowiki) {
             $text = $newFrame->expand($text, PPFrame::RECOVER_ORIG);
         } elseif ($titleText !== false && $newFrame->isEmpty()) {
             # Expansion is eligible for the empty-frame cache
             if (isset($this->mTplExpandCache[$titleText])) {
                 $text = $this->mTplExpandCache[$titleText];
             } else {
                 $text = $newFrame->expand($text);
                 $this->mTplExpandCache[$titleText] = $text;
             }
         } else {
             # Uncached expansion
             $text = $newFrame->expand($text);
         }
     }
     if ($isLocalObj && $nowiki) {
         $text = $frame->expand($text, PPFrame::RECOVER_ORIG);
         $isLocalObj = false;
     }
     # Replace raw HTML by a placeholder
     # Add a blank line preceding, to prevent it from mucking up
     # immediately preceding headings
     if ($isHTML) {
         $text = "\n\n" . $this->insertStripItem($text);
     } elseif ($nowiki && ($this->ot['html'] || $this->ot['pre'])) {
         $text = wfEscapeWikiText($text);
     } elseif (is_string($text) && !$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\\*)/', $text)) {
         $text = "\n" . $text;
     }
     if (is_string($text) && !$this->incrementIncludeSize('post-expand', strlen($text))) {
         # Error, oversize inclusion
         $text = "[[{$originalTitle}]]" . $this->insertStripItem('<!-- WARNING: template omitted, post-expand include size too large -->');
         $this->limitationWarn('post-expand-template-inclusion');
     }
     if ($isLocalObj) {
         $ret = array('object' => $text);
     } else {
         $ret = array('text' => $text);
     }
     wfProfileOut(__METHOD__);
     return $ret;
 }
 /**
  * Return the text of a template, after recursively
  * replacing any variables or templates within the template.
  *
  * @param array $piece The parts of the template
  *  $piece['text']: matched text
  *  $piece['title']: the title, i.e. the part before the |
  *  $piece['parts']: the parameter array
  * @return string the text of the template
  * @private
  */
 function braceSubstitution($piece)
 {
     global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces;
     $fname = __METHOD__;
     wfProfileIn($fname);
     wfProfileIn(__METHOD__ . '-setup');
     # Flags
     $found = false;
     # $text has been filled
     $nowiki = false;
     # wiki markup in $text should be escaped
     $noparse = false;
     # Unsafe HTML tags should not be stripped, etc.
     $noargs = false;
     # Don't replace triple-brace arguments in $text
     $replaceHeadings = false;
     # Make the edit section links go to the template not the article
     $headingOffset = 0;
     # Skip headings when number, to account for those that weren't transcluded.
     $isHTML = false;
     # $text is HTML, armour it against wikitext transformation
     $forceRawInterwiki = false;
     # Force interwiki transclusion to be done in raw mode not rendered
     # Title object, where $text came from
     $title = NULL;
     $linestart = '';
     # $part1 is the bit before the first |, and must contain only title characters
     # $args is a list of arguments, starting from index 0, not including $part1
     $titleText = $part1 = $piece['title'];
     # If the third subpattern matched anything, it will start with |
     if (null == $piece['parts']) {
         $replaceWith = $this->variableSubstitution(array($piece['text'], $piece['title']));
         if ($replaceWith != $piece['text']) {
             $text = $replaceWith;
             $found = true;
             $noparse = true;
             $noargs = true;
         }
     }
     $args = null == $piece['parts'] ? array() : $piece['parts'];
     wfProfileOut(__METHOD__ . '-setup');
     # SUBST
     wfProfileIn(__METHOD__ . '-modifiers');
     if (!$found) {
         $mwSubst =& MagicWord::get('subst');
         if ($mwSubst->matchStartAndRemove($part1) xor $this->ot['wiki']) {
             # One of two possibilities is true:
             # 1) Found SUBST but not in the PST phase
             # 2) Didn't find SUBST and in the PST phase
             # In either case, return without further processing
             $text = $piece['text'];
             $found = true;
             $noparse = true;
             $noargs = true;
         }
     }
     # MSG, MSGNW and RAW
     if (!$found) {
         # Check for MSGNW:
         $mwMsgnw =& MagicWord::get('msgnw');
         if ($mwMsgnw->matchStartAndRemove($part1)) {
             $nowiki = true;
         } else {
             # Remove obsolete MSG:
             $mwMsg =& MagicWord::get('msg');
             $mwMsg->matchStartAndRemove($part1);
         }
         # Check for RAW:
         $mwRaw =& MagicWord::get('raw');
         if ($mwRaw->matchStartAndRemove($part1)) {
             $forceRawInterwiki = true;
         }
     }
     wfProfileOut(__METHOD__ . '-modifiers');
     //save path level before recursing into functions & templates.
     $lastPathLevel = $this->mTemplatePath;
     # Parser functions
     if (!$found) {
         wfProfileIn(__METHOD__ . '-pfunc');
         $colonPos = strpos($part1, ':');
         if ($colonPos !== false) {
             # Case sensitive functions
             $function = substr($part1, 0, $colonPos);
             if (isset($this->mFunctionSynonyms[1][$function])) {
                 $function = $this->mFunctionSynonyms[1][$function];
             } else {
                 # Case insensitive functions
                 $function = strtolower($function);
                 if (isset($this->mFunctionSynonyms[0][$function])) {
                     $function = $this->mFunctionSynonyms[0][$function];
                 } else {
                     $function = false;
                 }
             }
             if ($function) {
                 $funcArgs = array_map('trim', $args);
                 $funcArgs = array_merge(array(&$this, trim(substr($part1, $colonPos + 1))), $funcArgs);
                 $result = call_user_func_array($this->mFunctionHooks[$function], $funcArgs);
                 $found = true;
                 // The text is usually already parsed, doesn't need triple-brace tags expanded, etc.
                 //$noargs = true;
                 //$noparse = true;
                 if (is_array($result)) {
                     if (isset($result[0])) {
                         $text = $linestart . $result[0];
                         unset($result[0]);
                     }
                     // Extract flags into the local scope
                     // This allows callers to set flags such as nowiki, noparse, found, etc.
                     extract($result);
                 } else {
                     $text = $linestart . $result;
                 }
             }
         }
         wfProfileOut(__METHOD__ . '-pfunc');
     }
     # Template table test
     # Did we encounter this template already? If yes, it is in the cache
     # and we need to check for loops.
     if (!$found && isset($this->mTemplates[$piece['title']])) {
         $found = true;
         # Infinite loop test
         if (isset($this->mTemplatePath[$part1])) {
             $noparse = true;
             $noargs = true;
             $found = true;
             $text = $linestart . "[[{$part1}]]<!-- WARNING: template loop detected -->";
             wfDebug(__METHOD__ . ": template loop broken at '{$part1}'\n");
         } else {
             # set $text to cached message.
             $text = $linestart . $this->mTemplates[$piece['title']];
             #treat title for cached page the same as others
             $ns = NS_TEMPLATE;
             $subpage = '';
             $part1 = $this->maybeDoSubpageLink($part1, $subpage);
             if ($subpage !== '') {
                 $ns = $this->mTitle->getNamespace();
             }
             $title = Title::newFromText($part1, $ns);
             //used by include size checking
             $titleText = $title->getPrefixedText();
             //used by edit section links
             $replaceHeadings = true;
         }
     }
     # Load from database
     if (!$found) {
         wfProfileIn(__METHOD__ . '-loadtpl');
         $ns = NS_TEMPLATE;
         # declaring $subpage directly in the function call
         # does not work correctly with references and breaks
         # {{/subpage}}-style inclusions
         $subpage = '';
         $part1 = $this->maybeDoSubpageLink($part1, $subpage);
         if ($subpage !== '') {
             $ns = $this->mTitle->getNamespace();
         }
         $title = Title::newFromText($part1, $ns);
         if (!is_null($title)) {
             $titleText = $title->getPrefixedText();
             # Check for language variants if the template is not found
             if ($wgContLang->hasVariants() && $title->getArticleID() == 0) {
                 $wgContLang->findVariantLink($part1, $title);
             }
             if (!$title->isExternal()) {
                 if ($title->getNamespace() == NS_SPECIAL && $this->mOptions->getAllowSpecialInclusion() && $this->ot['html']) {
                     $text = SpecialPage::capturePath($title);
                     if (is_string($text)) {
                         $found = true;
                         $noparse = true;
                         $noargs = true;
                         $isHTML = true;
                         $this->disableCache();
                     }
                 } else {
                     if ($wgNonincludableNamespaces && in_array($title->getNamespace(), $wgNonincludableNamespaces)) {
                         $found = false;
                         //access denied
                         wfDebug("{$fname}: template inclusion denied for " . $title->getPrefixedDBkey());
                     } else {
                         list($articleContent, $title) = $this->fetchTemplateAndtitle($title);
                         if ($articleContent !== false) {
                             $found = true;
                             $text = $articleContent;
                             $replaceHeadings = true;
                         }
                     }
                 }
                 # If the title is valid but undisplayable, make a link to it
                 if (!$found && ($this->ot['html'] || $this->ot['pre'])) {
                     $text = "[[:{$titleText}]]";
                     $found = true;
                 }
             } elseif ($title->isTrans()) {
                 // Interwiki transclusion
                 if ($this->ot['html'] && !$forceRawInterwiki) {
                     $text = $this->interwikiTransclude($title, 'render');
                     $isHTML = true;
                     $noparse = true;
                 } else {
                     $text = $this->interwikiTransclude($title, 'raw');
                     $replaceHeadings = true;
                 }
                 $found = true;
             }
             # Template cache array insertion
             # Use the original $piece['title'] not the mangled $part1, so that
             # modifiers such as RAW: produce separate cache entries
             if ($found) {
                 if ($isHTML) {
                     // A special page; don't store it in the template cache.
                 } else {
                     $this->mTemplates[$piece['title']] = $text;
                 }
                 $text = $linestart . $text;
             }
         }
         wfProfileOut(__METHOD__ . '-loadtpl');
     }
     if ($found && !$this->incrementIncludeSize('pre-expand', strlen($text))) {
         # Error, oversize inclusion
         $text = $linestart . "[[{$titleText}]]<!-- WARNING: template omitted, pre-expand include size too large -->";
         $noparse = true;
         $noargs = true;
     }
     # Recursive parsing, escaping and link table handling
     # Only for HTML output
     if ($nowiki && $found && ($this->ot['html'] || $this->ot['pre'])) {
         $text = wfEscapeWikiText($text);
     } elseif (!$this->ot['msg'] && $found) {
         if ($noargs) {
             $assocArgs = array();
         } else {
             # Clean up argument array
             $assocArgs = self::createAssocArgs($args);
             # Add a new element to the templace recursion path
             $this->mTemplatePath[$part1] = 1;
         }
         if (!$noparse) {
             # If there are any <onlyinclude> tags, only include them
             if (in_string('<onlyinclude>', $text) && in_string('</onlyinclude>', $text)) {
                 $replacer = new OnlyIncludeReplacer();
                 StringUtils::delimiterReplaceCallback('<onlyinclude>', '</onlyinclude>', array(&$replacer, 'replace'), $text);
                 $text = $replacer->output;
             }
             # Remove <noinclude> sections and <includeonly> tags
             $text = StringUtils::delimiterReplace('<noinclude>', '</noinclude>', '', $text);
             $text = strtr($text, array('<includeonly>' => '', '</includeonly>' => ''));
             if ($this->ot['html'] || $this->ot['pre']) {
                 # Strip <nowiki>, <pre>, etc.
                 $text = $this->strip($text, $this->mStripState);
                 if ($this->ot['html']) {
                     $text = Sanitizer::removeHTMLtags($text, array(&$this, 'replaceVariables'), $assocArgs);
                 } elseif ($this->ot['pre'] && $this->mOptions->getRemoveComments()) {
                     $text = Sanitizer::removeHTMLcomments($text);
                 }
             }
             $text = $this->replaceVariables($text, $assocArgs);
             # If the template begins with a table or block-level
             # element, it should be treated as beginning a new line.
             if (!$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\\*)/', $text)) {
                 $text = "\n" . $text;
             }
         } elseif (!$noargs) {
             # $noparse and !$noargs
             # Just replace the arguments, not any double-brace items
             # This is used for rendered interwiki transclusion
             $text = $this->replaceVariables($text, $assocArgs, true);
         }
     }
     # Prune lower levels off the recursion check path
     $this->mTemplatePath = $lastPathLevel;
     if ($found && !$this->incrementIncludeSize('post-expand', strlen($text))) {
         # Error, oversize inclusion
         $text = $linestart . "[[{$titleText}]]<!-- WARNING: template omitted, post-expand include size too large -->";
         $noparse = true;
         $noargs = true;
     }
     if (!$found) {
         wfProfileOut($fname);
         return $piece['text'];
     } else {
         wfProfileIn(__METHOD__ . '-placeholders');
         if ($isHTML) {
             # Replace raw HTML by a placeholder
             # Add a blank line preceding, to prevent it from mucking up
             # immediately preceding headings
             $text = "\n\n" . $this->insertStripItem($text, $this->mStripState);
         } else {
             # replace ==section headers==
             # XXX this needs to go away once we have a better parser.
             if (!$this->ot['wiki'] && !$this->ot['pre'] && $replaceHeadings) {
                 if (!is_null($title)) {
                     $encodedname = base64_encode($title->getPrefixedDBkey());
                 } else {
                     $encodedname = base64_encode("");
                 }
                 $m = preg_split('/(^={1,6}.*?={1,6}\\s*?$)/m', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
                 $text = '';
                 $nsec = $headingOffset;
                 for ($i = 0; $i < count($m); $i += 2) {
                     $text .= $m[$i];
                     if (!isset($m[$i + 1]) || $m[$i + 1] == "") {
                         continue;
                     }
                     $hl = $m[$i + 1];
                     if (strstr($hl, "<!--MWTEMPLATESECTION")) {
                         $text .= $hl;
                         continue;
                     }
                     $m2 = array();
                     preg_match('/^(={1,6})(.*?)(={1,6}\\s*?)$/m', $hl, $m2);
                     $text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION=" . $encodedname . "&" . base64_encode("{$nsec}") . "-->" . $m2[3];
                     $nsec++;
                 }
             }
         }
         wfProfileOut(__METHOD__ . '-placeholders');
     }
     # Prune lower levels off the recursion check path
     $this->mTemplatePath = $lastPathLevel;
     if (!$found) {
         wfProfileOut($fname);
         return $piece['text'];
     } else {
         wfProfileOut($fname);
         return $text;
     }
 }