getHookContainer() ); if ( !$hookRunner->onVisualEditorBeforeEditor( $output, $skin ) ) { return; } if ( !( ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) && $services->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 = $services->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::UNSUPPORTED_EDIT_PARAMS, ]; } /** * Handler for the DifferenceEngineViewHeader hook, to add visual diffs code as configured * * @param DifferenceEngine $diff The difference engine * @return void */ public static function onDifferenceEngineViewHeader( DifferenceEngine $diff ) { $services = MediaWikiServices::getInstance(); $veConfig = $services->getConfigFactory() ->makeConfig( 'visualeditor' ); $userOptionsLookup = $services->getUserOptionsLookup(); $output = RequestContext::getMain()->getOutput(); $user = RequestContext::getMain()->getUser(); if ( !( // Enabled globally on wiki $veConfig->get( 'VisualEditorEnableDiffPage' ) || // Enabled as user beta feature $userOptionsLookup->getOption( $user, 'visualeditor-visualdiffpage' ) || // Enabled by query param (deprecated) $output->getRequest()->getVal( 'visualdiff' ) !== null || // Enabled by query param $output->getRequest()->getVal( 'diffmode' ) === 'visual' ) ) { 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 ButtonGroupWidget( [ 'items' => [ new ButtonWidget( [ 'data' => 'visual', 'icon' => 'eye', 'disabled' => true, 'label' => $output->msg( 'visualeditor-savedialog-review-visual' )->plain() ] ), new ButtonWidget( [ 'data' => 'source', 'icon' => 'wikiText', 'active' => true, 'label' => $output->msg( 'visualeditor-savedialog-review-wikitext' )->plain() ] ) ] ] ) . '
' ); } /** * Detect incompatible 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 The User Agent is unsupported */ private static function isUAUnsupported( WebRequest $req, $config ) { if ( $req->getVal( 'vesupported' ) ) { return false; } $unsupportedList = $config->get( 'VisualEditorBrowserUnsupportedList' ); $ua = strtolower( $req->getHeader( 'User-Agent' ) ); foreach ( $unsupportedList 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' || !MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan( 'edit', $user, $title ) ) { return false; } foreach ( self::UNSUPPORTED_EDIT_PARAMS as $param ) { if ( $req->getVal( $param ) !== null ) { return false; } } if ( $req->getVal( 'wteswitched' ) ) { return self::isVisualAvailable( $title, $req, $user ); } switch ( self::getEditPageEditor( $user, $req ) ) { case 'visualeditor': return self::isVisualAvailable( $title, $req, $user ) || self::isWikitextAvailable( $title, $user ); case 'wikitext': default: return self::isWikitextAvailable( $title, $user ); } } /** * @param User $user * @return bool */ private static function enabledForUser( $user ) { $services = MediaWikiServices::getInstance(); $veConfig = $services->getConfigFactory() ->makeConfig( 'visualeditor' ); $userOptionsLookup = $services->getUserOptionsLookup(); $isBeta = $veConfig->get( 'VisualEditorEnableBetaFeature' ); return ( $isBeta ? $userOptionsLookup->getOption( $user, 'visualeditor-enable' ) : !$userOptionsLookup->getOption( $user, 'visualeditor-betatempdisable' ) ) && !$userOptionsLookup->getOption( $user, 'visualeditor-autodisable' ); } /** * @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 ) { $services = MediaWikiServices::getInstance(); $userOptionsLookup = $services->getUserOptionsLookup(); return $userOptionsLookup->getOption( $user, 'visualeditor-newwikitext' ) && $title->getContentModel() === 'wikitext'; } /** * @param UserIdentity $user * @param string $key * @param string $value */ private static function deferredSetUserOption( UserIdentity $user, string $key, string $value ) { DeferredUpdates::addCallableUpdate( static function () use ( $user, $key, $value ) { $services = MediaWikiServices::getInstance(); if ( $services->getReadOnlyMode()->isReadOnly() ) { return; } $userOptionsManager = $services->getUserOptionsManager(); $userOptionsManager->setOption( $user, $key, $value ); $userOptionsManager->saveOptions( $user ); } ); } /** * 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(); $services = MediaWikiServices::getInstance(); $veConfig = $services->getConfigFactory()->makeConfig( 'visualeditor' ); if ( !self::enabledForUser( $user ) || self::isUAUnsupported( $req, $veConfig ) ) { return true; } $title = $article->getTitle(); if ( $req->getVal( 'venoscript' ) ) { $req->response()->setCookie( 'VEE', 'wikitext', 0, [ 'prefix' => '' ] ); if ( $user->isRegistered() ) { self::deferredSetUserOption( $user, 'visualeditor-editor', 'wikitext' ); } 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 (