/** * Return the text to be used for a given extension tag. * This is the ghost of strip(). * * @param $params Associative array of parameters: * name PPNode for the tag name * attr PPNode for unparsed text where tag attributes are thought to be * attributes Optional associative array of parsed attributes * inner Contents of extension element * noClose Original text did not have a close tag * @param $frame PPFrame * * @return string */ function extensionSubstitution($params, $frame) { $name = $frame->expand($params['name']); $attrText = !isset($params['attr']) ? null : $frame->expand($params['attr']); $content = !isset($params['inner']) ? null : $frame->expand($params['inner']); # RTE (Rich Text Editor) - begin # @author: Inez Korczyński global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { $wikitextIdx = RTEMarker::getDataIdx(RTEMarker::EXT_WIKITEXT, $content); # Allow parser extensions to generate their own placeholders (instead of default one from RTE) # @author: Macbre if (wfRunHooks('RTEUseDefaultPlaceholder', array($name, $params, $frame, $wikitextIdx))) { if ($wikitextIdx !== null) { $dataIdx = RTEData::put('placeholder', array('type' => 'ext', 'wikitextIdx' => $wikitextIdx)); return RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); } } else { RTE::log(__METHOD__, "skipped default placeholder for <{$name}>"); // restore value of $content $content = RTEData::get('wikitext', $wikitextIdx); // keep inner content of tag $content = preg_replace('#^<[^>]+>(.*)<[^>]+>$#s', '\\1', $content); } } # RTE - end $marker = "{$this->mUniqPrefix}-{$name}-" . sprintf('%08X', $this->mMarkerIndex++) . self::MARKER_SUFFIX; $isFunctionTag = isset($this->mFunctionTagHooks[strtolower($name)]) && ($this->ot['html'] || $this->ot['pre']); if ($isFunctionTag) { $markerType = 'none'; } else { $markerType = 'general'; } if ($this->ot['html'] || $isFunctionTag) { $name = strtolower($name); # PLB - begin # @author: Tomasz Odrobny $this->mCurrentTagName = $name; # PLB - end $attributes = Sanitizer::decodeTagAttributes($attrText); if (isset($params['attributes'])) { $attributes = $attributes + $params['attributes']; } if (isset($this->mTagHooks[$name])) { # Workaround for PHP bug 35229 and similar if (!is_callable($this->mTagHooks[$name])) { throw new MWException("Tag hook for {$name} is not callable\n"); } wfRunHooks('ParserTagHooksBeforeInvoke', [$name, $marker, $content, $attributes, $this, $frame]); $output = call_user_func_array($this->mTagHooks[$name], array($content, $attributes, $this, $frame)); } elseif (isset($this->mFunctionTagHooks[$name])) { list($callback, $flags) = $this->mFunctionTagHooks[$name]; if (!is_callable($callback)) { throw new MWException("Tag hook for {$name} is not callable\n"); } $output = call_user_func_array($callback, array(&$this, $frame, $content, $attributes)); } else { $output = '<span class="error">Invalid tag extension name: ' . htmlspecialchars($name) . '</span>'; } if (is_array($output)) { # Extract flags to local scope (to override $markerType) $flags = $output; $output = $flags[0]; unset($flags[0]); extract($flags); } } else { if (is_null($attrText)) { $attrText = ''; } if (isset($params['attributes'])) { foreach ($params['attributes'] as $attrName => $attrValue) { $attrText .= ' ' . htmlspecialchars($attrName) . '="' . htmlspecialchars($attrValue) . '"'; } } if ($content === null) { $output = "<{$name}{$attrText}/>"; } else { $close = is_null($params['close']) ? '' : $frame->expand($params['close']); $output = "<{$name}{$attrText}>{$content}{$close}"; } } if ($markerType === 'none') { return $output; } elseif ($markerType === 'nowiki') { $this->mStripState->addNoWiki($marker, $output); } elseif ($markerType === 'general') { $this->mStripState->addGeneral($marker, $output); } else { throw new MWException(__METHOD__ . ': invalid marker type'); } return $marker; }
/** * @throws MWException * @param $root * @param $flags int * @return string */ function expand($root, $flags = 0) { static $expansionDepth = 0; if (is_string($root)) { return $root; } if (++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount()) { return '<span class="error">Node-count limit exceeded</span>'; } if ($expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth()) { return '<span class="error">Expansion depth limit exceeded</span>'; } wfProfileIn(__METHOD__); ++$expansionDepth; if ($root instanceof PPNode_DOM) { $root = $root->node; } if ($root instanceof DOMDocument) { $root = $root->documentElement; } $outStack = array('', ''); $iteratorStack = array(false, $root); $indexStack = array(0, 0); $RTEext_1 = false; $RTEext_2 = false; while (count($iteratorStack) > 1) { if ($RTEext_1) { $RTEext_1 = false; $RTEext_2 = true; } $level = count($outStack) - 1; $iteratorNode =& $iteratorStack[$level]; $out =& $outStack[$level]; $index =& $indexStack[$level]; if ($iteratorNode instanceof PPNode_DOM) { $iteratorNode = $iteratorNode->node; } if (is_array($iteratorNode)) { if ($index >= count($iteratorNode)) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode[$index]; $index++; } } elseif ($iteratorNode instanceof DOMNodeList) { if ($index >= $iteratorNode->length) { // All done with this iterator $iteratorStack[$level] = false; $contextNode = false; } else { $contextNode = $iteratorNode->item($index); $index++; } } else { // Copy to $contextNode and then delete from iterator stack, // because this is not an iterator but we do have to execute it once $contextNode = $iteratorStack[$level]; $iteratorStack[$level] = false; } if ($contextNode instanceof PPNode_DOM) { $contextNode = $contextNode->node; } $newIterator = false; if ($contextNode === false) { // nothing to do } elseif (is_string($contextNode)) { $out .= $contextNode; } elseif (is_array($contextNode) || $contextNode instanceof DOMNodeList) { $newIterator = $contextNode; } elseif ($contextNode instanceof DOMNode) { if ($contextNode->nodeType == XML_TEXT_NODE) { # RTE (Rich Text Editor) - begin global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { if ($RTEext_2) { if (strpos($contextNode->nodeValue, 'table') !== false) { RTE::$edgeCases[] = 'COMPLEX.11'; } } } # RTE - end $out .= $contextNode->nodeValue; } elseif ($contextNode->nodeName == 'template') { # Double-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); # RTE (Rich Text Editor) - begin # @author: Inez Korczyński global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { $dataIdx = RTEData::put('placeholder', array('type' => 'double-brackets', 'wikitextIdx' => $contextNode->getAttribute('_rte_wikitextidx'), 'lineStart' => $contextNode->getAttribute('lineStart'), 'title' => $title->textContent)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); } else { if ($flags & PPFrame::NO_TEMPLATES) { $newIterator = $this->virtualBracketedImplode('{{', '|', '}}', $title, $parts); } else { $lineStart = $contextNode->getAttribute('lineStart'); $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts), 'lineStart' => $lineStart); $ret = $this->parser->braceSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } # RTE - end } elseif ($contextNode->nodeName == 'tplarg') { # Triple-brace expansion $xpath = new DOMXPath($contextNode->ownerDocument); $titles = $xpath->query('title', $contextNode); $title = $titles->item(0); $parts = $xpath->query('part', $contextNode); # RTE (Rich Text Editor) - begin # @author: Wladyslaw Bodzek global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { //var_dump($contextNode->getAttribute('_rte_wikitextidx')); $dataIdx = RTEData::put('placeholder', array('type' => 'tplarg', 'wikitextIdx' => $contextNode->getAttribute('_rte_wikitextidx'), 'lineStart' => $contextNode->getAttribute('lineStart'), 'title' => $title->textContent)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); } else { if ($flags & PPFrame::NO_ARGS) { $newIterator = $this->virtualBracketedImplode('{{{', '|', '}}}', $title, $parts); } else { $params = array('title' => new PPNode_DOM($title), 'parts' => new PPNode_DOM($parts)); $ret = $this->parser->argSubstitution($params, $this); if (isset($ret['object'])) { $newIterator = $ret['object']; } else { $out .= $ret['text']; } } } # RTE - end } elseif ($contextNode->nodeName == 'comment') { # HTML-style comment # Remove it in HTML, pre+remove and STRIP_COMMENTS modes if ($this->parser->ot['html'] || $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() || $flags & PPFrame::STRIP_COMMENTS) { # RTE (Rich Text Editor) - begin # @author: Inez Korczyński global $wgRTEParserEnabled; if (!empty($wgRTEParserEnabled)) { if (strlen($out) === 0 || substr($out, -1) == "\n") { if (substr($contextNode->textContent, -1) == "\n") { $add = "\n"; $text = substr($contextNode->textContent, 0, -1); } else { $add = ""; $text = $contextNode->textContent; } $dataIdx = RTEData::put('placeholder', array('type' => 'comment', 'wikitext' => $text)); $out .= RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx) . $add; } else { RTE::$edgeCases[] = 'COMMENT'; $out .= ''; } } else { $out .= ''; } # RTE - end } elseif ($this->parser->ot['wiki'] && !($flags & PPFrame::RECOVER_COMMENTS)) { $out .= $this->parser->insertStripItem($contextNode->textContent); } else { $out .= $contextNode->textContent; } } elseif ($contextNode->nodeName == 'ignore') { # Output suppression used by <includeonly> etc. # OT_WIKI will only respect <ignore> in substed templates. # The other output types respect it unless NO_IGNORE is set. # extractSections() sets NO_IGNORE and so never respects it. if (!isset($this->parent) && $this->parser->ot['wiki'] || $flags & PPFrame::NO_IGNORE) { $out .= $contextNode->textContent; } else { $out .= ''; } } elseif ($contextNode->nodeName == 'ext') { # Extension tag $xpath = new DOMXPath($contextNode->ownerDocument); $names = $xpath->query('name', $contextNode); $attrs = $xpath->query('attr', $contextNode); $inners = $xpath->query('inner', $contextNode); $closes = $xpath->query('close', $contextNode); $params = array('name' => new PPNode_DOM($names->item(0)), 'attr' => $attrs->length > 0 ? new PPNode_DOM($attrs->item(0)) : null, 'inner' => $inners->length > 0 ? new PPNode_DOM($inners->item(0)) : null, 'close' => $closes->length > 0 ? new PPNode_DOM($closes->item(0)) : null); $out .= $this->parser->extensionSubstitution($params, $this); $RTEext_1 = true; } elseif ($contextNode->nodeName == 'h') { # Heading $s = $this->expand($contextNode->childNodes, $flags); # Insert a heading marker only for <h> children of <root> # This is to stop extractSections from going over multiple tree levels if ($contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html']) { # Insert heading index marker $headingIndex = $contextNode->getAttribute('i'); $titleText = $this->title->getPrefixedDBkey(); $this->parser->mHeadings[] = array($titleText, $headingIndex); $serial = count($this->parser->mHeadings) - 1; $marker = "{$this->parser->mUniqPrefix}-h-{$serial}-" . Parser::MARKER_SUFFIX; $count = $contextNode->getAttribute('level'); $s = substr($s, 0, $count) . $marker . substr($s, $count); $this->parser->mStripState->addGeneral($marker, ''); } $out .= $s; } else { # Generic recursive expansion $newIterator = $contextNode->childNodes; } } else { wfProfileOut(__METHOD__); throw new MWException(__METHOD__ . ': Invalid parameter type'); } if ($newIterator !== false) { if ($newIterator instanceof PPNode_DOM) { $newIterator = $newIterator->node; } $outStack[] = ''; $iteratorStack[] = $newIterator; $indexStack[] = 0; } elseif ($iteratorStack[$level] === false) { // Return accumulated value to parent // With tail recursion while ($iteratorStack[$level] === false && $level > 0) { $outStack[$level - 1] .= $out; array_pop($outStack); array_pop($iteratorStack); array_pop($indexStack); $level--; } } $RTEext_2 = false; } --$expansionDepth; wfProfileOut(__METHOD__); return $outStack[0]; }
/** * Returns placeholder for broken image link * * This one is called directly by RTEParser::makeImage() * */ public static function makeBrokenImageLinkObj($title, $html = '', $query = '', $trail = '', $prefix = '', $time = false, $wikitextIdx = null) { wfProfileIn(__METHOD__); if (!$title instanceof Title) { throw new \Exception('$title is not Title class instance'); } // try to resolve internal links in broken image caption (RT #90616) $wikitext = RTEData::get('wikitext', $wikitextIdx); if (RTEData::resolveLinksInMediaCaption($wikitext)) { // update wikitext data $wikitextIdx = RTEData::put('wikitext', $wikitext); } $ret = RTEMarker::generate(RTEMarker::PLACEHOLDER, RTEData::put('placeholder', array('type' => 'broken-image', 'wikitextIdx' => $wikitextIdx, 'title' => $title->getDBkey()))); wfProfileOut(__METHOD__); return $ret; }
public static function doDoubleUnderscoreReplace($matches) { wfProfileIn(__METHOD__); $dataIdx = RTEData::put('placeholder', array('type' => 'double-underscore', 'wikitext' => $matches[0])); $ret = RTEMarker::generate(RTEMarker::PLACEHOLDER, $dataIdx); wfProfileOut(__METHOD__); return $ret; }