getConfigFactory() ->makeConfig( 'visualeditor' ); $class = $name === 'visualeditor' ? 'ApiVisualEditor' : 'ApiVisualEditorEdit'; return new $class( $main, $name, $config ); } /** * Adds VisualEditor JS to the output. * * This is attached to the MediaWiki 'BeforePageDisplay' hook. * * @param OutputPage $output The page view. * @param Skin $skin The skin that's going to build the UI. */ public static function onBeforePageDisplay( OutputPage $output, Skin $skin ) { if ( !( ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) && MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' ) ->shouldDisplayMobileView() ) ) { $output->addModules( [ 'ext.visualEditor.desktopArticleTarget.init', 'ext.visualEditor.targetLoader' ] ); $output->addModuleStyles( [ 'ext.visualEditor.desktopArticleTarget.noscript' ] ); } // add scroll offset js variable to output $veConfig = MediaWikiServices::getInstance()->getConfigFactory() ->makeConfig( 'visualeditor' ); $skinsToolbarScrollOffset = $veConfig->get( 'VisualEditorSkinToolbarScrollOffset' ); $toolbarScrollOffset = 0; $skinName = $skin->getSkinName(); if ( isset( $skinsToolbarScrollOffset[$skinName] ) ) { $toolbarScrollOffset = $skinsToolbarScrollOffset[$skinName]; } // T220158: Don't add this unless it's non-default // TODO: Move this to packageFiles as it's not relevant to the HTML request. if ( $toolbarScrollOffset !== 0 ) { $output->addJsConfigVars( 'wgVisualEditorToolbarScrollOffset', $toolbarScrollOffset ); } $output->addJsConfigVars( 'wgEditSubmitButtonLabelPublish', $veConfig->get( 'EditSubmitButtonLabelPublish' ) ); } /** * @internal For internal use in extension.json only. * @return array */ public static function getDataForDesktopArticleTargetInitModule() { return [ 'unsupportedEditParams' => self::$unsupportedEditParams, ]; } /** * Handler for the DiffViewHeader hook, to add visual diffs code as configured * * @param DifferenceEngine $diff The difference engine * @param Revision|null $oldRev The old revision * @param Revision|null $newRev The new revision * @return void */ public static function onDiffViewHeader( DifferenceEngine $diff, Revision $oldRev = null, Revision $newRev = null ) { $veConfig = MediaWikiServices::getInstance()->getConfigFactory() ->makeConfig( 'visualeditor' ); $output = RequestContext::getMain()->getOutput(); $user = RequestContext::getMain()->getUser(); if ( !( // Enabled globally on wiki $veConfig->get( 'VisualEditorEnableDiffPage' ) || // Enabled as user beta feature $user->getOption( 'visualeditor-visualdiffpage' ) || // Enabled by query param $output->getRequest()->getVal( 'visualdiff' ) !== null ) ) { return; } if ( !ApiVisualEditor::isAllowedContentType( $veConfig, $diff->getTitle()->getContentModel() ) ) { return; } $output->addModuleStyles( [ 'ext.visualEditor.diffPage.init.styles', 'oojs-ui.styles.icons-accessibility', 'oojs-ui.styles.icons-editing-advanced' ] ); $output->addModules( 'ext.visualEditor.diffPage.init' ); $output->enableOOUI(); $output->addHtml( '
' . // Will be replaced by a ButtonSelectWidget in JS new OOUI\ButtonGroupWidget( [ 'items' => [ new \OOUI\ButtonWidget( [ 'data' => 'visual', 'icon' => 'eye', 'disabled' => true, 'label' => $output->msg( 'visualeditor-savedialog-review-visual' )->plain() ] ), new \OOUI\ButtonWidget( [ 'data' => 'source', 'icon' => 'wikiText', 'active' => true, 'label' => $output->msg( 'visualeditor-savedialog-review-wikitext' )->plain() ] ) ] ] ) . '
' ); } /** * Detect incompatibile browsers which we can't expect to load VE * * @param WebRequest $req The web request to check the details of * @param Config $config VE config object * @return bool True if the User Agent is blacklisted */ private static function isUABlacklisted( WebRequest $req, $config ) { if ( $req->getVal( 'vewhitelist' ) ) { return false; } $blacklist = $config->get( 'VisualEditorBrowserBlacklist' ); $ua = strtolower( $req->getHeader( 'User-Agent' ) ); foreach ( $blacklist as $uaSubstr => $rules ) { if ( !strpos( $ua, $uaSubstr . '/' ) ) { continue; } if ( !is_array( $rules ) ) { return true; } $matches = []; $ret = preg_match( '/' . $uaSubstr . '\/([0-9\.]*) ?/i', $ua, $matches ); if ( $ret !== 1 ) { continue; } $version = $matches[1]; foreach ( $rules as $rule ) { list( $op, $matchVersion ) = $rule; if ( ( $op === '<' && $version < $matchVersion ) || ( $op === '>' && $version > $matchVersion ) || ( $op === '<=' && $version <= $matchVersion ) || ( $op === '>=' && $version >= $matchVersion ) ) { return true; } } } return false; } /** * @param Title $title * @param User $user * @param WebRequest $req * @return bool */ private static function isSupportedEditPage( Title $title, User $user, WebRequest $req ) { if ( $req->getVal( 'action' ) !== 'edit' || !$title->quickUserCan( 'edit' ) ) { return false; } foreach ( self::$unsupportedEditParams as $param ) { if ( $req->getVal( $param ) !== null ) { return false; } } if ( $req->getVal( 'wteswitched' ) ) { return self::isVisualAvailable( $title, $req, $user ); } switch ( self::getPreferredEditor( $user, $req ) ) { case 'visualeditor': return self::isVisualAvailable( $title, $req, $user ) || self::isWikitextAvailable( $title, $user ); case 'wikitext': return self::isWikitextAvailable( $title, $user ); } return false; } /** * @param User $user * @return bool */ private static function enabledForUser( $user ) { $veConfig = MediaWikiServices::getInstance()->getConfigFactory() ->makeConfig( 'visualeditor' ); return $user->getOption( 'visualeditor-enable' ) && !$user->getOption( 'visualeditor-betatempdisable' ) && !$user->getOption( 'visualeditor-autodisable' ) && !( $veConfig->get( 'VisualEditorDisableForAnons' ) && $user->isAnon() ); } /** * @param Title $title * @param WebRequest $req * @param User $user * @return bool */ private static function isVisualAvailable( $title, $req, $user ) { $veConfig = MediaWikiServices::getInstance()->getConfigFactory() ->makeConfig( 'visualeditor' ); return ( // If forced by the URL parameter, skip the namespace check (T221892) and preference check $req->getVal( 'veaction' ) === 'edit' || ( // Only in enabled namespaces ApiVisualEditor::isAllowedNamespace( $veConfig, $title->getNamespace() ) && // Enabled per user preferences self::enabledForUser( $user ) ) && // Only for pages with a supported content model ApiVisualEditor::isAllowedContentType( $veConfig, $title->getContentModel() ) ); } /** * @param Title $title * @param User $user * @return bool */ private static function isWikitextAvailable( $title, $user ) { return $user->getOption( 'visualeditor-newwikitext' ) && $title->getContentModel() === 'wikitext'; } /** * Decide whether to bother showing the wikitext editor at all. * If not, we expect the VE initialisation JS to activate. * * @param Article $article The article being viewed. * @param User $user The user-specific settings. * @return bool Whether to show the wikitext editor or not. */ public static function onCustomEditor( Article $article, User $user ) { $req = $article->getContext()->getRequest(); $veConfig = MediaWikiServices::getInstance()->getConfigFactory() ->makeConfig( 'visualeditor' ); if ( !self::enabledForUser( $user ) || self::isUABlacklisted( $req, $veConfig ) ) { return true; } $title = $article->getTitle(); if ( $req->getVal( 'venoscript' ) ) { $req->response()->setCookie( 'VEE', 'wikitext', 0, [ 'prefix' => '' ] ); $user->setOption( 'visualeditor-editor', 'wikitext' ); if ( !wfReadOnly() && !$user->isAnon() ) { DeferredUpdates::addCallableUpdate( function () use ( $user ) { $user->saveSettings(); } ); } return true; } if ( self::isSupportedEditPage( $title, $user, $req ) ) { $params = $req->getValues(); $params['venoscript'] = '1'; $url = wfScript() . '?' . wfArrayToCgi( $params ); $escapedUrl = htmlspecialchars( $url ); $out = $article->getContext()->getOutput(); $titleMsg = $title->exists() ? 'editing' : 'creating'; $out->setPageTitle( wfMessage( $titleMsg, $title->getPrefixedText() ) ); $out->addWikiMsg( 'visualeditor-toload', wfExpandUrl( $url ) ); // Redirect if the user has no JS (