/** * Convert editor content from one format to another. * * @param $content The content to convert from the request object or passed in as a string * @param $convertToFormat The format to convert to ('richtext' or 'wikitext') * @param $response (optional) The response object to add edgeCases to */ public static function convertContent($content = '', $convertToFormat = '', WikiaResponse $response = null) { if (class_exists('RTE')) { // Clear out edge cases to avoid polluting future requests RTE::$edgeCases = array(); if (!empty($content) && !empty($convertToFormat)) { if ($convertToFormat == 'richtext') { $html = RTE::WikitextToHtml($content); if (empty(RTE::$edgeCases)) { $content = $html; // Edge cases were found, add them to the response object (if provided) } else { if (!is_null($response)) { $response->setVal('edgeCases', RTE::$edgeCases); } } } else { if ($convertToFormat == 'wikitext') { $content = RTE::HtmlToWikitext($content); } } } } return $content; }
/** * Reverse parse wikitext when performing diff for edit conflict */ static function onEditPageBeforeConflictDiff(&$editform, &$out) { $helper = EditPageLayoutHelper::getInstance(); if (class_exists('RTE') && $helper->getRequest()->getVal('RTEMode') == 'wysiwyg') { $editform->textbox2 = RTE::HtmlToWikitext($editform->textbox2); } return true; }
public static function convertDataToAttributes($data) { // get ID of current CK instance $instance = RTE::getInstanceId(); // properly encode JSON $encoded = RTEReverseParser::encodeRTEData($data); $encoded = Sanitizer::encodeAttribute($encoded); return " data-rte-meta=\"{$encoded}\" data-rte-instance=\"{$instance}\" "; }
/** * 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)); } }
public function makeGlobalVariables() { $app = F::app(); $vars = array(); // RTE has been disabled but minieditor is enabled. probably shouldn't be allowed to happen if (!class_exists('RTE')) { $vars['RTEDisabledReason'] = 'sitedisabled'; } else { // Need to call RTE::init to get Disabled reason (if any, usually preferences) $ep = new EditPage(new Article(new Title())); RTE::init($ep); RTE::makeGlobalVariablesScript($vars); // pass by reference } // FIXME: We have to force AssetsManager to combine scripts. // MiniEditor will break in loadOnDemand mode because of improper script execution order. $minify = empty($this->wg->DevelEnvironment); // If RTE is disabled, fall back to the mediawiki editor if (isset($vars['RTEDisabledReason'])) { $assetList = AssetsManager::getInstance()->getGroupCommonURL('mini_editor_js', array(), true, $minify); } else { $assetList = AssetsManager::getInstance()->getGroupCommonURL('mini_editor_rte_js', array(), true, $minify); } // Save our asset list for loading in JavaScript $vars['wgMiniEditorAssets'] = $assetList; // EditPage needs to know if this is an article page, and it is not. $vars['wgIsArticle'] = false; // Image uploading can only happen on action=edit pages, make it so. $vars['wgAction'] = 'edit'; // Extensions use hooks to load their setup only on edit pages (VideoEmbedTool, WikiaMiniUploader) // To load ONLY the vars we need and not all of them we will call the setup functions directly if ($app->wg->EnableVideoToolExt) { VETSetupVars($vars); // pass by reference } if ($app->wg->EnableWikiaMiniUploadExt) { WMUSetupVars($vars); // pass by reference } $this->response->setData($vars); }
/** * 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; }
/** * Render template for <body> tag content */ public function executeEditPage() { wfProfileIn(__METHOD__); $helper = F::build('EditPageLayoutHelper'); $editPage = $helper->getEditPage(); if ($helper->fullScreen) { // add stylesheet $this->wg->Out->addStyle(AssetsManager::getInstance()->getSassCommonURL('extensions/wikia/EditPageLayout/css/EditPageLayout.scss')); $packageName = 'epl'; if (class_exists('RTE') && RTE::isEnabled() && !$editPage->isReadOnlyPage()) { $packageName = 'eplrte'; } $srcs = F::build('AssetsManager', array(), 'getInstance')->getGroupCommonURL($packageName); $wgJsMimeType = $this->wg->JsMimeType; foreach ($srcs as $src) { $this->wg->Out->addScript("<script type=\"{$wgJsMimeType}\" src=\"{$src}\"></script>"); } } // render WikiLogo $response = $this->app->sendRequest('WikiHeader', 'Wordmark'); // move wordmark data $this->wordmark = $response->getData(); // render global and user navigation $this->header = F::app()->renderView('GlobalHeader', 'Index'); // Editing [foo] $this->title = $editPage->getEditedTitle(); $section = $this->wg->Request->getVal('section'); // Is user logged in? $this->isLoggedIn = $this->wg->User->isLoggedIn(); // Text for Edit summary label $wpSummaryLabelText = 'editpagelayout-edit-summary-label'; if ($section == 'new') { $msgKey = 'editingcomment'; // If adding new section to page, change label text (BugId: 7243) $wpSummaryLabelText = 'editpagelayout-subject-headline-label'; } else { if (is_numeric($section)) { $msgKey = 'editingsection'; } else { $msgKey = 'editing'; } } // title $this->titleText = $this->title->getPrefixedText(); if ($this->titleText == '') { $this->titleText = ' '; } // limit title length if (mb_strlen($this->titleText) > self::TITLE_MAX_LENGTH) { $this->titleShortText = htmlspecialchars(mb_substr($this->titleText, 0, self::TITLE_MAX_LENGTH)) . '…'; } else { $this->titleShortText = htmlspecialchars($this->titleText); } $this->editing = wfMsg($msgKey, ''); $this->wpSummaryLabelText = wfMsg($wpSummaryLabelText); // render help link and point the link to new tab $this->helpLink = $this->app->runFunction('wfMsgExt', 'editpagelayout-helpLink', array('parseinline')); $this->helpLink = str_replace('<a ', '<a target="_blank" ', $this->helpLink); // action for edit form $this->editFormAction = $editPage->getFormAction(); // preloads $this->editPagePreloads = $editPage->getEditPagePreloads(); // minor edit checkbox (BugId:6461) $this->minorEditCheckbox = !empty($editPage->minoredit); // summary box $this->summaryBox = $editPage->renderSummaryBox(); // extra buttons $this->buttons = $editPage->getControlButtons(); // extra checkboxes $this->customCheckboxes = $editPage->getCustomCheckboxes(); // dismissable notifications $this->notices = $editPage->getNotices(); $this->noticesHtml = $editPage->getNoticesHtml(); // notifications link (BugId:7951) $this->notificationsLink = count($this->notices) == 0 ? $this->app->runFunction('wfMsg', 'editpagelayout-notificationsLink-none') : $this->app->runFunction('wfMsgExt', 'editpagelayout-notificationsLink', array('parsemag'), count($this->notices)); // check if we're in read only mode // disable edit form when in read-only mode if ($this->app->runFunction('wfReadOnly')) { $this->bodytext = '<div id="mw-read-only-warning" class="WikiaArticle">' . $this->app->runFunction('wfMsg', 'oasis-editpage-readonlywarning', $this->app->runFunction('wfReadOnlyReason')) . '</div>'; wfDebug(__METHOD__ . ": edit form disabled because read-only mode is on\n"); } $this->hideTitle = $editPage->hideTitle; $this->wf->RunHooks('EditPageLayoutExecute', array($this)); wfProfileOut(__METHOD__); }
/** * 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; }
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); } }
protected function showTextbox1($customAttribs = null, $textoverride = null) { if (!empty($this->mSpecialPage)) { if ($this->mSpecialPage->showOwnTextbox()) { return true; } } // MW core says: In an edit conflict bypass the overrideable content form method // and fallback to the raw wpTextbox1 // Wikia: in visual mode we want to show HTML (BugId:5428) if ($this->isConflict) { $textoverride = $this->getContent(); // parse it for visual editor (if needed) - BugId:7956 if ($this->app->wg->Request->wasPosted()) { if ($this->app->wg->Request->getVal('RTEMode') == 'wysiwyg') { $textoverride = RTE::WikitextToHtml($textoverride); } } } parent::showTextbox1($customAttribs, $textoverride); }
/** * Reverse parse wikitext when performing diff for edit conflict */ function onEditPageBeforeConflictDiff(&$editform, &$out) { if (class_exists('RTE') && $this->request->getVal('RTEMode') == 'wysiwyg') { $editform->textbox2 = RTE::HtmlToWikitext($editform->textbox2); } return true; }
protected function showFormBeforeText() { global $wgOut; $section = htmlspecialchars($this->section); $wgOut->addHTML(<<<HTML <input type='hidden' value="{$section}" name="wpSection" /> <input type='hidden' value="{$this->starttime}" name="wpStarttime" /> <input type='hidden' value="{$this->edittime}" name="wpEdittime" /> <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" /> HTML ); if (!$this->checkUnicodeCompliantBrowser()) { $wgOut->addHTML(Html::hidden('safemode', '1')); } $isSourceEditor = !(class_exists("RTE") && RTE::isWysiwygModeEnabled()) ? 1 : 0; $wgOut->addHTML(Html::hidden('isMediaWikiEditor', $isSourceEditor)); }
/** * 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; }
/** * Return wikitext from given POST request */ protected function getWikitextFromField($fieldName) { $wikitext = $this->request->getText($fieldName); // perform reverse parsing when needed (i.e. convert HTML from RTE to wikitext) if (class_exists('RTE') && ($this->request->getVal('RTEMode') == 'wysiwyg' || $this->request->getVal('mode') == 'wysiwyg')) { $wikitext = RTE::HtmlToWikitext($wikitext); $this->request->setVal('RTEMode', null); } return $wikitext; }
/** * Get list of frequently used templates */ public static function getHotTemplates() { return RTE::getHotTemplates(); }
/** * 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; }