diff --git a/hooks.txt b/hooks.txt index e3cd9c957..4d49a0735 100644 --- a/hooks.txt +++ b/hooks.txt @@ -10,6 +10,10 @@ to add events to the AbuseFilter extension. 'AbuseFilter-builder': Allows overwriting of the builder values returned by AbuseFilter::getBuilderValues &$realValues: Builder values +'AbuseFilter-deprecatedVariables': Allows adding deprecated variables. If a filter uses an old variable, the parser +will automatically translate it to the new one. +&$deprecatedVariables: array of deprecated variables, syntax: [ 'old_name' => 'new_name' ] + 'AbuseFilter-computeVariable': Like AbuseFilter-interceptVariable but called if the requested method wasn't found. Return true to indicate that the method is known to the hook and was computed successful. $method: Method to generate the variable diff --git a/i18n/en.json b/i18n/en.json index d931bb425..f94103e26 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -312,18 +312,18 @@ "abusefilter-edit-builder-vars-new-content-model": "New content model", "abusefilter-edit-builder-vars-removedlines": "Lines removed in edit", "abusefilter-edit-builder-vars-summary": "Edit summary/reason", - "abusefilter-edit-builder-vars-article-id": "Page ID", - "abusefilter-edit-builder-vars-article-ns": "Page namespace", - "abusefilter-edit-builder-vars-article-text": "Page title (without namespace)", - "abusefilter-edit-builder-vars-article-prefixedtext": "Full page title", + "abusefilter-edit-builder-vars-page-id": "Page ID", + "abusefilter-edit-builder-vars-page-ns": "Page namespace", + "abusefilter-edit-builder-vars-page-title": "Page title (without namespace)", + "abusefilter-edit-builder-vars-page-prefixedtitle": "Full page title", "abusefilter-edit-builder-vars-movedfrom-id": "Page ID of move source page", "abusefilter-edit-builder-vars-movedfrom-ns": "Namespace of move source page", - "abusefilter-edit-builder-vars-movedfrom-text": "Title of move source page", - "abusefilter-edit-builder-vars-movedfrom-prefixedtext": "Full title of move source page", + "abusefilter-edit-builder-vars-movedfrom-title": "Title of move source page", + "abusefilter-edit-builder-vars-movedfrom-prefixedtitle": "Full title of move source page", "abusefilter-edit-builder-vars-movedto-id": "Page ID of move destination page", "abusefilter-edit-builder-vars-movedto-ns": "Namespace of move destination page", - "abusefilter-edit-builder-vars-movedto-text": "Title of move destination page", - "abusefilter-edit-builder-vars-movedto-prefixedtext": "Full title of move destination page", + "abusefilter-edit-builder-vars-movedto-title": "Title of move destination page", + "abusefilter-edit-builder-vars-movedto-prefixedtitle": "Full title of move destination page", "abusefilter-edit-builder-vars-user-editcount": "Edit count of the user", "abusefilter-edit-builder-vars-user-age": "Age of the user account", "abusefilter-edit-builder-vars-user-name": "Name of the user account", diff --git a/i18n/qqq.json b/i18n/qqq.json index 4dbd0ba99..fc01a8b04 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -345,18 +345,18 @@ "abusefilter-edit-builder-vars-new-content-model": "New content model of the page. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-removedlines": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-summary": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-article-id": "Abuse filter syntax option in a dropdown from the group {{msg-mw|Abusefilter-edit-builder-group-vars}}.\n{{Identical|Page ID}}", - "abusefilter-edit-builder-vars-article-ns": "The namespace that the page for the trigger is supposed to be in. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-article-text": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-article-prefixedtext": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-page-id": "Abuse filter syntax option in a dropdown from the group {{msg-mw|Abusefilter-edit-builder-group-vars}}.\n{{Identical|Page ID}}", + "abusefilter-edit-builder-vars-page-ns": "The namespace that the page for the trigger is supposed to be in. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-page-title": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-page-prefixedtitle": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-movedfrom-id": "Paraphrase: The page ID of the page to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-movedfrom-ns": "Paraphrase: Namespace of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-movedfrom-text": "Paraphrase: Name of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-movedfrom-prefixedtext": "Paraphrase: Full name of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-movedfrom-title": "Paraphrase: Name of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-movedfrom-prefixedtitle": "Paraphrase: Full name of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-movedto-id": "Paraphrased: Page ID of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-movedto-ns": "Paraphrased: Namespace of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-movedto-text": "Paraphrased: Name of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", - "abusefilter-edit-builder-vars-movedto-prefixedtext": "Paraphrased: Full name of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-movedto-title": "Paraphrased: Name of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", + "abusefilter-edit-builder-vars-movedto-prefixedtitle": "Paraphrased: Full name of the destination of the page that is to be moved. Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-user-editcount": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-user-age": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", "abusefilter-edit-builder-vars-user-name": "Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-vars}}.", diff --git a/includes/AbuseFilter.php b/includes/AbuseFilter.php index e84238d7a..584fdfc56 100644 --- a/includes/AbuseFilter.php +++ b/includes/AbuseFilter.php @@ -109,18 +109,18 @@ class AbuseFilter { 'old_content_model' => 'old-content-model', 'removed_lines' => 'removedlines', 'summary' => 'summary', - 'article_articleid' => 'article-id', - 'article_namespace' => 'article-ns', - 'article_text' => 'article-text', - 'article_prefixedtext' => 'article-prefixedtext', - 'moved_from_articleid' => 'movedfrom-id', + 'page_id' => 'page-id', + 'page_namespace' => 'page-ns', + 'page_title' => 'page-title', + 'page_prefixedtitle' => 'page-prefixedtitle', + 'moved_from_id' => 'movedfrom-id', 'moved_from_namespace' => 'movedfrom-ns', - 'moved_from_text' => 'movedfrom-text', - 'moved_from_prefixedtext' => 'movedfrom-prefixedtext', - 'moved_to_articleid' => 'movedto-id', + 'moved_from_title' => 'movedfrom-title', + 'moved_from_prefixedtitle' => 'movedfrom-prefixedtitle', + 'moved_to_id' => 'movedto-id', 'moved_to_namespace' => 'movedto-ns', - 'moved_to_text' => 'movedto-text', - 'moved_to_prefixedtext' => 'movedto-prefixedtext', + 'moved_to_title' => 'movedto-title', + 'moved_to_prefixedtitle' => 'movedto-prefixedtitle', 'user_editcount' => 'user-editcount', 'user_age' => 'user-age', 'user_name' => 'user-name', @@ -138,12 +138,12 @@ class AbuseFilter { 'added_lines_pst' => 'addedlines-pst', 'new_text' => 'new-text-stripped', 'new_html' => 'new-html', - 'article_restrictions_edit' => 'restrictions-edit', - 'article_restrictions_move' => 'restrictions-move', - 'article_restrictions_create' => 'restrictions-create', - 'article_restrictions_upload' => 'restrictions-upload', - 'article_recent_contributors' => 'recent-contributors', - 'article_first_contributor' => 'first-contributor', + 'page_restrictions_edit' => 'restrictions-edit', + 'page_restrictions_move' => 'restrictions-move', + 'page_restrictions_create' => 'restrictions-create', + 'page_restrictions_upload' => 'restrictions-upload', + 'page_recent_contributors' => 'recent-contributors', + 'page_first_contributor' => 'first-contributor', 'moved_from_restrictions_edit' => 'movedfrom-restrictions-edit', 'moved_from_restrictions_move' => 'movedfrom-restrictions-move', 'moved_from_restrictions_create' => 'movedfrom-restrictions-create', @@ -174,6 +174,25 @@ class AbuseFilter { 'old_html' => 'old-html' ]; + public static $deprecatedVars = [ + 'article_text' => 'page_title', + 'article_prefixedtext' => 'page_prefixedtitle', + 'article_namespace' => 'page_namespace', + 'article_articleid' => 'page_id', + 'article_restrictions_edit' => 'page_restrictions_edit', + 'article_restrictions_move' => 'page_restrictions_move', + 'article_restrictions_create' => 'page_restrictions_create', + 'article_restrictions_upload' => 'page_restrictions_upload', + 'article_recent_contributors' => 'page_recent_contributors', + 'article_first_contributor' => 'page_first_contributor', + 'moved_from_text' => 'moved_from_title', + 'moved_from_prefixedtext' => 'moved_from_prefixedtitle', + 'moved_from_articleid' => 'moved_from_id', + 'moved_to_text' => 'moved_to_title', + 'moved_to_prefixedtext' => 'moved_to_prefixedtitle', + 'moved_to_articleid' => 'moved_to_id', + ]; + public static $editboxName = null; /** @@ -311,6 +330,23 @@ class AbuseFilter { return $realValues; } + /** + * @return array + */ + public static function getDeprecatedVariables() { + static $deprecatedVars = null; + + if ( $deprecatedVars ) { + return $deprecatedVars; + } + + $deprecatedVars = self::$deprecatedVars; + + Hooks::run( 'AbuseFilter-deprecatedVariables', [ &$deprecatedVars ] ); + + return $deprecatedVars; + } + /** * @param string $filter * @return bool @@ -364,19 +400,36 @@ class AbuseFilter { /** * @param Title|null $title * @param string $prefix + * @param bool $transition Temporary parameter to help with T173889 and to be removed afterwards * @return AbuseFilterVariableHolder */ - public static function generateTitleVars( $title, $prefix ) { + public static function generateTitleVars( $title, $prefix, $transition = true ) { $vars = new AbuseFilterVariableHolder; if ( !$title ) { return $vars; } - $vars->setVar( $prefix . '_ARTICLEID', $title->getArticleID() ); + // Temporary overrides for T173889, necessary because Flow (and maybe + // other extensions) still pass old prefix/suffix and thus fail, since + // hybrid variables are generated (e.g. article_prefixedtitle). + // Once their variables will be renamed according to the new syntax, + // we should get rid of these if and just use the new prefix/suffix. + // Right now, what we want to do is: + // - Use new prefix/suffix for AF's own variables (they're handled at parser level) + // - Use old prefix/suffix for external variables (we don't handle them) + $titleSuffix = 'TITLE'; + if ( $transition && $prefix === 'BOARD' ) { + $titleSuffix = 'TEXT'; + } + if ( $transition && $prefix === 'ARTICLE' ) { + $prefix = 'PAGE'; + } + + $vars->setVar( $prefix . '_ID', $title->getArticleID() ); $vars->setVar( $prefix . '_NAMESPACE', $title->getNamespace() ); - $vars->setVar( $prefix . '_TEXT', $title->getText() ); - $vars->setVar( $prefix . '_PREFIXEDTEXT', $title->getPrefixedText() ); + $vars->setVar( $prefix . "_$titleSuffix", $title->getText() ); + $vars->setVar( $prefix . "_PREFIXED$titleSuffix", $title->getPrefixedText() ); global $wgRestrictionTypes; foreach ( $wgRestrictionTypes as $action ) { @@ -2040,7 +2093,9 @@ class AbuseFilter { */ public static function getAceConfig( $canEdit ) { $values = self::getBuilderValues(); - $builderVariables = implode( '|', array_keys( $values['vars'] ) ); + $deprecatedVars = self::getDeprecatedVariables(); + $builderVariables = implode( '|', + array_keys( array_merge( $values['vars'], $deprecatedVars ) ) ); $builderFunctions = implode( '|', array_keys( AbuseFilterParser::$mFunctions ) ); // AbuseFilterTokenizer::$keywords also includes constants (true, false and null), // but Ace redefines these constants afterwards so this will not be an issue @@ -2581,7 +2636,7 @@ class AbuseFilter { $vars->addHolders( self::generateUserVars( $user ), - self::generateTitleVars( $title, 'ARTICLE' ) + self::generateTitleVars( $title, 'PAGE' ) ); $vars->setVar( 'ACTION', 'delete' ); @@ -2607,7 +2662,7 @@ class AbuseFilter { $vars->addHolders( self::generateUserVars( $user ), - self::generateTitleVars( $title, 'ARTICLE' ) + self::generateTitleVars( $title, 'PAGE' ) ); $vars->setVar( 'ACTION', 'edit' ); @@ -2765,9 +2820,13 @@ class AbuseFilter { } // Now, build the body of the table. + $deprecatedVars = self::getDeprecatedVariables(); foreach ( $vars as $key => $value ) { $key = strtolower( $key ); + if ( array_key_exists( $key, $deprecatedVars ) ) { + $key = $deprecatedVars[$key]; + } if ( !empty( $variableMessageMappings[$key] ) ) { $mapping = $variableMessageMappings[$key]; $keyDisplay = $context->msg( "abusefilter-edit-builder-vars-$mapping" )->parse() . diff --git a/includes/AbuseFilterHooks.php b/includes/AbuseFilterHooks.php index 1692bac6c..253dfa9ee 100644 --- a/includes/AbuseFilterHooks.php +++ b/includes/AbuseFilterHooks.php @@ -151,7 +151,7 @@ class AbuseFilterHooks { $vars = new AbuseFilterVariableHolder(); $vars->addHolders( AbuseFilter::generateUserVars( $user ), - AbuseFilter::generateTitleVars( $title, 'ARTICLE' ) + AbuseFilter::generateTitleVars( $title, 'PAGE' ) ); $vars->setVar( 'action', 'edit' ); $vars->setVar( 'summary', $summary ); @@ -241,7 +241,7 @@ class AbuseFilterHooks { /** @var AbuseFilterVariableHolder|bool $vars */ $vars = self::$successful_action_vars; - if ( $vars->getVar( 'article_prefixedtext' )->toString() !== + if ( $vars->getVar( 'page_prefixedtitle' )->toString() !== $wikiPage->getTitle()->getPrefixedText() ) { return; @@ -356,7 +356,7 @@ class AbuseFilterHooks { $vars->addHolders( AbuseFilter::generateUserVars( $user ), - AbuseFilter::generateTitleVars( $article->getTitle(), 'ARTICLE' ) + AbuseFilter::generateTitleVars( $article->getTitle(), 'PAGE' ) ); $vars->setVar( 'SUMMARY', $reason ); diff --git a/includes/AbuseFilterVariableHolder.php b/includes/AbuseFilterVariableHolder.php index b0a648f5c..715859801 100644 --- a/includes/AbuseFilterVariableHolder.php +++ b/includes/AbuseFilterVariableHolder.php @@ -144,14 +144,16 @@ class AbuseFilterVariableHolder { $coreVariables = AbuseFilter::getBuilderValues(); $coreVariables = array_keys( $coreVariables['vars'] ); + $deprecatedVariables = array_keys( AbuseFilter::getDeprecatedVariables() ); + $coreVariables = array_merge( $coreVariables, $deprecatedVariables ); // Title vars can have several prefixes - $prefixes = [ 'ARTICLE', 'MOVED_FROM', 'MOVED_TO' ]; + $prefixes = [ 'MOVED_FROM', 'MOVED_TO', 'PAGE' ]; $titleVars = [ - '_ARTICLEID', + '_ID', '_NAMESPACE', - '_TEXT', - '_PREFIXEDTEXT', + '_TITLE', + '_PREFIXEDTITLE', '_recent_contributors' ]; foreach ( $wgRestrictionTypes as $action ) { diff --git a/includes/parser/AbuseFilterParser.php b/includes/parser/AbuseFilterParser.php index bf7cbaa23..669fa558b 100644 --- a/includes/parser/AbuseFilterParser.php +++ b/includes/parser/AbuseFilterParser.php @@ -843,6 +843,11 @@ class AbuseFilterParser { protected function getVarValue( $var ) { $var = strtolower( $var ); $builderValues = AbuseFilter::getBuilderValues(); + $deprecatedVars = AbuseFilter::getDeprecatedVariables(); + if ( array_key_exists( $var, $deprecatedVars ) ) { + wfDebug( "AbuseFilter: deprecated variable $var used." ); + $var = $deprecatedVars[$var]; + } if ( !( array_key_exists( $var, $builderValues['vars'] ) || $this->mVars->varIsSet( $var ) ) ) { @@ -867,9 +872,11 @@ class AbuseFilterParser { */ protected function setUserVariable( $name, $value ) { $builderValues = AbuseFilter::getBuilderValues(); + $deprecatedVars = AbuseFilter::getDeprecatedVariables(); $blacklistedValues = AbuseFilterVariableHolder::$varBlacklist; if ( array_key_exists( $name, $builderValues['vars'] ) || array_key_exists( $name, AbuseFilter::$disabledVars ) || + array_key_exists( $name, $deprecatedVars ) || in_array( $name, $blacklistedValues ) ) { throw new AFPUserVisibleException( 'overridebuiltin', $this->mCur->pos, [ $name ] ); } diff --git a/tests/phpunit/AbuseFilterConsequencesTest.php b/tests/phpunit/AbuseFilterConsequencesTest.php index 3f6cd362f..a0c26b19f 100644 --- a/tests/phpunit/AbuseFilterConsequencesTest.php +++ b/tests/phpunit/AbuseFilterConsequencesTest.php @@ -79,7 +79,7 @@ class AbuseFilterConsequencesTest extends MediaWikiTestCase { ], 2 => [ 'af_id' => 2, - 'af_pattern' => 'action = "move" & moved_to_text contains "test"', + 'af_pattern' => 'action = "move" & moved_to_title contains "test"', 'af_enabled' => 1, 'af_comments' => 'No comment', 'af_public_comments' => 'Mock filter for move', @@ -99,7 +99,7 @@ class AbuseFilterConsequencesTest extends MediaWikiTestCase { ], 3 => [ 'af_id' => 3, - 'af_pattern' => 'action = "delete" & "test" in lcase(article_prefixedtext)', + 'af_pattern' => 'action = "delete" & "test" in lcase(page_prefixedtitle)', 'af_enabled' => 1, 'af_comments' => '', 'af_public_comments' => 'Mock filter for delete', @@ -257,7 +257,7 @@ class AbuseFilterConsequencesTest extends MediaWikiTestCase { ], 12 => [ 'af_id' => 12, - 'af_pattern' => 'article_text == user_name', + 'af_pattern' => 'page_title == user_name', 'af_enabled' => 1, 'af_comments' => '', 'af_public_comments' => 'Mock filter for userpage', diff --git a/tests/phpunit/AbuseFilterTest.php b/tests/phpunit/AbuseFilterTest.php index d9814244c..779802788 100644 --- a/tests/phpunit/AbuseFilterTest.php +++ b/tests/phpunit/AbuseFilterTest.php @@ -279,16 +279,16 @@ class AbuseFilterTest extends MediaWikiTestCase { } $success = true; switch ( $suffix ) { - case '_articleid': + case '_id': $result = self::$mTitle->getArticleID(); break; case '_namespace': $result = self::$mTitle->getNamespace(); break; - case '_text': + case '_title': $result = self::$mTitle->getText(); break; - case '_prefixedtext': + case '_prefixedtitle': $result = self::$mTitle->getPrefixedText(); break; case '_restrictions_create': @@ -423,24 +423,24 @@ class AbuseFilterTest extends MediaWikiTestCase { */ public function provideTitleVars() { return [ - [ 'article', '_articleid' ], - [ 'article', '_namespace' ], - [ 'article', '_text' ], - [ 'article', '_prefixedtext' ], - [ 'article', '_restrictions_create' ], - [ 'article', '_restrictions_create', 'restricted' ], - [ 'article', '_restrictions_edit' ], - [ 'article', '_restrictions_edit', 'restricted' ], - [ 'article', '_restrictions_move' ], - [ 'article', '_restrictions_move', 'restricted' ], - [ 'article', '_restrictions_upload' ], - [ 'article', '_restrictions_upload', 'restricted' ], - [ 'article', '_first_contributor' ], - [ 'article', '_recent_contributors' ], - [ 'moved_from', '_articleid' ], + [ 'page', '_id' ], + [ 'page', '_namespace' ], + [ 'page', '_title' ], + [ 'page', '_prefixedtitle' ], + [ 'page', '_restrictions_create' ], + [ 'page', '_restrictions_create', 'restricted' ], + [ 'page', '_restrictions_edit' ], + [ 'page', '_restrictions_edit', 'restricted' ], + [ 'page', '_restrictions_move' ], + [ 'page', '_restrictions_move', 'restricted' ], + [ 'page', '_restrictions_upload' ], + [ 'page', '_restrictions_upload', 'restricted' ], + [ 'page', '_first_contributor' ], + [ 'page', '_recent_contributors' ], + [ 'moved_from', '_id' ], [ 'moved_from', '_namespace' ], - [ 'moved_from', '_text' ], - [ 'moved_from', '_prefixedtext' ], + [ 'moved_from', '_title' ], + [ 'moved_from', '_prefixedtitle' ], [ 'moved_from', '_restrictions_create' ], [ 'moved_from', '_restrictions_create', 'restricted' ], [ 'moved_from', '_restrictions_edit' ], @@ -451,10 +451,10 @@ class AbuseFilterTest extends MediaWikiTestCase { [ 'moved_from', '_restrictions_upload', 'restricted' ], [ 'moved_from', '_first_contributor' ], [ 'moved_from', '_recent_contributors' ], - [ 'moved_to', '_articleid' ], + [ 'moved_to', '_id' ], [ 'moved_to', '_namespace' ], - [ 'moved_to', '_text' ], - [ 'moved_to', '_prefixedtext' ], + [ 'moved_to', '_title' ], + [ 'moved_to', '_prefixedtitle' ], [ 'moved_to', '_restrictions_create' ], [ 'moved_to', '_restrictions_create', 'restricted' ], [ 'moved_to', '_restrictions_edit' ],