Special page PageLanguage to set the page language of a page.
To enable the feature, set $wgPageLanguageUseDB to true
and assign the 'pagelang' user right to a user group.
Bug: 35489
Change-Id: I0f82b146fbe948f917c1c5d29f7469644d797e80
for Template:! for purposes such as passing pipes inside of parameters.
* (bug 20790) The block log snippet on Special:Contributions and while
editing user and user talk pages now works for IP range blocks.
+* (bug 35489) Added ability to change the page language for MediaWiki pages using
+ Special:PageLanguage. All pages are set to wiki language by default.
+ The feature needs to be enabled with $wgPageLanguageUseDB=true and
+ permission needs to be set for 'pagelang'.
=== Bug fixes in 1.24 ===
* (bug 49116) Footer copyright notice is now always displayed in user language
'ManualLogEntry' => 'includes/logging/LogEntry.php',
'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php',
'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php',
+ 'PageLangLogFormatter' => 'includes/logging/PageLangLogFormatter.php',
'PatrolLog' => 'includes/logging/PatrolLog.php',
'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php',
'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
'SpecialMyuploads' => 'includes/specials/SpecialMyRedirectPages.php',
'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php',
'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
+ 'SpecialPageLanguage' => 'includes/specials/SpecialPageLanguage.php',
'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php',
'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php',
'SpecialPermanentLink' => 'includes/specials/SpecialPermanentLink.php',
$wgGroupPermissions['sysop']['movefile'] = true;
$wgGroupPermissions['sysop']['unblockself'] = true;
$wgGroupPermissions['sysop']['suppressredirect'] = true;
+#$wgGroupPermissions['sysop']['pagelang'] = true;
#$wgGroupPermissions['sysop']['upload_by_url'] = true;
#$wgGroupPermissions['sysop']['mergehistory'] = true;
$wgHKDFSecret = false;
$wgHKDFAlgorithm = 'sha256';
+/**
+ * Enable page language feature
+ * Allows setting page language in database
+ * @var bool
+ * @since 1.24
+ */
+$wgPageLanguageUseDB = false;
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
$wgLogActionsHandlers['newusers/autocreate'] = 'NewUsersLogFormatter';
}
+if ( $wgPageLanguageUseDB ) {
+ $wgLogTypes[] = 'pagelang';
+ $wgLogActionsHandlers['pagelang/pagelang'] = 'PageLangLogFormatter';
+}
+
if ( $wgCookieSecure === 'detect' ) {
$wgCookieSecure = ( WebRequest::detectProtocol() === 'https' );
}
/** @var bool The (string) language code of the page's language and content code. */
private $mPageLanguage = false;
+ /** @var string The page language code from the database */
+ private $mDbPageLanguage = null;
+
/** @var TitleValue A corresponding TitleValue object */
private $mTitleValue = null;
// @}
} else {
$this->mContentModel = false; # initialized lazily in getContentModel()
}
+ if ( isset( $row->page_lang ) ) {
+ $this->mDbPageLanguage = (string)$row->page_lang;
+ }
} else { // page not found
$this->mArticleID = 0;
$this->mLength = 0;
$this->mContentModel = false;
$this->mEstimateRevisions = null;
$this->mPageLanguage = false;
+ $this->mDbPageLanguage = null;
}
/**
return $wgLang;
}
+ // Checking if DB language is set
+ if ( $this->mDbPageLanguage ) {
+ wfProfileOut( __METHOD__ );
+ return wfGetLangObj( $this->mDbPageLanguage );
+ }
+
if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
// Note that this may depend on user settings, so the cache should
// be only per-request.
} else {
$langObj = wfGetLangObj( $this->mPageLanguage[0] );
}
+
wfProfileOut( __METHOD__ );
return $langObj;
}
'nominornewtalk',
'noratelimit',
'override-export-depth',
+ 'pagelang',
'passwordreset',
'patrol',
'patrolmarks',
*/
protected function pageInfo() {
global $wgContLang, $wgRCMaxAge, $wgMemc, $wgMiserMode,
- $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit;
+ $wgUnwatchedPageThreshold, $wgPageInfoTransclusionLimit, $wgPageLanguageUseDB;
$user = $this->getUser();
$lang = $this->getLanguage();
// Language in which the page content is (supposed to be) written
$pageLang = $title->getPageLanguage()->getCode();
- $pageInfo['header-basic'][] = array( $this->msg( 'pageinfo-language' ),
+
+ if ( $wgPageLanguageUseDB && $this->getTitle()->userCan( 'pagelang' ) ) {
+ // Link to Special:PageLanguage with pre-filled page title if user has permissions
+ $titleObj = SpecialPage::getTitleFor( 'PageLanguage', $title->getPrefixedText() );
+ $langDisp = Linker::link(
+ $titleObj,
+ $this->msg( 'pageinfo-language' )->escaped()
+ );
+ } else {
+ // Display just the message
+ $langDisp = $this->msg( 'pageinfo-language' )->escaped();
+ }
+
+ $pageInfo['header-basic'][] = array( $langDisp,
Language::fetchLanguageName( $pageLang, $lang->getCode() )
. ' ' . $this->msg( 'parentheses', $pageLang ) );
array( 'addField', 'mwuser', 'user_password_expires', 'patch-user_password_expires.sql' ),
// 1.24
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql'),
);
}
}
array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp', 'patch-watchlist-user-notificationtimestamp-index.sql' ),
+ array( 'addField', 'page', 'page_lang', 'patch-page_lang.sql' ),
);
}
array( 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ),
// 1.24
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
array( 'addPgField', 'page_props', 'pp_sortkey', 'float NULL' ),
array( 'addPgIndex', 'page_props', 'pp_propname_sortkey_page',
'( pp_propname, pp_sortkey, pp_page ) WHERE ( pp_sortkey IS NOT NULL )' ),
+ array( 'addPgField', 'page', 'page_lang', 'TEXT default NULL' ),
);
}
array( 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ),
array( 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ),
array( 'addIndex', 'watchlist', 'wl_user_notificationtimestamp', 'patch-watchlist-user-notificationtimestamp-index.sql' ),
+ array( 'addField', 'page', 'page_lang', 'patch-page-page_lang.sql' ),
);
}
--- /dev/null
+<?php
+/**
+ * Formatter for changelang log entries.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Kunal Grover
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.24
+ */
+
+/**
+ * This class formats language change log entries.
+ *
+ * @since 1.24
+ */
+class PageLangLogFormatter extends LogFormatter {
+ protected function getMessageParameters() {
+ // Get the user language for displaying language names
+ $userLang = $this->context->getLanguage()->getCode();
+ $params = parent::getMessageParameters();
+
+ // Get the language codes from log
+ $oldLang = $params[3];
+ $kOld = strrpos( $oldLang, '[' );
+ if ( $kOld ) {
+ $oldLang = substr( $oldLang, 0, $kOld );
+ }
+
+ $newLang = $params[4];
+ $kNew = strrpos( $newLang, '[' );
+ if ( $kNew ) {
+ $newLang = substr( $newLang, 0, $kNew );
+ }
+
+ // Convert language codes to names in user language
+ $logOld = Language::fetchLanguageName( $oldLang, $userLang )
+ . ' (' . $oldLang . ')';
+ $logNew = Language::fetchLanguageName( $newLang, $userLang )
+ . ' (' . $newLang . ')';
+
+ // Add the default message to languages if required
+ $params[3] = !$kOld ? $logOld : $logOld . ' [' . $this->msg( 'default' ) . ']';
+ $params[4] = !$kNew ? $logNew : $logNew . ' [' . $this->msg( 'default' ) . ']';
+ return $params;
+ }
+}
* @return array
*/
public static function selectFields() {
- global $wgContentHandlerUseDB;
+ global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
$fields = array(
'page_id',
$fields[] = 'page_content_model';
}
+ if ( $wgPageLanguageUseDB ) {
+ $fields[] = 'page_lang';
+ }
+
return $fields;
}
global $wgSpecialPages;
global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
global $wgEnableEmail, $wgEnableJavaScriptTest;
+ global $wgPageLanguageUseDB;
if ( !is_object( self::$list ) ) {
wfProfileIn( __METHOD__ );
self::$list['JavaScriptTest'] = 'SpecialJavaScriptTest';
}
+ if ( $wgPageLanguageUseDB ) {
+ self::$list['PageLanguage'] = 'SpecialPageLanguage';
+ }
+
self::$list['Activeusers'] = 'SpecialActiveUsers';
// Add extension special pages
--- /dev/null
+<?php
+/**
+ * Implements Special:PageLanguage
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @author Kunal Grover
+ * @since 1.24
+ */
+
+/**
+ * Special page for changing the content language of a page
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPageLanguage extends FormSpecialPage {
+ /**
+ * @var $goToUrl URL to go to if language change successful
+ */
+ private $goToUrl;
+
+ public function __construct() {
+ parent::__construct( 'PageLanguage', 'pagelang' );
+ }
+
+ protected function preText() {
+ $this->getOutput()->addModules( 'mediawiki.special.pageLanguage' );
+ }
+
+ protected function getFormFields() {
+ global $wgLanguageCode;
+ // Get default from the subpage of Special page
+ $defaultName = $this->par;
+
+ $page = array();
+ $page['pagename'] = array(
+ 'type' => 'text',
+ 'label-message' => 'pagelang-name',
+ 'default' => $defaultName,
+ );
+
+ // Options for whether to use the default language or select language
+ $selectoptions = array(
+ (string)$this->msg( 'pagelang-use-default' )->escaped() => 1,
+ (string)$this->msg( 'pagelang-select-lang' )->escaped() => 2,
+ );
+ $page['selectoptions'] = array(
+ 'id' => 'mw-pl-options',
+ 'type' => 'radio',
+ 'options' => $selectoptions,
+ 'default' => 1
+ );
+
+ // Building a language selector
+ $userLang = $this->getLanguage()->getCode();
+ $languages = Language::fetchLanguageNames( $userLang, 'mwfile' );
+ ksort( $languages );
+ $options = array();
+ foreach ( $languages as $code => $name ) {
+ $options["$code - $name"] = $code;
+ }
+
+ $page['language'] = array(
+ 'id' => 'mw-pl-languageselector',
+ 'type' => 'select',
+ 'options' => $options,
+ 'label-message' => 'pagelang-language',
+ 'default' => $wgLanguageCode
+ );
+
+ return $page;
+ }
+
+ public function alterForm( HTMLForm $form ) {
+ $form->setDisplayFormat( 'vform' );
+ $form->setWrapperLegend( false );
+ }
+
+ /**
+ *
+ * @param array $data
+ */
+ public function onSubmit( array $data ) {
+ $title = Title::newFromText( $data['pagename'] );
+
+ // Check if title is valid
+ if ( !$title ) {
+ return false;
+ }
+
+ // Get the default language for the wiki
+ // Returns the default since the page is not loaded from DB
+ $defLang = $title->getPageLanguage()->getCode();
+
+ $pageId = $title->getArticleID();
+
+ // Check if article exists
+ if ( !$pageId ) {
+ return false;
+ }
+
+ // Load the page language from DB
+ $dbw = wfGetDB( DB_MASTER );
+ $langOld = $dbw->selectField(
+ 'page',
+ 'page_lang',
+ array( 'page_id' => $pageId ),
+ __METHOD__
+ );
+
+ // Url to redirect to after the operation
+ $this->goToUrl = $title->getFullURL();
+
+ // Check if user wants to use default language
+ if ( $data['selectoptions'] == 1 ) {
+ $langNew = null;
+ } else {
+ $langNew = $data['language'];
+ }
+
+ // No change in language
+ if ( $langNew === $langOld ) {
+ return false;
+ }
+
+ // Hardcoded [def] if the language is set to null
+ $logOld = $langOld ? $langOld : $defLang . '[def]';
+ $logNew = $langNew ? $langNew : $defLang . '[def]';
+
+ // Writing new page language to database
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'page',
+ array( 'page_lang' => $langNew ),
+ array(
+ 'page_id' => $pageId,
+ 'page_lang' => $langOld
+ ),
+ __METHOD__
+ );
+
+ if ( !$dbw->affectedRows() ) {
+ return false;
+ }
+
+ // Logging change of language
+ $logParams = array(
+ '4::oldlanguage' => $logOld,
+ '5::newlanguage' => $logNew
+ );
+ $entry = new ManualLogEntry( 'pagelang', 'pagelang' );
+ $entry->setPerformer( $this->getUser() );
+ $entry->setTarget( $title );
+ $entry->setParameters( $logParams );
+
+ $logid = $entry->insert();
+ $entry->publish( $logid );
+
+ return true;
+ }
+
+ public function onSuccess() {
+ // Success causes a redirect
+ $this->getOutput()->redirect( $this->goToUrl );
+ }
+}
"expand_templates_remove_nowiki": "Suppress <nowiki> tags in result",
"expand_templates_generate_xml": "Show XML parse tree",
"expand_templates_generate_rawhtml": "Show raw HTML",
- "expand_templates_preview": "Preview"
+ "expand_templates_preview": "Preview",
+ "pagelanguage": "Page language selector",
+ "pagelang-name": "Page",
+ "pagelang-language": "Language",
+ "pagelang-use-default": "Use default language",
+ "pagelang-select-lang": "Select language",
+ "right-pagelang": "Change page language",
+ "action-pagelang": "change the page language",
+ "log-name-pagelang": "Change language log",
+ "log-description-pagelang": "This is a log of changes in page languages.",
+ "logentry-pagelang-pagelang": "$1 {{GENDER:$2|changed}} page language for $3 from $4 to $5."
}
"expand_templates_remove_nowiki": "Option on [[Special:Expandtemplates]]\n\nHere's what the tick box does:\n\nFor:\n<pre>\n<nowiki>test</nowiki>\n</pre>\n\nNot ticked:\n<pre>\n<nowiki>test</nowiki>\n</pre>\n\nTicked:\n<pre>\ntest\n</pre>",
"expand_templates_generate_xml": "Used as checkbox label.",
"expand_templates_generate_rawhtml": "Used as checkbox label.",
- "expand_templates_preview": "{{Identical|Preview}}"
+ "expand_templates_preview": "{{Identical|Preview}}",
+ "pagelanguage": "Title for page Special:PageLanguage",
+ "pagelang-name": "Input label for page name on Special:PageLanguage",
+ "pagelang-language": "Language selector label for Special:PageLanguage",
+ "pagelang-use-default": "Radio label for selector on Special:PageLanguage for default language",
+ "pagelang-select-lang": "Radio label for selector on Special:PageLanguage for language selection",
+ "right-pagelang": "{{Doc-right|pagelang}}\nRight to change page language on Special:PageLanguage",
+ "action-pagelang": "{{Doc-action|pagelang}}",
+ "log-name-pagelang": "Display entry for log name for changes in page language in Special:Log.",
+ "log-description-pagelang": "Display description for log name for changes in page language in Special:Log.",
+ "logentry-pagelang-pagelang": "Log entry for page language changes in Special:Log."
}
'Newimages' => array( 'NewFiles', 'NewImages' ),
'Newpages' => array( 'NewPages' ),
'PagesWithProp' => array( 'PagesWithProp', 'Pageswithprop', 'PagesByProp', 'Pagesbyprop' ),
+ 'PageLanguage' => array( 'PageLanguage' ),
'PasswordReset' => array( 'PasswordReset' ),
'PermanentLink' => array( 'PermanentLink', 'PermaLink' ),
'Popularpages' => array( 'PopularPages' ),
--- /dev/null
+ALTER TABLE /*$wgDBprefix*/page
+ ADD page_lang varbinary(35) DEFAULT NULL;
--- /dev/null
+ALTER TABLE /*_*/page ADD page_lang VARBINARY(35) DEFAULT NULL
page_links_updated varchar(14) DEFAULT NULL,
page_latest INT, -- FK inserted later
page_len INT NOT NULL,
- page_content_model nvarchar(32) default null
+ page_content_model nvarchar(32) default null,
+ page_lang VARBINARY(35) DEFAULT NULL
);
CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.page ADD page_lang VARCHAR2(35);
page_links_updated TIMESTAMP(6) WITH TIME ZONE,
page_latest NUMBER DEFAULT 0 NOT NULL, -- FK?
page_len NUMBER DEFAULT 0 NOT NULL,
- page_content_model VARCHAR2(32)
+ page_content_model VARCHAR2(32),
+ page_lang VARCHAR2(35) DEFAULT NULL
);
ALTER TABLE &mw_prefix.page ADD CONSTRAINT &mw_prefix.page_pk PRIMARY KEY (page_id);
CREATE UNIQUE INDEX &mw_prefix.page_u01 ON &mw_prefix.page (page_namespace,page_title);
page_links_updated TIMESTAMPTZ NULL,
page_latest INTEGER NOT NULL, -- FK?
page_len INTEGER NOT NULL,
- page_content_model TEXT
+ page_content_model TEXT,
+ page_lang TEXT DEFAULT NULL
);
CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
CREATE INDEX page_main_title ON page (page_title text_pattern_ops) WHERE page_namespace = 0;
--- /dev/null
+-- Add page_lang column
+
+ALTER TABLE /*$wgDBprefix*/page ADD COLUMN page_lang TEXT default NULL;
page_len int unsigned NOT NULL,
-- content model, see CONTENT_MODEL_XXX constants
- page_content_model varbinary(32) DEFAULT NULL
+ page_content_model varbinary(32) DEFAULT NULL,
+
+ -- Page content language
+ page_lang varbinary(35) DEFAULT NULL
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.movePage.js',
'dependencies' => 'jquery.byteLimit',
),
+ 'mediawiki.special.pageLanguage' => array(
+ 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.pageLanguage.js',
+ ),
'mediawiki.special.pagesWithProp' => array(
'styles' => 'resources/src/mediawiki.special/mediawiki.special.pagesWithProp.css',
),
--- /dev/null
+( function ( $ ) {
+ $( document ).ready( function () {
+
+ // Select the 'Language select' option if user is trying to select language
+ $( '#mw-pl-languageselector' ).on( 'click', function () {
+ $( '#mw-pl-options-2' ).prop( 'checked', true );
+ } );
+ } );
+} ( jQuery ) );