diff options
Diffstat (limited to 'www/wiki/extensions/WikiEditor/WikiEditor.hooks.php')
-rw-r--r-- | www/wiki/extensions/WikiEditor/WikiEditor.hooks.php | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/www/wiki/extensions/WikiEditor/WikiEditor.hooks.php b/www/wiki/extensions/WikiEditor/WikiEditor.hooks.php new file mode 100644 index 00000000..0ba259f4 --- /dev/null +++ b/www/wiki/extensions/WikiEditor/WikiEditor.hooks.php @@ -0,0 +1,446 @@ +<?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 = [ + + /* Toolbar Features */ + + // 'toolbar' is the main wikieditor feature, including toolbars and dialogs. + // The legacy name preserves user preferences for disabling the feature. + 'toolbar' => [ + 'preferences' => [ + // Ideally this key would be 'wikieditor-toolbar' + 'usebetatoolbar' => [ + 'type' => 'toggle', + 'label-message' => 'wikieditor-toolbar-preference', + 'section' => 'editing/editor', + ], + ], + 'requirements' => [ + 'usebetatoolbar' => true, + ], + 'modules' => [ + 'ext.wikiEditor', + ], + 'stylemodules' => [ + 'ext.wikiEditor.styles', + ], + ], + + /* Labs Features */ + + 'preview' => [ + 'preferences' => [ + 'wikieditor-preview' => [ + 'type' => 'toggle', + 'label-message' => 'wikieditor-preview-preference', + 'section' => 'editing/labs', + ], + ], + 'requirements' => [ + 'wikieditor-preview' => true, + ], + 'modules' => [ + 'ext.wikiEditor.preview', + ], + ], + 'publish' => [ + 'preferences' => [ + 'wikieditor-publish' => [ + 'type' => 'toggle', + 'label-message' => 'wikieditor-publish-preference', + 'section' => 'editing/labs', + ], + ], + 'requirements' => [ + 'wikieditor-publish' => true, + ], + 'modules' => [ + 'ext.wikiEditor.publish', + ], + ] + ]; + + /* Static Methods */ + + /** + * 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 string $name 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 = [] ) { + global $wgVersion; + if ( !class_exists( 'EventLogging' ) ) { + return false; + } + // Sample 6.25% (via hex digit) + if ( $data['editingSessionId'][0] > '0' ) { + return false; + } + + $user = $article->getContext()->getUser(); + $page = $article->getPage(); + $title = $article->getTitle(); + + $data = [ + 'action' => $action, + 'version' => 1, + 'editor' => 'wikitext', + 'platform' => 'desktop', // FIXME + 'integration' => 'page', + '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', 13457736, $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 $editPage, OutputPage $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 = []; + $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 $editPage, OutputPage $outputPage ) { + if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) { + return true; + } + + $req = $outputPage->getRequest(); + $editingStatsId = $req->getVal( 'editingStatsId' ); + if ( !$editingStatsId || !$req->wasPosted() ) { + $editingStatsId = self::getEditingStatsId(); + } + + $outputPage->addHTML( + Xml::element( + 'input', + [ + 'type' => 'hidden', + 'name' => 'editingStatsId', + 'id' => 'editingStatsId', + 'value' => $editingStatsId + ] + ) + ); + return true; + } + + /** + * EditPageBeforeEditToolbar hook + * + * Disable the old toolbar if the new one is enabled + * + * @param string &$toolbar + * @return bool + */ + public static function EditPageBeforeEditToolbar( &$toolbar ) { + if ( self::isEnabled( 'toolbar' ) ) { + $toolbar = Html::rawElement( + 'div', [ + 'class' => 'wikiEditor-oldToolbar' + ], + $toolbar + ); + // Return false to signify that the toolbar has been over-written, so + // the old toolbar code shouldn't be added to the page. + return false; + } + 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 + self::getMagicWords( $vars ); + + $vars['mw.msg.wikieditor'] = wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(); + + 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'] = [ + 'scripts' => [ 'tests/qunit/ext.wikiEditor.toolbar.test.js' ], + 'dependencies' => [ '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 = []; + 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 = [ + 'redirect', + 'img_right', + 'img_left', + 'img_none', + 'img_center', + 'img_thumbnail', + 'img_framed', + 'img_frameless', + ]; + $magicWords = []; + foreach ( $requiredMagicWords as $name ) { + $magicWords[$name] = MagicWord::get( $name )->getSynonym( 0 ); + } + $vars['wgWikiEditorMagicWords'] = $magicWords; + } + + /** + * 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 + * @return bool + */ + public static function editPageAttemptSave( EditPage $editPage ) { + $article = $editPage->getArticle(); + $request = $article->getContext()->getRequest(); + if ( $request->getVal( 'editingStatsId' ) ) { + self::doEventLogging( + 'saveAttempt', + $article, + [ 'editingSessionId' => $request->getVal( 'editingStatsId' ) ] + ); + } + + return true; + } + + /** + * This is attached to the MediaWiki 'EditPage::attemptSave:after' hook. + * + * @param EditPage $editPage + * @param Status $status + * @return bool + */ + public static function editPageAttemptSaveAfter( EditPage $editPage, Status $status ) { + $article = $editPage->getArticle(); + $request = $article->getContext()->getRequest(); + if ( $request->getVal( 'editingStatsId' ) ) { + $data = []; + $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; + } +} |