/** * Perform reverse parsing from HTML to wikitext */ public static function html2wiki() { global $wgRequest; $html = $wgRequest->getVal('html', ''); RTE::log(__METHOD__, $html); $wikitext = RTE::HtmlToWikitext($html); return array('wikitext' => $wikitext); }
/** * Check for __NOWYSIWYG__ magic word inside transcluded templates (RT #24167) - and add edgecase if needed * * Note: hook Parser::FetchTemplateAndTitle is not called when doing RTE parsing */ public static function fetchTemplate($text, $finalTitle) { wfProfileIn(__METHOD__); RTE::log(__METHOD__, $finalTitle); if (self::searchForMagicWord($text)) { RTE::log('__NOWYSIWYG__ found inside ' . $finalTitle); RTE::edgeCasesPush('NOWYSIWYG'); } wfProfileOut(__METHOD__); return true; }
protected function createEditPage($sPostBody) { $oArticle = new Article(Title::makeTitle(NS_MAIN, '')); $this->mEditPage = new EditPage($oArticle); $this->mEditPage->textbox1 = $sPostBody; // fix for RT #33844 - run hook fired by "classical" EditPage // Allow extensions to modify edit form global $wgEnableRTEExt, $wgRequest; if (!empty($wgEnableRTEExt)) { wfRunHooks('AlternateEdit', array($this->mEditPage)); $this->mEditPage->textbox1 = $wgRequest->getVal('wpTextbox1'); RTE::log(__METHOD__ . '::wikitext', $this->mEditPage->textbox1); } // fix for RT #38845 - allow for preloading text content if (!$wgRequest->wasPosted()) { wfRunHooks('EditFormPreloadText', array(&$this->mEditPage->textbox1, &$this->mEditPage->mTitle)); } }
/** * Check given link whether it is in Category/File namespace (refs RT #41323) */ private static function isNamespacedLink($node, $data) { wfProfileIn(__METHOD__); $ret = false; if (strpos($data['link'], ':') !== false) { // get localised names of namespaces and cache them global $wgContLang; static $namespaces = false; if ($namespaces === false) { $namespaces = array($wgContLang->getNsText(NS_CATEGORY), $wgContLang->getNsText(NS_FILE)); RTE::log(__METHOD__, 'got localised namespaces'); } foreach ($namespaces as $NSprefix) { if (substr($data['link'], 0, strlen($NSprefix) + 1) == $NSprefix . ':') { RTE::log(__METHOD__, $data['link']); $ret = true; break; } } } wfProfileOut(__METHOD__); return $ret; }
/** * 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; }
protected function createEditPage($sPostBody) { $oArticle = new Article(Title::makeTitle(NS_BLOG_ARTICLE, 'New or Updated Blog Post')); $this->mEditPage = new EditPage($oArticle); $this->mEditPage->textbox1 = $sPostBody; // this applies user preferences, such as minor and watchlist // EditPage::getContent was called twice (causes BugId:4604) // beware: dirty copy&paste of the code (will be replaced by RTE reskin) global $wgUser; # Sort out the "watch" checkbox if ($wgUser->getOption('watchdefault')) { # Watch all edits $this->mEditPage->watchthis = true; } elseif ($wgUser->getOption('watchcreations') && !$this->mEditPage->mTitle->exists()) { # Watch creations $this->mEditPage->watchthis = true; } elseif ($this->mEditPage->mTitle->userIsWatching()) { # Already watched $this->mEditPage->watchthis = true; } if ($wgUser->getOption('minordefault')) { $this->mEditPage->minoredit = true; } // fix for RT #33844 - run hook fired by "classical" EditPage // Allow extensions to modify edit form global $wgEnableRTEExt, $wgRequest; if (!empty($wgEnableRTEExt)) { $wgRequest->setVal('wpTextbox1', $sPostBody); // RT #34055 wfRunHooks('AlternateEdit', array(&$this->mEditPage)); $this->mEditPage->textbox1 = $wgRequest->getVal('wpTextbox1'); RTE::log(__METHOD__ . '::wikitext', $this->mEditPage->textbox1); } }
/** * Convert wikitext to HTML and add extra HTML attributes for RTE * * @param $text String: text we want to parse * @param $title A title object * @param $options ParserOptions * @param $linestart boolean * @param $clearState boolean * @param $revid Int: number to pass in {{REVISIONID}} * @return ParserOutput a ParserOutput */ public function parse($text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null) { wfProfileIn(__METHOD__); // get rid of all \r in wikitext $text = str_replace("\r\n", "\n", $text); // count newlines at the beginning of the wikitext $emptyLinesAtStart = strspn($text, "\n"); if ($emptyLinesAtStart > 0) { // don't double count empty lines at the beginning of wikitext $this->emptyLinesBefore = $emptyLinesAtStart * -1; } // mark HTML entities from wikitext (& : _) $text = self::markEntities($text); // set flag indicating parsing for CK global $wgRTEParserEnabled; $wgRTEParserEnabled = true; // don't use XML cache in preprocessor global $wgPreprocessorCacheThreshold; $wgPreprocessorCacheThreshold = 1000000; //RTE::log(__METHOD__ . '::beforeParse', $text); // parse to HTML $output = parent::parse($text, $title, $options, $linestart, $clearState, $revid); $wgRTEParserEnabled = false; // add extra RTE attributes to HTML elements (for correct handling of spaces and newlines when parsing back to wikitext) $html = $output->getText(); // add RTE_EMPTY_LINES_BEFORE comment if ($emptyLinesAtStart > 0) { $html = RTEReverseParser::addEmptyLinesBeforeComment($emptyLinesAtStart) . $html; } //RTE::log(__METHOD__ . '::beforeReplace', $html); // wrap HTML entities inside span "placeholders" (& : _) $html = self::wrapEntities($html); wfProfileIn(__METHOD__ . '::regexp'); // remove EMPTY_LINES_BEFORE comments which are before closing tags - refs RT#38889 // <!-- RTE_EMPTY_LINES_BEFORE_1 --></td></tr></table> <= remove this one // <!-- RTE_EMPTY_LINES_BEFORE_1 --><p> $html = preg_replace('%<!-- RTE_EMPTY_LINES_BEFORE_(\\d+) -->(</[^>]+></)%s', '\\2', $html); // move empty lines counter data from comment to next opening tag attribute (thx to Marooned) $html = preg_replace('%<!-- RTE_EMPTY_LINES_BEFORE_(\\d+) -->(?!<!)(.*?)(<[^/][^>]*)>%s', '\\2\\3 data-rte-empty-lines-before="\\1">', $html); // remove not replaced EMPTY_LINES_BEFORE comments // <!-- RTE_EMPTY_LINES_BEFORE_1 -- data-rte-empty-lines-before="1"> $html = preg_replace('%<!-- RTE_EMPTY_LINES_BEFORE_(\\d+) [^>]+>%s', '', $html); // add data-rte-spaces-before for list items and table cells $html = preg_replace_callback("/<(li|dd|dt|td|th)([^>]*)>( +)/", 'RTEParser::spacesBeforeCallback', $html); // replace placeholder markers with placeholders $html = preg_replace_callback("/-01-(\\d{4})/", 'RTE::replacePlaceholder', $html); // replace dataidx attribute with data-rte-meta attribute storing JSON encoded meta data $html = preg_replace_callback('/ _rte_dataidx="(\\d{4})" /', 'RTEData::replaceIdxByData', $html); $html = preg_replace("/-(?:" . RTEMarker::INTERNAL_WIKITEXT . "|" . RTEMarker::EXTERNAL_WIKITEXT . ")-\\d{4}/", '', $html); // RT#40786: add empty paragraphs between headings (</h3>\n<h3 ...) $html = preg_replace("%(</h\\d>\\s)(<h\\d)%s", '$1<p data-rte-filler="true"></p>$2', $html); wfProfileOut(__METHOD__ . '::regexp'); // add extra attribute for p tags coming from parser $html = strtr($html, array('<p>' => '<p data-rte-fromparser="true">', '<p ' => '<p data-rte-fromparser="true" ')); // add empty paragraph for new / empty pages if ($html == '') { $html = Xml::element('p'); } // update parser output RTE::log(__METHOD__, $html); $output->setText($html); wfProfileOut(__METHOD__); return $output; }
/** * Check whether current browser is compatible with RTE * * FCKeditor - The text editor for Internet - http://www.fckeditor.net * Copyright (C) 2003-2009 Frederico Caldeira Knabben */ private static function isCompatibleBrowser() { wfProfileIn(__METHOD__); if (isset($_SERVER) && isset($_SERVER['HTTP_USER_AGENT'])) { $sAgent = $_SERVER['HTTP_USER_AGENT']; } else { global $HTTP_SERVER_VARS; if (isset($HTTP_SERVER_VARS) && isset($HTTP_SERVER_VARS['HTTP_USER_AGENT'])) { $sAgent = $HTTP_SERVER_VARS['HTTP_USER_AGENT']; } else { global $HTTP_USER_AGENT; $sAgent = $HTTP_USER_AGENT; } } RTE::log(__METHOD__, $sAgent); $ret = false; if (strpos($sAgent, 'Chrome') !== false) { $ret = true; } else { if (strpos($sAgent, 'MSIE') !== false && strpos($sAgent, 'mac') === false && strpos($sAgent, 'Opera') === false) { $iVersion = (double) substr($sAgent, strpos($sAgent, 'MSIE') + 5, 3); $ret = $iVersion >= 7.0; } else { if (strpos($sAgent, 'Gecko/') !== false) { $ret = true; } else { if (strpos($sAgent, 'Opera/') !== false) { $fVersion = (double) substr($sAgent, strpos($sAgent, 'Opera/') + 6, 4); $ret = $fVersion >= 9.5; } else { if (strpos($sAgent, 'Mobile') !== false && strpos($sAgent, 'Safari') !== false) { // disable for mobile devices from Apple (RT #38829) $ret = false; } else { if (preg_match("|AppleWebKit/(\\d+)|i", $sAgent, $matches)) { $ret = $matches[1] >= 522; } } } } } } RTE::log(__METHOD__, $ret ? 'yes' : 'no'); wfProfileOut(__METHOD__); return $ret; }
/** * Check whether current browser is compatible with RTE * * FCKeditor - The text editor for Internet - http://www.fckeditor.net * Copyright (C) 2003-2009 Frederico Caldeira Knabben */ private static function isCompatibleBrowser() { wfProfileIn(__METHOD__); if (isset($_SERVER) && isset($_SERVER['HTTP_USER_AGENT'])) { $sAgent = $_SERVER['HTTP_USER_AGENT']; } else { global $HTTP_SERVER_VARS; if (isset($HTTP_SERVER_VARS) && isset($HTTP_SERVER_VARS['HTTP_USER_AGENT'])) { $sAgent = $HTTP_SERVER_VARS['HTTP_USER_AGENT']; } else { global $HTTP_USER_AGENT; $sAgent = $HTTP_USER_AGENT; } } RTE::log(__METHOD__, $sAgent); $ret = true; if (strpos($sAgent, 'Mobile') !== false && strpos($sAgent, 'Safari') !== false) { // disable for mobile devices from Apple (RT #38829) $ret = false; } // Disable for IE 11 (VE-675). RTE should be gone by the time IE 12 rolls out, so it's // not necessary to match for future versions. if (strpos($sAgent, 'Trident/') !== false && strpos($sAgent, 'rv:11.0') !== false) { $ret = false; } RTE::log(__METHOD__, $ret ? 'yes' : 'no'); wfProfileOut(__METHOD__); return $ret; }