3 * Hooks for Title Blacklist
4 * @author Victor Vasiliev
5 * @copyright © 2007-2010 Victor Vasiliev et al
6 * @license GNU General Public License 2.0 or later
9 use MediaWiki\Auth\AuthManager;
12 * Hooks for the TitleBlacklist class
16 class TitleBlacklistHooks {
19 * Called right after configuration variables have been set.
21 public static function onRegistration() {
22 global $wgDisableAuthManager, $wgAuthManagerAutoConfig;
24 if ( class_exists( AuthManager::class ) && !$wgDisableAuthManager ) {
25 $wgAuthManagerAutoConfig['preauth'][TitleBlacklistPreAuthenticationProvider::class] =
26 [ 'class' => TitleBlacklistPreAuthenticationProvider::class ];
28 Hooks::register( 'AbortNewAccount', 'TitleBlacklistHooks::abortNewAccount' );
29 Hooks::register( 'AbortAutoAccount', 'TitleBlacklistHooks::abortAutoAccount' );
30 Hooks::register( 'UserCreateForm', 'TitleBlacklistHooks::addOverrideCheckbox' );
31 Hooks::register( 'APIGetAllowedParams', 'TitleBlacklistHooks::onAPIGetAllowedParams' );
32 Hooks::register( 'AddNewAccountApiForm',
33 'TitleBlacklistHooks::onAddNewAccountApiForm' );
38 * getUserPermissionsErrorsExpensive hook
42 * @param string $action
43 * @param array &$result
46 public static function userCan( $title, $user, $action, &$result ) {
47 # Some places check createpage, while others check create.
48 # As it stands, upload does createpage, but normalize both
49 # to the same action, to stop future similar bugs.
50 if ( $action === 'createpage' || $action === 'createtalk' ) {
53 if ( $action == 'create' || $action == 'edit' || $action == 'upload' ) {
54 $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $user, $action );
55 if ( $blacklisted instanceof TitleBlacklistEntry ) {
56 $errmsg = $blacklisted->getErrorMessage( 'edit' );
58 $blacklisted->getRaw(),
61 ApiResult::setIndexedTagName( $params, 'param' );
62 $result = ApiMessage::create(
65 htmlspecialchars( $blacklisted->getRaw() ),
68 'titleblacklist-forbidden',
74 'line' => $blacklisted->getRaw(),
75 // As $errmsg usually represents a non-default message here, and ApiBase
76 // uses ->inLanguage( 'en' )->useDatabase( false ) for all messages, it will
77 // never result in useful 'info' text in the API. Try this, extra data seems
78 // to override the default.
79 'info' => 'TitleBlacklist prevents this title from being created',
89 * Display a notice if a user is only able to create or edit a page
90 * because they have tboverride.
94 * @param array &$notices
97 public static function displayBlacklistOverrideNotice( Title $title, $oldid, array &$notices ) {
98 if ( !RequestContext::getMain()->getUser()->isAllowed( 'tboverride' ) ) {
102 $blacklisted = TitleBlacklist::singleton()->isBlacklisted(
104 $title->exists() ? 'edit' : 'create'
106 if ( !$blacklisted ) {
110 $params = $blacklisted->getParams();
111 if ( isset( $params['autoconfirmed'] ) ) {
115 $msg = wfMessage( 'titleblacklist-warning' );
116 $notices['titleblacklist'] = $msg->rawParams(
117 htmlspecialchars( $blacklisted->getRaw() ) )->parseAsBlock();
122 * MovePageCheckPermissions hook (1.25+)
124 * @param Title $oldTitle
125 * @param Title $newTitle
127 * @param string $reason
128 * @param Status $status
131 public static function onMovePageCheckPermissions(
132 Title $oldTitle, Title $newTitle, User $user, $reason, Status $status
134 $titleBlacklist = TitleBlacklist::singleton();
135 $blacklisted = $titleBlacklist->userCannot( $newTitle, $user, 'move' );
136 if ( !$blacklisted ) {
137 $blacklisted = $titleBlacklist->userCannot( $oldTitle, $user, 'edit' );
139 if ( $blacklisted instanceof TitleBlacklistEntry ) {
140 $status->fatal( ApiMessage::create( [
141 $blacklisted->getErrorMessage( 'move' ),
142 $blacklisted->getRaw(),
143 $oldTitle->getFullText(),
144 $newTitle->getFullText()
153 * Check whether a user name is acceptable,
154 * and set a message if unacceptable.
156 * Used by abortNewAccount and centralAuthAutoCreate.
157 * May also be called externally to vet alternate account names.
159 * @param string $userName
160 * @param User $permissionsUser
161 * @param string &$err
162 * @param bool $override
164 * @return bool Acceptable
166 public static function acceptNewUserName(
167 $userName, $permissionsUser, &$err, $override = true, $log = false
169 $sv = self::testUserName( $userName, $permissionsUser, $override, $log );
170 if ( !$sv->isGood() ) {
171 $err = Status::wrap( $sv )->getMessage()->parse();
173 return $sv->isGood();
177 * Check whether a user name is acceptable for account creation or autocreation, and explain
178 * why not if that's the case.
180 * @param string $userName
181 * @param User $creatingUser
182 * @param bool $override Should the test be skipped, if the user has sufficient privileges?
183 * @param bool $log Log blacklist hits to Special:Log
184 * @return StatusValue
186 public static function testUserName(
187 $userName, User $creatingUser, $override = true, $log = false
189 $title = Title::makeTitleSafe( NS_USER, $userName );
190 $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $creatingUser,
191 'new-account', $override );
192 if ( $blacklisted instanceof TitleBlacklistEntry ) {
194 self::logFilterHitUsername( $creatingUser, $title, $blacklisted->getRaw() );
196 $message = $blacklisted->getErrorMessage( 'new-account' );
198 $blacklisted->getRaw(),
201 ApiResult::setIndexedTagName( $params, 'param' );
202 return StatusValue::newFatal( ApiMessage::create(
203 [ $message, $blacklisted->getRaw(), $userName ],
204 'titleblacklist-forbidden',
210 'line' => $blacklisted->getRaw(),
211 // The text of the message probably isn't useful API info, so do this instead
212 'info' => 'TitleBlacklist prevents this username from being created',
216 return StatusValue::newGood();
220 * AbortNewAccount hook
223 * @param string &$message
224 * @param Status &$status
227 public static function abortNewAccount( $user, &$message, &$status ) {
228 global $wgUser, $wgRequest;
229 $override = $wgRequest->getCheck( 'wpIgnoreTitleBlacklist' );
230 $sv = self::testUserName( $user->getName(), $wgUser, $override, true );
231 if ( !$sv->isGood() ) {
232 $status = Status::wrap( $sv );
233 $message = $status->getMessage()->parse();
235 return $sv->isGood();
239 * AbortAutoAccount hook
242 * @param string &$message
245 public static function abortAutoAccount( $user, &$message ) {
246 global $wgTitleBlacklistBlockAutoAccountCreation;
247 if ( $wgTitleBlacklistBlockAutoAccountCreation ) {
248 return self::abortNewAccount( $user, $message );
256 * @param EditPage $editor
257 * @param string $text
258 * @param string $section
259 * @param string &$error
262 public static function validateBlacklist( $editor, $text, $section, &$error ) {
263 $title = $editor->getTitle();
265 if ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
266 $blackList = TitleBlacklist::singleton();
267 $bl = $blackList->parseBlacklist( $text, 'page' );
268 $ok = $blackList->validate( $bl );
269 if ( count( $ok ) == 0 ) {
273 $errmsg = wfMessage( 'titleblacklist-invalid' )->numParams( count( $ok ) )->text();
274 $errlines = '* <code>' .
275 implode( "</code>\n* <code>", array_map( 'wfEscapeWikiText', $ok ) ) .
277 $error = Html::openElement( 'div', [ 'class' => 'errorbox' ] ) .
281 Html::closeElement( 'div' ) . "\n" .
282 Html::element( 'br', [ 'clear' => 'all' ] ) . "\n";
284 // $error will be displayed by the edit class
290 * PageContentSaveComplete hook
292 * @param Article &$article
294 * @param Content $content
295 * @param string $summary
296 * @param bool $isminor
297 * @param bool $iswatch
298 * @param string $section
301 public static function clearBlacklist( &$article, &$user,
302 $content, $summary, $isminor, $iswatch, $section
304 $title = $article->getTitle();
305 if ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
306 TitleBlacklist::singleton()->invalidate();
312 * UserCreateForm hook based on the one from AntiSpoof extension
313 * @param UsercreateTemplate &$template
316 public static function addOverrideCheckbox( &$template ) {
317 global $wgRequest, $wgUser;
319 if ( TitleBlacklist::userCanOverride( $wgUser, 'new-account' ) ) {
320 $template->addInputItem( 'wpIgnoreTitleBlacklist',
321 $wgRequest->getCheck( 'wpIgnoreTitleBlacklist' ),
322 'checkbox', 'titleblacklist-override' );
328 * @param ApiBase &$module
329 * @param array &$params
332 public static function onAPIGetAllowedParams( ApiBase &$module, array &$params ) {
333 if ( $module instanceof ApiCreateAccount ) {
334 $params['ignoretitleblacklist'] = [
335 ApiBase::PARAM_TYPE => 'boolean',
336 ApiBase::PARAM_DFLT => false
344 * Pass API parameter on to the login form when using
345 * API account creation.
347 * @param ApiBase $apiModule
348 * @param LoginForm $loginForm
349 * @return bool Always true
351 public static function onAddNewAccountApiForm( ApiBase $apiModule, LoginForm $loginForm ) {
353 $main = $apiModule->getMain();
355 if ( $main->getVal( 'ignoretitleblacklist' ) !== null ) {
356 $wgRequest->setVal( 'wpIgnoreTitleBlacklist', '1' );
358 // Suppress "unrecognized parameter" warning:
359 $main->getVal( 'wpIgnoreTitleBlacklist' );
366 * Logs the filter username hit to Special:Log if
367 * $wgTitleBlacklistLogHits is enabled.
370 * @param Title $title
371 * @param string $entry
373 public static function logFilterHitUsername( $user, $title, $entry ) {
374 global $wgTitleBlacklistLogHits;
375 if ( $wgTitleBlacklistLogHits ) {
376 $logEntry = new ManualLogEntry( 'titleblacklist', 'hit-username' );
377 $logEntry->setPerformer( $user );
378 $logEntry->setTarget( $title );
379 $logEntry->setParameters( [
380 '4::entry' => $entry,
382 $logid = $logEntry->insert();
383 $logEntry->publish( $logid );
388 * External Lua library for Scribunto
390 * @param string $engine
391 * @param array &$extraLibraries
394 public static function scribuntoExternalLibraries( $engine, array &$extraLibraries ) {
395 if ( $engine == 'lua' ) {
396 $extraLibraries['mw.ext.TitleBlacklist'] = 'Scribunto_LuaTitleBlacklistLibrary';