mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/WikiEditor
synced 2024-11-12 09:57:16 +00:00
8a1920907c
Change-Id: Ie38ddd4bb474026c592ba1678a19636fc9a20055
528 lines
14 KiB
PHP
528 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Hooks for WikiEditor extension
|
|
*
|
|
* @file
|
|
* @ingroup Extensions
|
|
*/
|
|
|
|
class WikiEditorHooks {
|
|
// ID used for grouping entries all of a session's entries together in
|
|
// EventLogging.
|
|
private static $statsId = false;
|
|
|
|
/* Protected Static Members */
|
|
|
|
protected static $features = array(
|
|
|
|
/* Toolbar Features */
|
|
|
|
'toolbar' => array(
|
|
'preferences' => array(
|
|
// Ideally this key would be 'wikieditor-toolbar'
|
|
'usebetatoolbar' => array(
|
|
'type' => 'toggle',
|
|
'label-message' => 'wikieditor-toolbar-preference',
|
|
'section' => 'editing/editor',
|
|
),
|
|
),
|
|
'requirements' => array(
|
|
'usebetatoolbar' => true,
|
|
),
|
|
'modules' => array(
|
|
'ext.wikiEditor.toolbar',
|
|
),
|
|
'stylemodules' => array(
|
|
'ext.wikiEditor.toolbar.styles',
|
|
),
|
|
),
|
|
'dialogs' => array(
|
|
'preferences' => array(
|
|
// Ideally this key would be 'wikieditor-toolbar-dialogs'
|
|
'usebetatoolbar-cgd' => array(
|
|
'type' => 'toggle',
|
|
'label-message' => 'wikieditor-toolbar-dialogs-preference',
|
|
'section' => 'editing/editor',
|
|
),
|
|
),
|
|
'requirements' => array(
|
|
'usebetatoolbar-cgd' => true,
|
|
'usebetatoolbar' => true,
|
|
),
|
|
'modules' => array(
|
|
'ext.wikiEditor.dialogs',
|
|
),
|
|
),
|
|
'hidesig' => array(
|
|
'preferences' => array(
|
|
'wikieditor-toolbar-hidesig' => array(
|
|
'type' => 'toggle',
|
|
'label-message' => 'wikieditor-toolbar-hidesig',
|
|
'section' => 'editing/editor',
|
|
),
|
|
),
|
|
'requirements' => array(
|
|
'wikieditor-toolbar-hidesig' => true,
|
|
'usebetatoolbar' => true,
|
|
),
|
|
'modules' => array(
|
|
'ext.wikiEditor.toolbar.hideSig',
|
|
),
|
|
),
|
|
|
|
/* Labs Features */
|
|
|
|
'preview' => array(
|
|
'preferences' => array(
|
|
'wikieditor-preview' => array(
|
|
'type' => 'toggle',
|
|
'label-message' => 'wikieditor-preview-preference',
|
|
'section' => 'editing/labs',
|
|
),
|
|
),
|
|
'requirements' => array(
|
|
'wikieditor-preview' => true,
|
|
),
|
|
'modules' => array(
|
|
'ext.wikiEditor.preview',
|
|
),
|
|
),
|
|
'publish' => array(
|
|
'preferences' => array(
|
|
'wikieditor-publish' => array(
|
|
'type' => 'toggle',
|
|
'label-message' => 'wikieditor-publish-preference',
|
|
'section' => 'editing/labs',
|
|
),
|
|
),
|
|
'requirements' => array(
|
|
'wikieditor-publish' => true,
|
|
),
|
|
'modules' => array(
|
|
'ext.wikiEditor.publish',
|
|
),
|
|
)
|
|
);
|
|
|
|
/* Static Methods */
|
|
|
|
/**
|
|
* T99257: Extension registration does not properly support 2d arrays so set it as a global for now
|
|
*/
|
|
public static function onRegistration() {
|
|
// Each module may be configured individually to be globally on/off or user preference based
|
|
$features = array(
|
|
|
|
/* Textarea / i-frame compatible (OK to deploy) */
|
|
|
|
'toolbar' => array( 'global' => false, 'user' => true ),
|
|
// Provides interactive tools
|
|
'dialogs' => array( 'global' => false, 'user' => true ),
|
|
// Hide signature button from main namespace
|
|
'hidesig' => array( 'global' => true, 'user' => false ),
|
|
|
|
/* Textarea / i-frame compatible, but still experimental and unstable (do not deploy!) */
|
|
|
|
// Adds a tab for previewing in-line
|
|
'preview' => array( 'global' => false, 'user' => true ),
|
|
// Adds a button and dialog for step-by-step publishing
|
|
'publish' => array( 'global' => false, 'user' => true ),
|
|
);
|
|
|
|
// Eww, do a 2d array merge so we don't wipe out settings
|
|
global $wgWikiEditorFeatures;
|
|
if ( $wgWikiEditorFeatures ) {
|
|
foreach ( $features as $name => $settings ) {
|
|
if ( isset( $wgWikiEditorFeatures[$name] ) ) {
|
|
$wgWikiEditorFeatures[$name] += $settings;
|
|
} else {
|
|
$wgWikiEditorFeatures[$name] = $settings;
|
|
}
|
|
}
|
|
} else {
|
|
$wgWikiEditorFeatures = $features;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks if a certain option is enabled
|
|
*
|
|
* This method is public to allow other extensions that use WikiEditor to use the
|
|
* same configuration as WikiEditor itself
|
|
*
|
|
* @param $name string Name of the feature, should be a key of $features
|
|
* @return bool
|
|
*/
|
|
public static function isEnabled( $name ) {
|
|
global $wgWikiEditorFeatures, $wgUser;
|
|
|
|
// Features with global set to true are always enabled
|
|
if ( !isset( $wgWikiEditorFeatures[$name] ) || $wgWikiEditorFeatures[$name]['global'] ) {
|
|
return true;
|
|
}
|
|
// Features with user preference control can have any number of preferences
|
|
// to be specific values to be enabled
|
|
if ( $wgWikiEditorFeatures[$name]['user'] ) {
|
|
if ( isset( self::$features[$name]['requirements'] ) ) {
|
|
foreach ( self::$features[$name]['requirements'] as $requirement => $value ) {
|
|
// Important! We really do want fuzzy evaluation here
|
|
if ( $wgUser->getOption( $requirement ) != $value ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
// Features controlled by $wgWikiEditorFeatures with both global and user
|
|
// set to false are always disabled
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Log stuff to EventLogging's Schema:Edit - see https://meta.wikimedia.org/wiki/Schema:Edit
|
|
* If you don't have EventLogging installed, does nothing.
|
|
*
|
|
* @param string $action
|
|
* @param Article $article Which article (with full context, page, title, etc.)
|
|
* @param array $data Data to log for this action
|
|
* @return bool Whether the event was logged or not.
|
|
*/
|
|
public static function doEventLogging( $action, $article, $data = array() ) {
|
|
global $wgVersion;
|
|
if ( !class_exists( 'EventLogging' ) ) {
|
|
return false;
|
|
}
|
|
// Sample 25% (via hex digit)
|
|
if ( $data['editingSessionId'][0] > '3' ) {
|
|
return false;
|
|
}
|
|
|
|
$user = $article->getContext()->getUser();
|
|
$page = $article->getPage();
|
|
$title = $article->getTitle();
|
|
|
|
$data = array(
|
|
'action' => $action,
|
|
'version' => 1,
|
|
'editor' => 'wikitext',
|
|
'platform' => 'desktop', // FIXME
|
|
'integration' => 'page',
|
|
'page.length' => -1, // FIXME
|
|
'page.id' => $page->getId(),
|
|
'page.title' => $title->getPrefixedText(),
|
|
'page.ns' => $title->getNamespace(),
|
|
'page.revid' => $page->getRevision() ? $page->getRevision()->getId() : 0,
|
|
'user.id' => $user->getId(),
|
|
'user.editCount' => $user->getEditCount() ?: 0,
|
|
'mediawiki.version' => $wgVersion
|
|
) + $data;
|
|
|
|
if ( $user->isAnon() ) {
|
|
$data['user.class'] = 'IP';
|
|
}
|
|
|
|
return EventLogging::logEvent( 'Edit', 11448630, $data );
|
|
}
|
|
|
|
/**
|
|
* EditPage::showEditForm:initial hook
|
|
*
|
|
* Adds the modules to the edit form
|
|
*
|
|
* @param EditPage $editPage the current EditPage object.
|
|
* @param OutputPage $outputPage object.
|
|
* @return bool
|
|
*/
|
|
public static function editPageShowEditFormInitial( $editPage, $outputPage ) {
|
|
if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) {
|
|
return true;
|
|
}
|
|
|
|
// Add modules for enabled features
|
|
foreach ( self::$features as $name => $feature ) {
|
|
if ( !self::isEnabled( $name ) ) {
|
|
continue;
|
|
}
|
|
if ( isset( $feature['stylemodules'] ) ) {
|
|
$outputPage->addModuleStyles( $feature['stylemodules'] );
|
|
}
|
|
if ( isset( $feature['modules'] ) ) {
|
|
$outputPage->addModules( $feature['modules'] );
|
|
}
|
|
}
|
|
|
|
$article = $editPage->getArticle();
|
|
$request = $article->getContext()->getRequest();
|
|
// Don't run this if the request was posted - we don't want to log 'init' when the
|
|
// user just pressed 'Show preview' or 'Show changes', or switched from VE keeping
|
|
// changes.
|
|
if ( class_exists( 'EventLogging' ) && !$request->wasPosted() ) {
|
|
$data = array();
|
|
$data['editingSessionId'] = self::getEditingStatsId();
|
|
if ( $request->getVal( 'section' ) ) {
|
|
$data['action.init.type'] = 'section';
|
|
} else {
|
|
$data['action.init.type'] = 'page';
|
|
}
|
|
if ( $request->getHeader( 'Referer' ) ) {
|
|
if ( $request->getVal( 'section' ) === 'new' || !$article->exists() ) {
|
|
$data['action.init.mechanism'] = 'new';
|
|
} else {
|
|
$data['action.init.mechanism'] = 'click';
|
|
}
|
|
} else {
|
|
$data['action.init.mechanism'] = 'url';
|
|
}
|
|
|
|
self::doEventLogging( 'init', $article, $data );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* EditPage::showEditForm:fields hook
|
|
*
|
|
* Adds the event fields to the edit form
|
|
*
|
|
* @param EditPage $editPage the current EditPage object.
|
|
* @param OutputPage $outputPage object.
|
|
* @return bool
|
|
*/
|
|
public static function editPageShowEditFormFields( $editPage, $outputPage ) {
|
|
if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) {
|
|
return true;
|
|
}
|
|
|
|
$req = $outputPage->getContext()->getRequest();
|
|
$editingStatsId = $req->getVal( 'editingStatsId' );
|
|
if ( !$editingStatsId || !$req->wasPosted() ) {
|
|
$editingStatsId = self::getEditingStatsId();
|
|
}
|
|
|
|
$outputPage->addHTML(
|
|
Xml::element(
|
|
'input',
|
|
array(
|
|
'type' => 'hidden',
|
|
'name' => 'editingStatsId',
|
|
'id' => 'editingStatsId',
|
|
'value' => $editingStatsId
|
|
)
|
|
)
|
|
);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* EditPageBeforeEditToolbar hook
|
|
*
|
|
* Disable the old toolbar if the new one is enabled
|
|
*
|
|
* @param $toolbar html
|
|
* @return bool
|
|
*/
|
|
public static function EditPageBeforeEditToolbar( &$toolbar ) {
|
|
if ( self::isEnabled( 'toolbar' ) ) {
|
|
$toolbar = Html::rawElement(
|
|
'div', array(
|
|
'class' => 'wikiEditor-oldToolbar'
|
|
),
|
|
$toolbar
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* GetPreferences hook
|
|
*
|
|
* Adds WikiEditor-related items to the preferences
|
|
*
|
|
* @param User $user current user
|
|
* @param array $defaultPreferences list of default user preference controls
|
|
* @return bool
|
|
*/
|
|
public static function getPreferences( $user, &$defaultPreferences ) {
|
|
global $wgWikiEditorFeatures;
|
|
|
|
foreach ( self::$features as $name => $feature ) {
|
|
if (
|
|
isset( $feature['preferences'] ) &&
|
|
( !isset( $wgWikiEditorFeatures[$name] ) || $wgWikiEditorFeatures[$name]['user'] )
|
|
) {
|
|
foreach ( $feature['preferences'] as $key => $options ) {
|
|
$defaultPreferences[$key] = $options;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param $vars array
|
|
* @return bool
|
|
*/
|
|
public static function resourceLoaderGetConfigVars( &$vars ) {
|
|
// expose magic words for use by the wikieditor toolbar
|
|
WikiEditorHooks::getMagicWords( $vars );
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* ResourceLoaderTestModules hook
|
|
*
|
|
* Registers JavaScript test modules
|
|
*
|
|
* @param $testModules array of javascript testing modules. 'qunit' is fed using
|
|
* tests/qunit/QUnitTestResources.php.
|
|
* @param $resourceLoader object
|
|
* @return bool
|
|
*/
|
|
public static function resourceLoaderTestModules( &$testModules, &$resourceLoader ) {
|
|
$testModules['qunit']['ext.wikiEditor.toolbar.test'] = array(
|
|
'scripts' => array( 'tests/qunit/ext.wikiEditor.toolbar.test.js' ),
|
|
'dependencies' => array( 'ext.wikiEditor.toolbar' ),
|
|
'localBasePath' => __DIR__,
|
|
'remoteExtPath' => 'WikiEditor',
|
|
);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* MakeGlobalVariablesScript hook
|
|
*
|
|
* Adds enabled/disabled switches for WikiEditor modules
|
|
* @param $vars array
|
|
* @return bool
|
|
*/
|
|
public static function makeGlobalVariablesScript( &$vars ) {
|
|
// Build and export old-style wgWikiEditorEnabledModules object for back compat
|
|
$enabledModules = array();
|
|
foreach ( self::$features as $name => $feature ) {
|
|
$enabledModules[$name] = self::isEnabled( $name );
|
|
}
|
|
|
|
$vars['wgWikiEditorEnabledModules'] = $enabledModules;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Expose useful magic words which are used by the wikieditor toolbar
|
|
* @param $vars array
|
|
* @return bool
|
|
*/
|
|
private static function getMagicWords( &$vars ) {
|
|
$requiredMagicWords = array(
|
|
'redirect',
|
|
'img_right',
|
|
'img_left',
|
|
'img_none',
|
|
'img_center',
|
|
'img_thumbnail',
|
|
'img_framed',
|
|
'img_frameless',
|
|
);
|
|
$magicWords = array();
|
|
foreach ( $requiredMagicWords as $name ) {
|
|
$magicWords[$name] = MagicWord::get( $name )->getSynonym( 0 );
|
|
}
|
|
$vars['wgWikiEditorMagicWords'] = $magicWords;
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds WikiEditor 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( array( 'ext.wikiEditor.init' ) );
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets a 32 character alphanumeric random string to be used for stats.
|
|
* @return string
|
|
*/
|
|
private static function getEditingStatsId() {
|
|
if ( self::$statsId ) {
|
|
return self::$statsId;
|
|
}
|
|
return self::$statsId = MWCryptRand::generateHex( 32 );
|
|
}
|
|
|
|
/**
|
|
* This is attached to the MediaWiki 'EditPage::attemptSave' hook.
|
|
*
|
|
* @param EditPage $editPage
|
|
* @param Status $status
|
|
* @return boolean
|
|
*/
|
|
public static function editPageAttemptSave( EditPage $editPage ) {
|
|
$article = $editPage->getArticle();
|
|
$request = $article->getContext()->getRequest();
|
|
if ( $request->getVal( 'editingStatsId' ) ) {
|
|
self::doEventLogging(
|
|
'saveAttempt',
|
|
$article,
|
|
array( 'editingSessionId' => $request->getVal( 'editingStatsId' ) )
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This is attached to the MediaWiki 'EditPage::attemptSave:after' hook.
|
|
*
|
|
* @param EditPage $editPage
|
|
* @param Status $status
|
|
* @return boolean
|
|
*/
|
|
public static function editPageAttemptSaveAfter( EditPage $editPage, Status $status ) {
|
|
$article = $editPage->getArticle();
|
|
$request = $article->getContext()->getRequest();
|
|
if ( $request->getVal( 'editingStatsId' ) ) {
|
|
$data = array();
|
|
$data['editingSessionId'] = $request->getVal( 'editingStatsId' );
|
|
|
|
if ( $status->isOK() ) {
|
|
$action = 'saveSuccess';
|
|
} else {
|
|
$action = 'saveFailure';
|
|
$errors = $status->getErrorsArray();
|
|
|
|
if ( isset( $errors[0][0] ) ) {
|
|
$data['action.saveFailure.message'] = $errors[0][0];
|
|
}
|
|
|
|
if ( $status->value === EditPage::AS_CONFLICT_DETECTED ) {
|
|
$data['action.saveFailure.type'] = 'editConflict';
|
|
} elseif ( $status->value === EditPage::AS_ARTICLE_WAS_DELETED ) {
|
|
$data['action.saveFailure.type'] = 'editPageDeleted';
|
|
} elseif ( isset( $errors[0][0] ) && $errors[0][0] === 'abusefilter-disallowed' ) {
|
|
$data['action.saveFailure.type'] = 'extensionAbuseFilter';
|
|
} elseif ( isset( $editPage->getArticle()->getPage()->ConfirmEdit_ActivateCaptcha ) ) {
|
|
// TODO: :(
|
|
$data['action.saveFailure.type'] = 'extensionCaptcha';
|
|
} elseif ( isset( $errors[0][0] ) && $errors[0][0] === 'spamprotectiontext' ) {
|
|
$data['action.saveFailure.type'] = 'extensionSpamBlacklist';
|
|
} else {
|
|
// Catch everything else... We don't seem to get userBadToken or
|
|
// userNewUser through this hook.
|
|
$data['action.saveFailure.type'] = 'responseUnknown';
|
|
}
|
|
}
|
|
self::doEventLogging( $action, $article, $data );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|