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 * @param Skin $skin * @return boolean */ public static function onBeforePageDisplay( OutputPage &$output, Skin &$skin ) { $output->addModules( [ 'ext.visualEditor.desktopArticleTarget.init', 'ext.visualEditor.targetLoader' ] ); $output->addModuleStyles( [ 'ext.visualEditor.desktopArticleTarget.noscript' ] ); // add scroll offset js variable to output $veConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); $skinsToolbarScrollOffset = $veConfig->get( 'VisualEditorSkinToolbarScrollOffset' ); $toolbarScrollOffset = 0; $skinName = $skin->getSkinName(); if ( isset( $skinsToolbarScrollOffset[$skinName] ) ) { $toolbarScrollOffset = $skinsToolbarScrollOffset[$skinName]; } $output->addJsConfigVars( 'wgVisualEditorToolbarScrollOffset', $toolbarScrollOffset ); $output->addJsConfigVars( 'wgEditSubmitButtonLabelPublish', $veConfig->get( 'EditSubmitButtonLabelPublish' ) ); return true; } /** * 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 * @param boolean */ 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; } /** * Decide whether to bother showing the wikitext editor at all. * If not, we expect the VE initialisation JS to activate. * @param $article Article * @param $user User * @return bool Whether to show the wikitext editor or not. */ public static function onCustomEditor( Article $article, User $user ) { $req = $article->getContext()->getRequest(); $veConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); if ( !$user->getOption( 'visualeditor-enable' ) || $user->getOption( 'visualeditor-betatempdisable' ) || $user->getOption( 'visualeditor-autodisable' ) || $user->getOption( 'visualeditor-tabs' ) === 'prefer-wt' || $user->getOption( 'visualeditor-tabs' ) === 'multi-tab' || ( $veConfig->get( 'VisualEditorDisableForAnons' ) && $user->isAnon() ) || self::isUABlacklisted( $req, $veConfig ) ) { return true; } $title = $article->getTitle(); $params = $req->getValues(); if ( isset( $params['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; } $ret = $req->getVal( 'action' ) !== 'edit' || !$veConfig->get( 'VisualEditorUseSingleEditTab' ) || self::getUserEditor( $user, $req ) === 'wikitext' || !$title->quickUserCan( 'edit' ) || !ApiVisualEditor::isAllowedNamespace( $veConfig, $title->getNamespace() ) || !ApiVisualEditor::isAllowedContentType( $veConfig, $title->getContentModel() ) || // Known parameters that VE does not handle // TODO: Other params too? See identical list in ve.init.mw.DesktopArticleTarget.init.js isset( $params['undo'] ) || isset( $params['undoafter'] ) || isset( $params['editintro'] ) || isset( $params['preload'] ) || isset( $params['preloadtitle'] ) || isset( $params['preloadparams'] ) || isset( $params['veswitched'] ); // Known-good parameters: edit, veaction, section, vesection $params['venoscript'] = '1'; $url = wfScript() . '?' . wfArrayToCgi( $params ); $out = $article->getContext()->getOutput(); if ( !$ret ) { $escapedUrl = htmlspecialchars( $url ); $out->addHeadItem( 've-noscript-fallback', "" ); $titleMsg = $title->exists() ? 'editing' : 'creating'; $out->setPageTitle( wfMessage( $titleMsg, $title->getPrefixedText() ) ); $out->addWikiMsg( 'visualeditor-toload', wfExpandUrl( $url ) ); } $out->addScript( Html::inlineScript( "(window.NORLQ=window.NORLQ||[]).push(" . "function(){" . "location.href=\"$url\";" . "}" . ");" ) ); return $ret; } private static function getUserEditor( User $user, WebRequest $req ) { if ( $user->isAnon() ) { return $req->getCookie( 'VEE', '', User::getDefaultOption( 'visualeditor-editor' ) ); } else { return $user->getOption( 'visualeditor-editor' ); } } /** * Changes the Edit tab and adds the VisualEditor tab. * * This is attached to the MediaWiki 'SkinTemplateNavigation' hook. * * @param SkinTemplate $skin * @param array $links Navigation links * @return boolean */ public static function onSkinTemplateNavigation( SkinTemplate &$skin, array &$links ) { $config = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); // Exit if there's no edit link for whatever reason (e.g. protected page) if ( !isset( $links['views']['edit'] ) ) { return true; } $user = $skin->getUser(); if ( $config->get( 'VisualEditorUseSingleEditTab' ) && $user->getOption( 'visualeditor-tabs' ) === 'prefer-wt' ) { return true; } $dbr = wfGetDB( DB_REPLICA ); if ( $config->get( 'VisualEditorUseSingleEditTab' ) && !$user->isAnon() && !$user->getOption( 'visualeditor-autodisable' ) && !$user->getOption( 'visualeditor-betatempdisable' ) && !$user->getOption( 'visualeditor-hidetabdialog' ) && $user->getOption( 'visualeditor-tabs' ) === 'remember-last' && $dbr->select( 'revision', '1', [ 'rev_user' => $user->getId(), 'rev_timestamp < ' . $dbr->addQuotes( $config->get( 'VisualEditorSingleEditTabSwitchTime' ) ) ], __METHOD__, [ 'LIMIT' => 1 ] )->numRows() === 1 ) { $links['views']['edit']['class'] .= ' visualeditor-showtabdialog'; } // Exit if the user doesn't have VE enabled if ( !$user->getOption( 'visualeditor-enable' ) || $user->getOption( 'visualeditor-betatempdisable' ) || $user->getOption( 'visualeditor-autodisable' ) || ( $config->get( 'VisualEditorDisableForAnons' ) && $user->isAnon() ) ) { return true; } $title = $skin->getRelevantTitle(); $namespaceEnabled = ApiVisualEditor::isAllowedNamespace( $config, $title->getNamespace() ); $pageContentModel = $title->getContentModel(); $contentModelEnabled = ApiVisualEditor::isAllowedContentType( $config, $pageContentModel ); // Don't exit if this page isn't VE-enabled, since we should still // change "Edit" to "Edit source". $isAvailable = $namespaceEnabled && $contentModelEnabled; // HACK: Exit if we're in the Education Program namespace (even though it's content) if ( defined( 'EP_NS' ) && $title->inNamespace( EP_NS ) ) { return true; } $tabMessages = $config->get( 'VisualEditorTabMessages' ); // Rebuild the $links['views'] array and inject the VisualEditor tab before or after // the edit tab as appropriate. We have to rebuild the array because PHP doesn't allow // us to splice into the middle of an associative array. $newViews = []; foreach ( $links['views'] as $action => $data ) { if ( $action === 'edit' ) { // Build the VisualEditor tab $existing = $title->exists() || ( $title->inNamespace( NS_MEDIAWIKI ) && $title->getDefaultMessageText() !== false ); $action = $existing ? 'edit' : 'create'; $veParams = $skin->editUrlOptions(); unset( $veParams['action'] ); // Remove action=edit $veParams['veaction'] = 'edit'; // Set veaction=edit $veTabMessage = $tabMessages[$action]; $veTabText = $veTabMessage === null ? $data['text'] : $skin->msg( $veTabMessage )->text(); $veTab = [ 'href' => $title->getLocalURL( $veParams ), 'text' => $veTabText, 'primary' => true, 'class' => '', ]; // Alter the edit tab $editTab = $data; if ( $title->inNamespace( NS_FILE ) && WikiPage::factory( $title ) instanceof WikiFilePage && !WikiPage::factory( $title )->isLocal() ) { $editTabMessage = $tabMessages[$action . 'localdescriptionsource']; } else { $editTabMessage = $tabMessages[$action . 'source']; } if ( $editTabMessage !== null ) { $editTab['text'] = $skin->msg( $editTabMessage )->text(); } $editor = self::getUserEditor( $user, $skin->getRequest() ); if ( $isAvailable && $config->get( 'VisualEditorUseSingleEditTab' ) && ( $user->getOption( 'visualeditor-tabs' ) === 'prefer-ve' || ( $user->getOption( 'visualeditor-tabs' ) === 'remember-last' && $editor === 'visualeditor' ) ) ) { $editTab['text'] = $veTabText; $newViews['edit'] = $editTab; } elseif ( $isAvailable && ( !$config->get( 'VisualEditorUseSingleEditTab' ) || $user->getOption( 'visualeditor-tabs' ) === 'multi-tab' ) ) { // Inject the VE tab before or after the edit tab if ( $config->get( 'VisualEditorTabPosition' ) === 'before' ) { $editTab['class'] .= ' collapsible'; $newViews['ve-edit'] = $veTab; $newViews['edit'] = $editTab; } else { $veTab['class'] .= ' collapsible'; $newViews['edit'] = $editTab; $newViews['ve-edit'] = $veTab; } } elseif ( !$config->get( 'VisualEditorUseSingleEditTab' ) || !$isAvailable || $user->getOption( 'visualeditor-tabs' ) === 'multi-tab' || ( $user->getOption( 'visualeditor-tabs' ) === 'remember-last' && $editor === 'wikitext' ) ) { // Don't add ve-edit, but do update the edit tab (e.g. "Edit source"). $newViews['edit'] = $editTab; } else { // This should not happen. } } else { // Just pass through $newViews[$action] = $data; } } $links['views'] = $newViews; return true; } /** * Called when the normal wikitext editor is shown. * Inserts a 'veswitched' hidden field if requested by the client * * @param $editPage EditPage * @param $output OutputPage * @return boolean true */ public static function onEditPageShowEditFormFields( EditPage $editPage, OutputPage $output ) { $request = $output->getRequest(); if ( $request->getBool( 'veswitched' ) ) { $output->addHTML( Xml::input( 'veswitched', false, '1', [ 'type' => 'hidden' ] ) ); } return true; } /** * Called when an edit is saved * Adds 'visualeditor-switched' tag to the edit if requested * * @param RecentChange $rc * @return boolean true */ public static function onRecentChange_save( RecentChange $rc ) { $request = RequestContext::getMain()->getRequest(); if ( $request->getBool( 'veswitched' ) && $rc->mAttribs['rc_this_oldid'] ) { ChangeTags::addTags( 'visualeditor-switched', $rc->mAttribs['rc_id'], $rc->mAttribs['rc_this_oldid'] ); } return true; } /** * Changes the section edit links to add a VE edit link. * * This is attached to the MediaWiki 'SkinEditSectionLinks' hook. * * @param $skin Skin * @param $title Title * @param $section string * @param $tooltip string * @param $result array * @param $lang Language * @return bool true */ public static function onSkinEditSectionLinks( Skin $skin, Title $title, $section, $tooltip, &$result, $lang ) { $config = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); // Exit if we're in parserTests if ( isset( $GLOBALS[ 'wgVisualEditorInParserTests' ] ) ) { return true; } // Exit if the user doesn't have VE enabled if ( !$skin->getUser()->getOption( 'visualeditor-enable' ) || $skin->getUser()->getOption( 'visualeditor-betatempdisable' ) || $skin->getUser()->getOption( 'visualeditor-autodisable' ) || ( $config->get( 'VisualEditorDisableForAnons' ) && $skin->getUser()->isAnon() ) ) { return true; } // Exit if we're on a foreign file description page if ( $title->inNamespace( NS_FILE ) && WikiPage::factory( $title ) instanceof WikiFilePage && !WikiPage::factory( $title )->isLocal() ) { return true; } $editor = self::getUserEditor( $skin->getUser(), RequestContext::getMain()->getRequest() ); if ( !$config->get( 'VisualEditorUseSingleEditTab' ) || $skin->getUser()->getOption( 'visualeditor-tabs' ) === 'multi-tab' || ( $skin->getUser()->getOption( 'visualeditor-tabs' ) === 'remember-last' && $editor === 'wikitext' ) ) { // Don't add ve-edit, but do update the edit tab (e.g. "Edit source"). $tabMessages = $config->get( 'VisualEditorTabMessages' ); $sourceEditSection = $tabMessages['editsectionsource'] !== null ? $tabMessages['editsectionsource'] : 'editsection'; $result['editsection']['text'] = $skin->msg( $sourceEditSection )->inLanguage( $lang )->text(); } // Exit if we're using the single edit tab. if ( $config->get( 'VisualEditorUseSingleEditTab' ) && $skin->getUser()->getOption( 'visualeditor-tabs' ) !== 'multi-tab' ) { return true; } // add VE edit section in VE available namespaces if ( ApiVisualEditor::isAllowedNamespace( $config, $title->getNamespace() ) ) { $veEditSection = $tabMessages['editsection'] !== null ? $tabMessages['editsection'] : 'editsection'; $veLink = [ 'text' => $skin->msg( $veEditSection )->inLanguage( $lang )->text(), 'targetTitle' => $title, 'attribs' => $result['editsection']['attribs'] + [ 'class' => 'mw-editsection-visualeditor' ], 'query' => [ 'veaction' => 'edit', 'vesection' => $section ], 'options' => [ 'noclasses', 'known' ] ]; $result['veeditsection'] = $veLink; if ( $config->get( 'VisualEditorTabPosition' ) === 'before' ) { krsort( $result ); // TODO: This will probably cause weird ordering if any other extensions added something // already. // ... wfArrayInsertBefore? } } return true; } /** * Convert a namespace index to the local text for display to the user. * * @param $nsIndex int * @return string */ private static function convertNs( $nsIndex ) { global $wgLang; if ( $nsIndex ) { return $wgLang->convertNamespace( $nsIndex ); } else { return wfMessage( 'blanknamespace' )->text(); } } public static function onGetPreferences( User $user, array &$preferences ) { global $wgLang; $config = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); if ( !class_exists( 'BetaFeatures' ) ) { $namespaces = ApiVisualEditor::getAvailableNamespaceIds( $config ); $enablePreference = [ 'type' => 'toggle', 'label-message' => [ 'visualeditor-preference-enable', $wgLang->commaList( array_map( [ 'self', 'convertNs' ], $namespaces ) ), count( $namespaces ) ], 'section' => 'editing/editor' ]; if ( $user->getOption( 'visualeditor-autodisable' ) ) { $enablePreference['default'] = false; } $preferences['visualeditor-enable'] = $enablePreference; } $preferences['visualeditor-betatempdisable'] = [ 'type' => 'toggle', 'label-message' => 'visualeditor-preference-betatempdisable', 'section' => 'editing/editor', 'default' => $user->getOption( 'visualeditor-betatempdisable' ) || $user->getOption( 'visualeditor-autodisable' ) ]; if ( $config->get( 'VisualEditorUseSingleEditTab' ) && !$user->getOption( 'visualeditor-autodisable' ) && !$user->getOption( 'visualeditor-betatempdisable' ) ) { $preferences['visualeditor-tabs'] = [ 'type' => 'select', 'label-message' => 'visualeditor-preference-tabs', 'section' => 'editing/editor', 'options' => [ wfMessage( 'visualeditor-preference-tabs-remember-last' )->escaped() => 'remember-last', wfMessage( 'visualeditor-preference-tabs-prefer-ve' )->escaped() => 'prefer-ve', wfMessage( 'visualeditor-preference-tabs-prefer-wt' )->escaped() => 'prefer-wt', wfMessage( 'visualeditor-preference-tabs-multi-tab' )->escaped() => 'multi-tab' ] ]; } $api = [ 'type' => 'api' ]; $preferences['visualeditor-autodisable'] = $api; $preferences['visualeditor-editor'] = $api; $preferences['visualeditor-hidebetawelcome'] = $api; $preferences['visualeditor-hidetabdialog'] = $api; $preferences['visualeditor-hidesourceswitchpopup'] = $api; $preferences['visualeditor-hidevisualswitchpopup'] = $api; $preferences['visualeditor-hideusered'] = $api; $preferences['visualeditor-findAndReplace-findText'] = $api; $preferences['visualeditor-findAndReplace-replaceText'] = $api; $preferences['visualeditor-findAndReplace-regex'] = $api; $preferences['visualeditor-findAndReplace-matchCase'] = $api; $preferences['visualeditor-findAndReplace-word'] = $api; return true; } public static function onGetBetaPreferences( User $user, array &$preferences ) { $coreConfig = RequestContext::getMain()->getConfig(); $iconpath = $coreConfig->get( 'ExtensionAssetsPath' ) . "/VisualEditor"; $veConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); $preferences['visualeditor-enable'] = [ 'version' => '1.0', 'label-message' => 'visualeditor-preference-core-label', 'desc-message' => 'visualeditor-preference-core-description', 'screenshot' => [ 'ltr' => "$iconpath/betafeatures-icon-VisualEditor-ltr.svg", 'rtl' => "$iconpath/betafeatures-icon-VisualEditor-rtl.svg", ], 'info-message' => 'visualeditor-preference-core-info-link', 'discussion-message' => 'visualeditor-preference-core-discussion-link', 'requirements' => [ 'javascript' => true, 'blacklist' => $veConfig->get( 'VisualEditorBrowserBlacklist' ), 'skins' => $veConfig->get( 'VisualEditorSupportedSkins' ), ] ]; if ( $veConfig->get( 'VisualEditorEnableWikitext' ) ) { $preferences['visualeditor-newwikitext'] = [ 'version' => '1.0', 'label-message' => 'visualeditor-preference-newwikitexteditor-label', 'desc-message' => 'visualeditor-preference-newwikitexteditor-description', 'screenshot' => [ 'ltr' => "$iconpath/betafeatures-icon-WikitextEditor-ltr.svg", 'rtl' => "$iconpath/betafeatures-icon-WikitextEditor-rtl.svg", ], 'info-message' => 'visualeditor-preference-newwikitexteditor-info-link', 'discussion-message' => 'visualeditor-preference-newwikitexteditor-discussion-link', 'requirements' => [ 'javascript' => true, 'blacklist' => $veConfig->get( 'VisualEditorBrowserBlacklist' ), 'skins' => $veConfig->get( 'VisualEditorSupportedSkins' ), ], 'exempt-from-auto-enrollment' => true ]; } } /** * Implements the PreferencesFormPreSave hook, to remove the 'autodisable' flag * when the user it was set on explicitly enables VE. * @param array $data User-submitted data * @param PreferencesForm $form A ContextSource * @param User $user User with new preferences already set * @param bool &$result Success or failure */ public static function onPreferencesFormPreSave( $data, $form, $user, &$result ) { $veConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); // On a wiki where enable is hidden and set to 1, if user sets betatempdisable=0 // then set autodisable=0 // On a wiki where betatempdisable is hidden and set to 0, if user sets enable=1 // then set autodisable=0 if ( $user->getOption( 'visualeditor-autodisable' ) && $user->getOption( 'visualeditor-enable' ) && !$user->getOption( 'visualeditor-betatempdisable' ) ) { $user->setOption( 'visualeditor-autodisable', false ); } elseif ( // On a wiki where betatempdisable is hidden and set to 0, if user sets enable=0, // then set autodisable=1 $veConfig->get( 'VisualEditorTransitionDefault' ) && !$user->getOption( 'visualeditor-betatempdisable' ) && !$user->getOption( 'visualeditor-enable' ) && !$user->getOption( 'visualeditor-autodisable' ) ) { $user->setOption( 'visualeditor-autodisable', true ); } } /** * Implements the ListDefinedTags and ChangeTagsListActive hooks, to populate * core Special:Tags with the change tags in use by VisualEditor. * * @param array $tags * @return bool true */ public static function onListDefinedTags( &$tags ) { $tags[] = 'visualeditor'; $tags[] = 'visualeditor-needcheck'; // No longer in active use $tags[] = 'visualeditor-switched'; return true; } /** * Adds extra variables to the page config. */ public static function onMakeGlobalVariablesScript( array &$vars, OutputPage $out ) { $pageLanguage = $out->getTitle()->getPageLanguage(); $vars['wgVisualEditor'] = [ 'pageLanguageCode' => $pageLanguage->getHtmlCode(), 'pageLanguageDir' => $pageLanguage->getDir(), 'usePageImages' => defined( 'PAGE_IMAGES_INSTALLED' ), 'usePageDescriptions' => defined( 'WBC_VERSION' ), ]; return true; } /** * Adds extra variables to the global config */ public static function onResourceLoaderGetConfigVars( array &$vars ) { $coreConfig = RequestContext::getMain()->getConfig(); $defaultUserOptions = $coreConfig->get( 'DefaultUserOptions' ); $thumbLimits = $coreConfig->get( 'ThumbLimits' ); $veConfig = ConfigFactory::getDefaultInstance()->makeConfig( 'visualeditor' ); $availableNamespaces = ApiVisualEditor::getAvailableNamespaceIds( $veConfig ); $availableContentModels = array_filter( array_merge( ExtensionRegistry::getInstance()->getAttribute( 'VisualEditorAvailableContentModels' ), $veConfig->get( 'VisualEditorAvailableContentModels' ) ) ); $vars['wgVisualEditorConfig'] = [ 'disableForAnons' => $veConfig->get( 'VisualEditorDisableForAnons' ), 'preferenceModules' => $veConfig->get( 'VisualEditorPreferenceModules' ), 'namespaces' => $availableNamespaces, 'contentModels' => $availableContentModels, 'signatureNamespaces' => array_values( array_filter( $availableNamespaces, 'MWNamespace::wantSignatures' ) ), 'pluginModules' => array_merge( ExtensionRegistry::getInstance()->getAttribute( 'VisualEditorPluginModules' ), $veConfig->get( 'VisualEditorPluginModules' ) // @todo deprecate the global setting ), 'defaultUserOptions' => [ 'defaultthumbsize' => $thumbLimits[ $defaultUserOptions['thumbsize'] ] ], 'galleryOptions' => $coreConfig->get( 'GalleryOptions' ), 'blacklist' => $veConfig->get( 'VisualEditorBrowserBlacklist' ), 'skins' => $veConfig->get( 'VisualEditorSupportedSkins' ), 'tabPosition' => $veConfig->get( 'VisualEditorTabPosition' ), 'tabMessages' => $veConfig->get( 'VisualEditorTabMessages' ), 'singleEditTab' => $veConfig->get( 'VisualEditorUseSingleEditTab' ), 'showBetaWelcome' => $veConfig->get( 'VisualEditorShowBetaWelcome' ), 'enableTocWidget' => $veConfig->get( 'VisualEditorEnableTocWidget' ), 'enableWikitext' => $veConfig->get( 'VisualEditorEnableWikitext' ), 'svgMaxSize' => $coreConfig->get( 'SVGMaxSize' ), 'namespacesWithSubpages' => $coreConfig->get( 'NamespacesWithSubpages' ), 'specialBooksources' => urldecode( SpecialPage::getTitleFor( 'Booksources' )->getPrefixedURL() ), 'restbaseUrl' => $coreConfig->get( 'VisualEditorRestbaseURL' ), 'fullRestbaseUrl' => $coreConfig->get( 'VisualEditorFullRestbaseURL' ), 'feedbackApiUrl' => $veConfig->get( 'VisualEditorFeedbackAPIURL' ), 'feedbackTitle' => $veConfig->get( 'VisualEditorFeedbackTitle' ), ]; return true; } /** * Conditionally register the jquery.uls.data and jquery.i18n modules, in case they've already * been registered by the UniversalLanguageSelector extension or the TemplateData extension. * * @param ResourceLoader $resourceLoader * @return boolean true */ public static function onResourceLoaderRegisterModules( ResourceLoader &$resourceLoader ) { $resourceModules = $resourceLoader->getConfig()->get( 'ResourceModules' ); $veResourceTemplate = [ 'localBasePath' => __DIR__, 'remoteExtPath' => 'VisualEditor', ]; // Only pull in VisualEditor core's local version of jquery.uls.data if it hasn't been // installed locally already (presumably, by the UniversalLanguageSelector extension). if ( !isset( $resourceModules[ 'jquery.uls.data' ] ) && !$resourceLoader->isModuleRegistered( 'jquery.uls.data' ) ) { $resourceLoader->register( [ 'jquery.uls.data' => $veResourceTemplate + [ 'scripts' => [ 'lib/ve/lib/jquery.uls/src/jquery.uls.data.js', 'lib/ve/lib/jquery.uls/src/jquery.uls.data.utils.js', ], 'targets' => [ 'desktop', 'mobile' ], ] ] ); } $extensionMessages = []; if ( class_exists( 'ConfirmEditHooks' ) ) { $extensionMessages[] = 'captcha-edit'; $extensionMessages[] = 'captcha-label'; if ( class_exists( 'QuestyCaptcha' ) ) { $extensionMessages[] = 'questycaptcha-edit'; } if ( class_exists( 'FancyCaptcha' ) ) { $extensionMessages[] = 'fancycaptcha-edit'; $extensionMessages[] = 'fancycaptcha-reload-text'; } } $resourceLoader->register( [ 'ext.visualEditor.mwextensionmessages' => $veResourceTemplate + [ 'messages' => $extensionMessages, 'targets' => [ 'desktop', 'mobile' ], ] ] ); return true; } public static function onResourceLoaderTestModules( array &$testModules, ResourceLoader &$resourceLoader ) { $testModules['qunit']['ext.visualEditor.test'] = [ 'styles' => [ // jsdifflib 'lib/ve/lib/jsdifflib/diffview.css', ], 'scripts' => [ // MW config preload 'modules/ve-mw/tests/mw-preload.js', // jsdifflib 'lib/ve/lib/jsdifflib/diffview.js', 'lib/ve/lib/jsdifflib/difflib.js', // QUnit plugin 'lib/ve/tests/ve.qunit.js', // VisualEditor Tests 'lib/ve/tests/ve.test.utils.js', 'modules/ve-mw/tests/ve.test.utils.js', 'lib/ve/tests/ve.test.js', 'lib/ve/tests/ve.Document.test.js', 'lib/ve/tests/ve.Node.test.js', 'lib/ve/tests/ve.BranchNode.test.js', 'lib/ve/tests/ve.LeafNode.test.js', // VisualEditor DataModel Tests 'lib/ve/tests/dm/ve.dm.example.js', 'lib/ve/tests/dm/ve.dm.AnnotationSet.test.js', 'lib/ve/tests/dm/ve.dm.NodeFactory.test.js', 'lib/ve/tests/dm/ve.dm.Node.test.js', 'lib/ve/tests/dm/ve.dm.Converter.test.js', 'lib/ve/tests/dm/ve.dm.BranchNode.test.js', 'lib/ve/tests/dm/ve.dm.LeafNode.test.js', 'lib/ve/tests/dm/ve.dm.LinearData.test.js', 'lib/ve/tests/dm/nodes/ve.dm.TextNode.test.js', 'modules/ve-mw/tests/dm/nodes/ve.dm.MWTransclusionNode.test.js', 'lib/ve/tests/dm/ve.dm.Document.test.js', 'modules/ve-mw/tests/dm/ve.dm.Document.test.js', 'lib/ve/tests/dm/ve.dm.DocumentSynchronizer.test.js', 'lib/ve/tests/dm/ve.dm.IndexValueStore.test.js', 'lib/ve/tests/dm/ve.dm.InternalList.test.js', 'lib/ve/tests/dm/ve.dm.Transaction.test.js', 'lib/ve/tests/dm/ve.dm.TransactionProcessor.test.js', 'lib/ve/tests/dm/ve.dm.APIResultsQueue.test.js', 'lib/ve/tests/dm/ve.dm.Surface.test.js', 'lib/ve/tests/dm/ve.dm.SurfaceFragment.test.js', 'modules/ve-mw/tests/dm/ve.dm.SurfaceFragment.test.js', 'lib/ve/tests/dm/ve.dm.ModelRegistry.test.js', 'lib/ve/tests/dm/ve.dm.MetaList.test.js', 'lib/ve/tests/dm/lineardata/ve.dm.FlatLinearData.test.js', 'lib/ve/tests/dm/lineardata/ve.dm.ElementLinearData.test.js', 'lib/ve/tests/dm/lineardata/ve.dm.MetaLinearData.test.js', 'modules/ve-mw/tests/dm/ve.dm.mwExample.js', 'modules/ve-mw/tests/dm/ve.dm.Converter.test.js', 'modules/ve-mw/tests/dm/ve.dm.MWImageModel.test.js', 'modules/ve-mw/tests/dm/ve.dm.MWInternalLinkAnnotation.test.js', // VisualEditor ContentEditable Tests 'lib/ve/tests/ce/ve.ce.test.js', 'lib/ve/tests/ce/ve.ce.Document.test.js', 'lib/ve/tests/ce/ve.ce.Surface.test.js', 'modules/ve-mw/tests/ce/ve.ce.Surface.test.js', 'lib/ve/tests/ce/ve.ce.NodeFactory.test.js', 'lib/ve/tests/ce/ve.ce.Node.test.js', 'lib/ve/tests/ce/ve.ce.BranchNode.test.js', 'lib/ve/tests/ce/ve.ce.ContentBranchNode.test.js', 'modules/ve-mw/tests/ce/ve.ce.ContentBranchNode.test.js', 'lib/ve/tests/ce/ve.ce.LeafNode.test.js', 'lib/ve/tests/ce/nodes/ve.ce.TextNode.test.js', // VisualEditor Actions Tests 'lib/ve/tests/ui/actions/ve.ui.AnnotationAction.test.js', 'lib/ve/tests/ui/actions/ve.ui.FormatAction.test.js', 'modules/ve-mw/tests/ui/actions/ve.ui.FormatAction.test.js', 'lib/ve/tests/ui/actions/ve.ui.IndentationAction.test.js', 'lib/ve/tests/ui/actions/ve.ui.LinkAction.test.js', 'modules/ve-mw/tests/ui/actions/ve.ui.MWLinkAction.test.js', 'lib/ve/tests/ui/actions/ve.ui.ListAction.test.js', // VisualEditor DataTransferHandler tests 'lib/ve/tests/ui/datatransferhandlers/ve.ui.DSVFileTransferHandler.test.js', 'lib/ve/tests/ui/datatransferhandlers/ve.ui.UrlStringTransferHandler.test.js', 'modules/ve-mw/tests/ui/datatransferhandlers/ve.ui.MWWikitextStringTransferHandler.test.js', 'modules/ve-mw/tests/ui/datatransferhandlers/ve.ui.UrlStringTransferHandler.test.js', // VisualEditor initialization Tests 'lib/ve/tests/init/ve.init.Platform.test.js', 'modules/ve-mw/tests/init/targets/ve.init.mw.DesktopArticleTarget.test.js', // IME tests 'lib/ve/tests/ce/ve.ce.TestRunner.js', 'lib/ve/tests/ce/ve.ce.imetests.test.js', 'lib/ve/tests/ce/imetests/backspace-chromium-ubuntu-none.js', 'lib/ve/tests/ce/imetests/backspace-firefox-ubuntu-none.js', 'lib/ve/tests/ce/imetests/backspace-ie9-win7-none.js', 'lib/ve/tests/ce/imetests/home-firefox-win7-none.js', 'lib/ve/tests/ce/imetests/input-chrome-mac-native-japanese-hiragana.js', 'lib/ve/tests/ce/imetests/input-chrome-mac-native-japanese-katakana.js', 'lib/ve/tests/ce/imetests/input-chrome-win7-chinese-traditional-handwriting.js', 'lib/ve/tests/ce/imetests/input-chrome-win7-greek.js', 'lib/ve/tests/ce/imetests/input-chrome-win7-polish.js', 'lib/ve/tests/ce/imetests/input-chrome-win7-welsh.js', 'lib/ve/tests/ce/imetests/input-chromium-ubuntu-ibus-chinese-cantonese.js', 'lib/ve/tests/ce/imetests/input-chromium-ubuntu-ibus-japanese-anthy--hiraganaonly.js', 'lib/ve/tests/ce/imetests/input-chromium-ubuntu-ibus-japanese-mozc.js', 'lib/ve/tests/ce/imetests/input-chromium-ubuntu-ibus-korean-korean.js', 'lib/ve/tests/ce/imetests/input-chromium-ubuntu-ibus-malayalam-swanalekha.js', 'lib/ve/tests/ce/imetests/input-firefox-mac-native-japanese-hiragana.js', 'lib/ve/tests/ce/imetests/input-firefox-mac-native-japanese-katakana.js', 'lib/ve/tests/ce/imetests/input-firefox-ubuntu-ibus-chinese-cantonese.js', 'lib/ve/tests/ce/imetests/input-firefox-ubuntu-ibus-japanese-anthy--hiraganaonly.js', 'lib/ve/tests/ce/imetests/input-firefox-ubuntu-ibus-japanese-mozc.js', 'lib/ve/tests/ce/imetests/input-firefox-ubuntu-ibus-korean-korean.js', 'lib/ve/tests/ce/imetests/input-firefox-ubuntu-ibus-malayalam.swanalekha.js', 'lib/ve/tests/ce/imetests/input-firefox-win7-chinese-traditional-handwriting.js', 'lib/ve/tests/ce/imetests/input-firefox-win7-greek.js', 'lib/ve/tests/ce/imetests/input-firefox-win7-welsh.js', 'lib/ve/tests/ce/imetests/input-ie9-win7-chinese-traditional-handwriting.js', 'lib/ve/tests/ce/imetests/input-ie9-win7-greek.js', 'lib/ve/tests/ce/imetests/input-ie9-win7-korean.js', 'lib/ve/tests/ce/imetests/input-ie9-win7-welsh.js', 'lib/ve/tests/ce/imetests/input-ie11-win8.1-korean.js', 'lib/ve/tests/ce/imetests/input-safari-mac-native-japanese-hiragana.js', 'lib/ve/tests/ce/imetests/input-safari-mac-native-japanese-katakana.js', 'lib/ve/tests/ce/imetests/leftarrow-chromium-ubuntu-none.js', 'lib/ve/tests/ce/imetests/leftarrow-firefox-ubuntu-none.js', 'lib/ve/tests/ce/imetests/leftarrow-ie9-win7-none.js', ], 'dependencies' => [ 'unicodejs', 'ext.visualEditor.standalone', 'ext.visualEditor.core', 'ext.visualEditor.mwcore', 'ext.visualEditor.mwformatting', 'ext.visualEditor.mwlink', 'ext.visualEditor.mwgallery', 'ext.visualEditor.mwimage', 'ext.visualEditor.mwmeta', 'ext.visualEditor.mwtransclusion', 'ext.visualEditor.mwalienextension', 'ext.visualEditor.experimental', 'ext.visualEditor.desktopArticleTarget.init', 'ext.visualEditor.desktopArticleTarget', ], 'localBasePath' => __DIR__, 'remoteExtPath' => 'VisualEditor', ]; return true; } /** * Ensures that we know whether we're running inside a parser test. */ public static function onParserTestGlobals( array &$settings ) { $settings['wgVisualEditorInParserTests'] = true; } /** * @param Array $redirectParams Parameters preserved on special page redirects * to wiki pages * @return bool Always true */ public static function onRedirectSpecialArticleRedirectParams( &$redirectParams ) { array_push( $redirectParams, 'veaction', 'vesection' ); return true; } /** * If the user has specified that they want to edit the page with VE, suppress any redirect. * @param Title $title Title being used for request * @param Article|null $article * @param OutputPage $output * @param User $user * @param WebRequest $request * @param MediaWiki $mediaWiki * @return bool Always true */ public static function onBeforeInitialize( Title $title, $article, OutputPage $output, User $user, WebRequest $request, MediaWiki $mediaWiki ) { if ( $request->getVal( 'veaction' ) === 'edit' ) { $request->setVal( 'redirect', 'no' ); } return true; } /** * Set user preferences for new and auto-created accounts if so configured. * * Sets user preference to enable the VisualEditor account for new auto- * created ('auth') accounts, if $wgVisualEditorAutoAccountEnable is set. * * Sets user preference to enable the VisualEditor account for new non-auto- * created accounts, if the account's userID matches, modulo the value of * $wgVisualEditorNewAccountEnableProportion, if set. If set to '1', all new * accounts would have VisualEditor enabled; at '2', 50% would; at '20', * 5% would, and so on. * * To be removed once no longer needed. */ public static function onLocalUserCreated( $user, $autocreated ) { $config = RequestContext::getMain()->getConfig(); $enableProportion = $config->get( 'VisualEditorNewAccountEnableProportion' ); if ( // Only act on actual accounts (avoid race condition bugs) $user->isLoggedin() && // Only act if the default isn't already set !User::getDefaultOption( 'visualeditor-enable' ) && // Act if either … ( // … this is an auto-created account and we're configured so to do ( $autocreated && $config->get( 'VisualEditorAutoAccountEnable' ) ) || // … this is a real new account that matches the modulo and we're configured so to do ( !$autocreated && $enableProportion && ( ( $user->getId() % $enableProportion ) === 0 ) ) ) ) { $user->setOption( 'visualeditor-enable', 1 ); $user->saveSettings(); } return true; } /** * On login, if user has a VEE cookie, set their preference equal to it. * @param $user User * @return bool true */ public static function onUserLoggedIn( $user ) { $cookie = RequestContext::getMain()->getRequest()->getCookie( 'VEE', '' ); if ( $cookie === 'visualeditor' || $cookie === 'wikitext' ) { DeferredUpdates::addUpdate( new AtomicSectionUpdate( wfGetDB( DB_MASTER ), __METHOD__, function () use ( $user, $cookie ) { $uLatest = $user->getInstanceForUpdate(); $uLatest->setOption( 'visualeditor-editor', $cookie ); $uLatest->saveSettings(); } ) ); } return true; } }