--- /dev/null
+<?php
+/**
+ * Hooks for Title Blacklist
+ * @author Victor Vasiliev
+ * @copyright © 2007-2010 Victor Vasiliev et al
+ * @license GNU General Public License 2.0 or later
+ */
+
+use MediaWiki\Auth\AuthManager;
+
+/**
+ * Hooks for the TitleBlacklist class
+ *
+ * @ingroup Extensions
+ */
+class TitleBlacklistHooks {
+
+ /**
+ * Called right after configuration variables have been set.
+ */
+ public static function onRegistration() {
+ global $wgDisableAuthManager, $wgAuthManagerAutoConfig;
+
+ if ( class_exists( AuthManager::class ) && !$wgDisableAuthManager ) {
+ $wgAuthManagerAutoConfig['preauth'][TitleBlacklistPreAuthenticationProvider::class] =
+ [ 'class' => TitleBlacklistPreAuthenticationProvider::class ];
+ } else {
+ Hooks::register( 'AbortNewAccount', 'TitleBlacklistHooks::abortNewAccount' );
+ Hooks::register( 'AbortAutoAccount', 'TitleBlacklistHooks::abortAutoAccount' );
+ Hooks::register( 'UserCreateForm', 'TitleBlacklistHooks::addOverrideCheckbox' );
+ Hooks::register( 'APIGetAllowedParams', 'TitleBlacklistHooks::onAPIGetAllowedParams' );
+ Hooks::register( 'AddNewAccountApiForm',
+ 'TitleBlacklistHooks::onAddNewAccountApiForm' );
+ }
+ }
+
+ /**
+ * getUserPermissionsErrorsExpensive hook
+ *
+ * @param Title $title
+ * @param User $user
+ * @param string $action
+ * @param array &$result
+ * @return bool
+ */
+ public static function userCan( $title, $user, $action, &$result ) {
+ # Some places check createpage, while others check create.
+ # As it stands, upload does createpage, but normalize both
+ # to the same action, to stop future similar bugs.
+ if ( $action === 'createpage' || $action === 'createtalk' ) {
+ $action = 'create';
+ }
+ if ( $action == 'create' || $action == 'edit' || $action == 'upload' ) {
+ $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $user, $action );
+ if ( $blacklisted instanceof TitleBlacklistEntry ) {
+ $errmsg = $blacklisted->getErrorMessage( 'edit' );
+ $params = [
+ $blacklisted->getRaw(),
+ $title->getFullText()
+ ];
+ ApiResult::setIndexedTagName( $params, 'param' );
+ $result = ApiMessage::create(
+ wfMessage(
+ $errmsg,
+ htmlspecialchars( $blacklisted->getRaw() ),
+ $title->getFullText()
+ ),
+ 'titleblacklist-forbidden',
+ [
+ 'message' => [
+ 'key' => $errmsg,
+ 'params' => $params,
+ ],
+ 'line' => $blacklisted->getRaw(),
+ // As $errmsg usually represents a non-default message here, and ApiBase
+ // uses ->inLanguage( 'en' )->useDatabase( false ) for all messages, it will
+ // never result in useful 'info' text in the API. Try this, extra data seems
+ // to override the default.
+ 'info' => 'TitleBlacklist prevents this title from being created',
+ ]
+ );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Display a notice if a user is only able to create or edit a page
+ * because they have tboverride.
+ *
+ * @param Title $title
+ * @param int $oldid
+ * @param array &$notices
+ * @return true
+ */
+ public static function displayBlacklistOverrideNotice( Title $title, $oldid, array &$notices ) {
+ if ( !RequestContext::getMain()->getUser()->isAllowed( 'tboverride' ) ) {
+ return true;
+ }
+
+ $blacklisted = TitleBlacklist::singleton()->isBlacklisted(
+ $title,
+ $title->exists() ? 'edit' : 'create'
+ );
+ if ( !$blacklisted ) {
+ return true;
+ }
+
+ $params = $blacklisted->getParams();
+ if ( isset( $params['autoconfirmed'] ) ) {
+ return true;
+ }
+
+ $msg = wfMessage( 'titleblacklist-warning' );
+ $notices['titleblacklist'] = $msg->rawParams(
+ htmlspecialchars( $blacklisted->getRaw() ) )->parseAsBlock();
+ return true;
+ }
+
+ /**
+ * MovePageCheckPermissions hook (1.25+)
+ *
+ * @param Title $oldTitle
+ * @param Title $newTitle
+ * @param User $user
+ * @param string $reason
+ * @param Status $status
+ * @return bool
+ */
+ public static function onMovePageCheckPermissions(
+ Title $oldTitle, Title $newTitle, User $user, $reason, Status $status
+ ) {
+ $titleBlacklist = TitleBlacklist::singleton();
+ $blacklisted = $titleBlacklist->userCannot( $newTitle, $user, 'move' );
+ if ( !$blacklisted ) {
+ $blacklisted = $titleBlacklist->userCannot( $oldTitle, $user, 'edit' );
+ }
+ if ( $blacklisted instanceof TitleBlacklistEntry ) {
+ $status->fatal( ApiMessage::create( [
+ $blacklisted->getErrorMessage( 'move' ),
+ $blacklisted->getRaw(),
+ $oldTitle->getFullText(),
+ $newTitle->getFullText()
+ ] ) );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether a user name is acceptable,
+ * and set a message if unacceptable.
+ *
+ * Used by abortNewAccount and centralAuthAutoCreate.
+ * May also be called externally to vet alternate account names.
+ *
+ * @param string $userName
+ * @param User $permissionsUser
+ * @param string &$err
+ * @param bool $override
+ * @param bool $log
+ * @return bool Acceptable
+ */
+ public static function acceptNewUserName(
+ $userName, $permissionsUser, &$err, $override = true, $log = false
+ ) {
+ $sv = self::testUserName( $userName, $permissionsUser, $override, $log );
+ if ( !$sv->isGood() ) {
+ $err = Status::wrap( $sv )->getMessage()->parse();
+ }
+ return $sv->isGood();
+ }
+
+ /**
+ * Check whether a user name is acceptable for account creation or autocreation, and explain
+ * why not if that's the case.
+ *
+ * @param string $userName
+ * @param User $creatingUser
+ * @param bool $override Should the test be skipped, if the user has sufficient privileges?
+ * @param bool $log Log blacklist hits to Special:Log
+ * @return StatusValue
+ */
+ public static function testUserName(
+ $userName, User $creatingUser, $override = true, $log = false
+ ) {
+ $title = Title::makeTitleSafe( NS_USER, $userName );
+ $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $creatingUser,
+ 'new-account', $override );
+ if ( $blacklisted instanceof TitleBlacklistEntry ) {
+ if ( $log ) {
+ self::logFilterHitUsername( $creatingUser, $title, $blacklisted->getRaw() );
+ }
+ $message = $blacklisted->getErrorMessage( 'new-account' );
+ $params = [
+ $blacklisted->getRaw(),
+ $userName,
+ ];
+ ApiResult::setIndexedTagName( $params, 'param' );
+ return StatusValue::newFatal( ApiMessage::create(
+ [ $message, $blacklisted->getRaw(), $userName ],
+ 'titleblacklist-forbidden',
+ [
+ 'message' => [
+ 'key' => $message,
+ 'params' => $params,
+ ],
+ 'line' => $blacklisted->getRaw(),
+ // The text of the message probably isn't useful API info, so do this instead
+ 'info' => 'TitleBlacklist prevents this username from being created',
+ ]
+ ) );
+ }
+ return StatusValue::newGood();
+ }
+
+ /**
+ * AbortNewAccount hook
+ *
+ * @param User $user
+ * @param string &$message
+ * @param Status &$status
+ * @return bool
+ */
+ public static function abortNewAccount( $user, &$message, &$status ) {
+ global $wgUser, $wgRequest;
+ $override = $wgRequest->getCheck( 'wpIgnoreTitleBlacklist' );
+ $sv = self::testUserName( $user->getName(), $wgUser, $override, true );
+ if ( !$sv->isGood() ) {
+ $status = Status::wrap( $sv );
+ $message = $status->getMessage()->parse();
+ }
+ return $sv->isGood();
+ }
+
+ /**
+ * AbortAutoAccount hook
+ *
+ * @param User $user
+ * @param string &$message
+ * @return bool
+ */
+ public static function abortAutoAccount( $user, &$message ) {
+ global $wgTitleBlacklistBlockAutoAccountCreation;
+ if ( $wgTitleBlacklistBlockAutoAccountCreation ) {
+ return self::abortNewAccount( $user, $message );
+ }
+ return true;
+ }
+
+ /**
+ * EditFilter hook
+ *
+ * @param EditPage $editor
+ * @param string $text
+ * @param string $section
+ * @param string &$error
+ * @return true
+ */
+ public static function validateBlacklist( $editor, $text, $section, &$error ) {
+ $title = $editor->getTitle();
+
+ if ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
+ $blackList = TitleBlacklist::singleton();
+ $bl = $blackList->parseBlacklist( $text, 'page' );
+ $ok = $blackList->validate( $bl );
+ if ( count( $ok ) == 0 ) {
+ return true;
+ }
+
+ $errmsg = wfMessage( 'titleblacklist-invalid' )->numParams( count( $ok ) )->text();
+ $errlines = '* <code>' .
+ implode( "</code>\n* <code>", array_map( 'wfEscapeWikiText', $ok ) ) .
+ '</code>';
+ $error = Html::openElement( 'div', [ 'class' => 'errorbox' ] ) .
+ $errmsg .
+ "\n" .
+ $errlines .
+ Html::closeElement( 'div' ) . "\n" .
+ Html::element( 'br', [ 'clear' => 'all' ] ) . "\n";
+
+ // $error will be displayed by the edit class
+ }
+ return true;
+ }
+
+ /**
+ * PageContentSaveComplete hook
+ *
+ * @param Article &$article
+ * @param User &$user
+ * @param Content $content
+ * @param string $summary
+ * @param bool $isminor
+ * @param bool $iswatch
+ * @param string $section
+ * @return true
+ */
+ public static function clearBlacklist( &$article, &$user,
+ $content, $summary, $isminor, $iswatch, $section
+ ) {
+ $title = $article->getTitle();
+ if ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
+ TitleBlacklist::singleton()->invalidate();
+ }
+ return true;
+ }
+
+ /**
+ * UserCreateForm hook based on the one from AntiSpoof extension
+ * @param UsercreateTemplate &$template
+ * @return true
+ */
+ public static function addOverrideCheckbox( &$template ) {
+ global $wgRequest, $wgUser;
+
+ if ( TitleBlacklist::userCanOverride( $wgUser, 'new-account' ) ) {
+ $template->addInputItem( 'wpIgnoreTitleBlacklist',
+ $wgRequest->getCheck( 'wpIgnoreTitleBlacklist' ),
+ 'checkbox', 'titleblacklist-override' );
+ }
+ return true;
+ }
+
+ /**
+ * @param ApiBase &$module
+ * @param array &$params
+ * @return bool
+ */
+ public static function onAPIGetAllowedParams( ApiBase &$module, array &$params ) {
+ if ( $module instanceof ApiCreateAccount ) {
+ $params['ignoretitleblacklist'] = [
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false
+ ];
+ }
+
+ return true;
+ }
+
+ /**
+ * Pass API parameter on to the login form when using
+ * API account creation.
+ *
+ * @param ApiBase $apiModule
+ * @param LoginForm $loginForm
+ * @return bool Always true
+ */
+ public static function onAddNewAccountApiForm( ApiBase $apiModule, LoginForm $loginForm ) {
+ global $wgRequest;
+ $main = $apiModule->getMain();
+
+ if ( $main->getVal( 'ignoretitleblacklist' ) !== null ) {
+ $wgRequest->setVal( 'wpIgnoreTitleBlacklist', '1' );
+
+ // Suppress "unrecognized parameter" warning:
+ $main->getVal( 'wpIgnoreTitleBlacklist' );
+ }
+
+ return true;
+ }
+
+ /**
+ * Logs the filter username hit to Special:Log if
+ * $wgTitleBlacklistLogHits is enabled.
+ *
+ * @param User $user
+ * @param Title $title
+ * @param string $entry
+ */
+ public static function logFilterHitUsername( $user, $title, $entry ) {
+ global $wgTitleBlacklistLogHits;
+ if ( $wgTitleBlacklistLogHits ) {
+ $logEntry = new ManualLogEntry( 'titleblacklist', 'hit-username' );
+ $logEntry->setPerformer( $user );
+ $logEntry->setTarget( $title );
+ $logEntry->setParameters( [
+ '4::entry' => $entry,
+ ] );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
+ }
+ }
+
+ /**
+ * External Lua library for Scribunto
+ *
+ * @param string $engine
+ * @param array &$extraLibraries
+ * @return bool
+ */
+ public static function scribuntoExternalLibraries( $engine, array &$extraLibraries ) {
+ if ( $engine == 'lua' ) {
+ $extraLibraries['mw.ext.TitleBlacklist'] = 'Scribunto_LuaTitleBlacklistLibrary';
+ }
+ return true;
+ }
+}