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 (