From 7816c5462531bfb20ab977b430081ab5ccf04755 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 21 Jun 2017 11:22:05 +0200 Subject: [PATCH] Allow to force a language and set it via the ocs api Signed-off-by: Joas Schilling --- .../lib/Controller/UsersController.php | 13 ++ .../tests/Controller/UsersControllerTest.php | 34 +++-- config/config.sample.php | 11 ++ lib/private/L10N/Factory.php | 8 +- settings/Controller/PersonalController.php | 87 ------------- settings/js/personal.js | 38 +++--- settings/personal.php | 109 ++++++++-------- settings/routes.php | 1 - settings/templates/personal.php | 2 + .../Controller/PersonalControllerTest.php | 122 ------------------ 10 files changed, 139 insertions(+), 286 deletions(-) delete mode 100644 settings/Controller/PersonalController.php delete mode 100644 tests/Settings/Controller/PersonalControllerTest.php diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 132727eecbd..25cc13d1c32 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -286,6 +286,7 @@ class UsersController extends OCSController { $data[AccountManager::PROPERTY_WEBSITE] = $userAccount[AccountManager::PROPERTY_WEBSITE]['value']; $data[AccountManager::PROPERTY_TWITTER] = $userAccount[AccountManager::PROPERTY_TWITTER]['value']; $data['groups'] = $gids; + $data['language'] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'lang'); return $data; } @@ -322,6 +323,10 @@ class UsersController extends OCSController { } $permittedFields[] = 'password'; + if ($this->config->getSystemValue('force_language', false) === false || + $this->groupManager->isAdmin($currentLoggedInUser->getUID())) { + $permittedFields[] = 'language'; + } if ($this->appManager->isEnabledForUser('federatedfilesharing')) { $federatedFileSharing = new \OCA\FederatedFileSharing\AppInfo\Application(); @@ -348,6 +353,7 @@ class UsersController extends OCSController { $permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME; $permittedFields[] = AccountManager::PROPERTY_EMAIL; $permittedFields[] = 'password'; + $permittedFields[] = 'language'; $permittedFields[] = AccountManager::PROPERTY_PHONE; $permittedFields[] = AccountManager::PROPERTY_ADDRESS; $permittedFields[] = AccountManager::PROPERTY_WEBSITE; @@ -392,6 +398,13 @@ class UsersController extends OCSController { case 'password': $targetUser->setPassword($value); break; + case 'language': + $languagesCodes = $this->l10nFactory->findAvailableLanguages(); + if (!in_array($value, $languagesCodes, true) && $value !== 'en') { + throw new OCSException('Invalid language', 102); + } + $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value); + break; case AccountManager::PROPERTY_EMAIL: if(filter_var($value, FILTER_VALIDATE_EMAIL)) { $targetUser->setEMailAddress($value); diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 61205b45900..03c9809009b 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -699,6 +699,11 @@ class UsersControllerTest extends TestCase { ->method('getUserValue') ->with('UID', 'core', 'enabled', 'true') ->will($this->returnValue('true')); + $this->config + ->expects($this->at(1)) + ->method('getUserValue') + ->with('UID', 'core', 'lang') + ->will($this->returnValue('de')); $this->api ->expects($this->once()) ->method('fillStorageInfo') @@ -709,7 +714,7 @@ class UsersControllerTest extends TestCase { ->method('getDisplayName') ->will($this->returnValue('Demo User')); $targetUser - ->expects($this->exactly(3)) + ->expects($this->exactly(4)) ->method('getUID') ->will($this->returnValue('UID')); @@ -723,7 +728,8 @@ class UsersControllerTest extends TestCase { 'address' => 'address', 'website' => 'website', 'twitter' => 'twitter', - 'groups' => ['group0', 'group1', 'group2'] + 'groups' => ['group0', 'group1', 'group2'], + 'language' => 'de', ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UserToGet'])); } @@ -778,6 +784,11 @@ class UsersControllerTest extends TestCase { ->method('getUserValue') ->with('UID', 'core', 'enabled', 'true') ->will($this->returnValue('true')); + $this->config + ->expects($this->at(1)) + ->method('getUserValue') + ->with('UID', 'core', 'lang') + ->will($this->returnValue('da')); $this->api ->expects($this->once()) ->method('fillStorageInfo') @@ -788,7 +799,7 @@ class UsersControllerTest extends TestCase { ->method('getDisplayName') ->will($this->returnValue('Demo User')); $targetUser - ->expects($this->exactly(3)) + ->expects($this->exactly(4)) ->method('getUID') ->will($this->returnValue('UID')); $this->accountManager->expects($this->any())->method('getUser') @@ -812,7 +823,8 @@ class UsersControllerTest extends TestCase { 'address' => 'address', 'website' => 'website', 'twitter' => 'twitter', - 'groups' => [] + 'groups' => [], + 'language' => 'da', ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UserToGet'])); } @@ -918,9 +930,14 @@ class UsersControllerTest extends TestCase { ->method('getEMailAddress') ->will($this->returnValue('subadmin@owncloud.org')); $targetUser - ->expects($this->exactly(3)) + ->expects($this->exactly(4)) ->method('getUID') ->will($this->returnValue('UID')); + $this->config + ->expects($this->at(0)) + ->method('getUserValue') + ->with('UID', 'core', 'lang') + ->will($this->returnValue('ru')); $this->accountManager->expects($this->any())->method('getUser') ->with($targetUser) ->willReturn( @@ -941,7 +958,8 @@ class UsersControllerTest extends TestCase { 'address' => 'address', 'website' => 'website', 'twitter' => 'twitter', - 'groups' => [] + 'groups' => [], + 'language' => 'ru', ]; $this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['subadmin'])); } @@ -1128,7 +1146,7 @@ class UsersControllerTest extends TestCase { ->with('UserToEdit') ->will($this->returnValue($targetUser)); $this->groupManager - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('isAdmin') ->with('UID') ->will($this->returnValue(true)); @@ -1163,7 +1181,7 @@ class UsersControllerTest extends TestCase { ->with('UserToEdit') ->will($this->returnValue($targetUser)); $this->groupManager - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('isAdmin') ->with('UID') ->will($this->returnValue(true)); diff --git a/config/config.sample.php b/config/config.sample.php index 9cff1a4b876..3d1ccb8c2c1 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -163,6 +163,17 @@ $CONFIG = array( */ 'default_language' => 'en', +/** + * With this setting a language can be forced for all users. If a language is + * forced, the users are also unable to change their language in the personal + * settings. If users shall be unable to change their language, but users have + * different languages, this value can be set to ``true`` instead of a language + * code. + * + * Defaults to ``false`` + */ +'force_language' => 'en', + /** * Set the default app to open on login. Use the app names as they appear in the * URL after clicking them in the Apps menu, such as documents, calendar, and diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index 581ca3fb5d1..399bebb8189 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -99,6 +99,12 @@ class Factory implements IFactory { if ($lang !== null) { $lang = str_replace(array('\0', '/', '\\', '..'), '', (string) $lang); } + + $forceLang = $this->config->getSystemValue('force_language', false); + if (is_string($forceLang)) { + $lang = $forceLang; + } + if ($lang === null || !$this->languageExists($app, $lang)) { $lang = $this->findLanguage($app); } @@ -125,7 +131,7 @@ class Factory implements IFactory { } /** - * At this point ownCloud might not yet be installed and thus the lookup + * At this point Nextcloud might not yet be installed and thus the lookup * in the preferences table might fail. For this reason we need to check * whether the instance has already been installed * diff --git a/settings/Controller/PersonalController.php b/settings/Controller/PersonalController.php deleted file mode 100644 index 696c0a5c414..00000000000 --- a/settings/Controller/PersonalController.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * @author Roeland Jago Douma - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -namespace OC\Settings\Controller; - -use OCP\AppFramework\Controller; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\IConfig; -use OCP\IL10N; -use OCP\IRequest; -use OCP\L10N\IFactory; - -class PersonalController extends Controller { - /** @var IFactory */ - private $l10nFactory; - - /** @var string */ - private $userId; - - /** @var IConfig */ - private $config; - - /** @var IL10N */ - private $l; - - /** - * PersonalController constructor. - * - * @param string $appName - * @param IRequest $request - * @param IFactory $l10nFactory - * @param $userId - * @param IConfig $config - * @param IL10N $l - */ - public function __construct($appName, - IRequest $request, - IFactory $l10nFactory, - $userId, - IConfig $config, - IL10N $l) { - parent::__construct($appName, $request); - - $this->l10nFactory = $l10nFactory; - $this->userId = $userId; - $this->config = $config; - $this->l = $l; - } - - /** - * @NoAdminRequired - * @NoSubadminRequired - * @param string $lang - * @return JSONResponse - */ - public function setLanguage($lang) { - if ($lang !== '') { - $languagesCodes = $this->l10nFactory->findAvailableLanguages(); - if (array_search($lang, $languagesCodes) || $lang === 'en') { - $this->config->setUserValue($this->userId, 'core', 'lang', $lang); - return new JSONResponse([]); - } - } - - return new JSONResponse(['message' => $this->l->t('Invalid request')], Http::STATUS_BAD_REQUEST); - } -} diff --git a/settings/js/personal.js b/settings/js/personal.js index 254ee8f415b..effce9de07e 100644 --- a/settings/js/personal.js +++ b/settings/js/personal.js @@ -258,23 +258,31 @@ $(document).ready(function () { }); federationSettingsView.render(); - $("#languageinput").change(function () { - // Serialize the data - var post = $("#languageinput").serialize(); - // Ajax foo - $.ajax( - 'ajax/setlanguage.php', - { - method: 'POST', - data: post + var updateLanguage = function () { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(updateLanguage); + return; + } + + var selectedLang = $("#languageinput").val(), + user = OC.getCurrentUser(); + + $.ajax({ + url: OC.linkToOCS('cloud/users', 2) + user['uid'], + method: 'PUT', + data: { + key: 'language', + value: selectedLang + }, + success: function() { + location.reload(); + }, + fail: function() { + OC.Notification.showTemporary(t('settings', 'An error occured while changing your language. Please reload the page and try again.')); } - ).done(function() { - location.reload(); - }).fail(function(jqXHR) { - $('#passworderror').text(jqXHR.responseJSON.message); }); - return false; - }); + }; + $("#languageinput").change(updateLanguage); var uploadparms = { pasteZone: null, diff --git a/settings/personal.php b/settings/personal.php index fefd8392a10..fcccbc50556 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -73,62 +73,65 @@ $storageInfo=OC_Helper::getStorageInfo('/'); $user = OC::$server->getUserManager()->get(OC_User::getUser()); -$userLang=$config->getUserValue( OC_User::getUser(), 'core', 'lang', \OC::$server->getL10NFactory()->findLanguage() ); -$languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages(); +$forceLanguage = $config->getSystemValue('force_language', false); +if ($forceLanguage === false) { + $userLang=$config->getUserValue( OC_User::getUser(), 'core', 'lang', \OC::$server->getL10NFactory()->findLanguage() ); + $languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages(); + + // array of common languages + $commonLangCodes = array( + 'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it', 'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko' + ); -// array of common languages -$commonLangCodes = array( - 'en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it', 'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko' -); + $languages=array(); + $commonLanguages = array(); + foreach($languageCodes as $lang) { + $l = \OC::$server->getL10N('settings', $lang); + // TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version + $potentialName = (string) $l->t('__language_name__'); + if($l->getLanguageCode() === $lang && substr($potentialName, 0, 1) !== '_') {//first check if the language name is in the translation file + $ln = array('code' => $lang, 'name' => $potentialName); + } elseif ($lang === 'en') { + $ln = ['code' => $lang, 'name' => 'English (US)']; + }else{//fallback to language code + $ln=array('code'=>$lang, 'name'=>$lang); + } -$languages=array(); -$commonLanguages = array(); -foreach($languageCodes as $lang) { - $l = \OC::$server->getL10N('settings', $lang); - // TRANSLATORS this is the language name for the language switcher in the personal settings and should be the localized version - $potentialName = (string) $l->t('__language_name__'); - if($l->getLanguageCode() === $lang && substr($potentialName, 0, 1) !== '_') {//first check if the language name is in the translation file - $ln = array('code' => $lang, 'name' => $potentialName); - } elseif ($lang === 'en') { - $ln = ['code' => $lang, 'name' => 'English (US)']; - }else{//fallback to language code - $ln=array('code'=>$lang, 'name'=>$lang); + // put appropriate languages into appropriate arrays, to print them sorted + // used language -> common languages -> divider -> other languages + if ($lang === $userLang) { + $userLang = $ln; + } elseif (in_array($lang, $commonLangCodes)) { + $commonLanguages[array_search($lang, $commonLangCodes)]=$ln; + } else { + $languages[]=$ln; + } } - // put appropriate languages into appropriate arrays, to print them sorted - // used language -> common languages -> divider -> other languages - if ($lang === $userLang) { - $userLang = $ln; - } elseif (in_array($lang, $commonLangCodes)) { - $commonLanguages[array_search($lang, $commonLangCodes)]=$ln; - } else { - $languages[]=$ln; + // if user language is not available but set somehow: show the actual code as name + if (!is_array($userLang)) { + $userLang = [ + 'code' => $userLang, + 'name' => $userLang, + ]; } -} - -// if user language is not available but set somehow: show the actual code as name -if (!is_array($userLang)) { - $userLang = [ - 'code' => $userLang, - 'name' => $userLang, - ]; -} -ksort($commonLanguages); + ksort($commonLanguages); -// sort now by displayed language not the iso-code -usort( $languages, function ($a, $b) { - if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) { - // If a doesn't have a name, but b does, list b before a - return 1; - } - if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) { - // If a does have a name, but b doesn't, list a before b - return -1; - } - // Otherwise compare the names - return strcmp($a['name'], $b['name']); -}); + // sort now by displayed language not the iso-code + usort( $languages, function ($a, $b) { + if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) { + // If a doesn't have a name, but b does, list b before a + return 1; + } + if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) { + // If a does have a name, but b doesn't, list a before b + return -1; + } + // Otherwise compare the names + return strcmp($a['name'], $b['name']); + }); +} //links to clients $clients = array( @@ -165,9 +168,11 @@ $tmpl->assign('usage_relative', $storageInfo['relative']); $tmpl->assign('quota', $storageInfo['quota']); $tmpl->assign('clients', $clients); $tmpl->assign('email', $userData[\OC\Accounts\AccountManager::PROPERTY_EMAIL]['value']); -$tmpl->assign('languages', $languages); -$tmpl->assign('commonlanguages', $commonLanguages); -$tmpl->assign('activelanguage', $userLang); +if ($forceLanguage === false) { + $tmpl->assign('languages', $languages); + $tmpl->assign('commonlanguages', $commonLanguages); + $tmpl->assign('activelanguage', $userLang); +} $tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser())); $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser())); $tmpl->assign('displayName', $userData[\OC\Accounts\AccountManager::PROPERTY_DISPLAYNAME]['value']); diff --git a/settings/routes.php b/settings/routes.php index fb85b11f390..048febaa129 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -69,7 +69,6 @@ $application->registerRoutes($this, [ ['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET'], ['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST'], ['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST'], - ['name' => 'Personal#setLanguage', 'url' => '/settings/ajax/setlanguage.php', 'verb' => 'POST'], ['name' => 'Groups#index', 'url' => '/settings/users/groups', 'verb' => 'GET'], ['name' => 'Groups#show', 'url' => '/settings/users/groups/{id}', 'requirements' => ['id' => '[^?]*'], 'verb' => 'GET'], ['name' => 'Groups#create', 'url' => '/settings/users/groups', 'verb' => 'POST'], diff --git a/settings/templates/personal.php b/settings/templates/personal.php index dbc5b5611d5..7a7d87e930d 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -292,6 +292,7 @@ if($_['passwordChangeSupported']) { } ?> +

@@ -317,6 +318,7 @@ if($_['passwordChangeSupported']) { t('Help translate'));?> +
diff --git a/tests/Settings/Controller/PersonalControllerTest.php b/tests/Settings/Controller/PersonalControllerTest.php deleted file mode 100644 index a1b727629bf..00000000000 --- a/tests/Settings/Controller/PersonalControllerTest.php +++ /dev/null @@ -1,122 +0,0 @@ - - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ -namespace Test\Settings\Controller; - -use OC\Settings\Controller\PersonalController; -use OCP\AppFramework\Http; -use OCP\AppFramework\Http\JSONResponse; -use OCP\IConfig; -use OCP\IL10N; -use OCP\IRequest; -use OCP\L10N\IFactory; - -class PersonalControllerTest extends \Test\TestCase { - - /** @var IFactory|\PHPUnit_Framework_MockObject_MockObject */ - private $l10nFactory; - /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ - private $config; - /** @var PersonalController */ - private $controller; - /** @var IL10N */ - private $l; - - public function setUp() { - parent::setUp(); - - $this->l10nFactory = $this->createMock(IFactory::class); - $this->config = $this->createMock(IConfig::class); - $this->l = $this->createMock(IL10N::class); - - $this->l->method('t') - ->will($this->returnCallback(function ($text, $parameters = []) { - return vsprintf($text, $parameters); - })); - - $this->controller = new PersonalController( - 'settings', - $this->createMock(IRequest::class), - $this->l10nFactory, - 'user', - $this->config, - $this->l - ); - } - - public function testSetLanguage() { - $this->l10nFactory->method('findAvailableLanguages') - ->willReturn(['aa', 'bb', 'cc']); - $this->config->expects($this->once()) - ->method('setUserValue') - ->with( - $this->equalTo('user'), - $this->equalTo('core'), - $this->equalTo('lang'), - $this->equalTo('bb') - ); - - $resp = $this->controller->setLanguage('bb'); - $expected = new JSONResponse([]); - $this->assertEquals($expected, $resp); - } - - public function testSetLanguageEn() { - $this->l10nFactory->method('findAvailableLanguages') - ->willReturn(['aa', 'bb', 'cc']); - $this->config->expects($this->once()) - ->method('setUserValue') - ->with( - $this->equalTo('user'), - $this->equalTo('core'), - $this->equalTo('lang'), - $this->equalTo('en') - ); - - $resp = $this->controller->setLanguage('en'); - $expected = new JSONResponse([]); - $this->assertEquals($expected, $resp); - } - - public function testSetLanguageFails() { - $this->l10nFactory->method('findAvailableLanguages') - ->willReturn(['aa', 'bb', 'cc']); - $this->config->expects($this->never()) - ->method('setUserValue'); - - $resp = $this->controller->setLanguage('dd'); - $expected = new JSONResponse(['message' => 'Invalid request'], Http::STATUS_BAD_REQUEST); - $this->assertEquals($expected, $resp); - } - - - public function testSetLanguageEmpty() { - $this->l10nFactory->method('findAvailableLanguages') - ->willReturn(['aa', 'bb', 'cc']); - $this->config->expects($this->never()) - ->method('setUserValue'); - - $resp = $this->controller->setLanguage(''); - $expected = new JSONResponse(['message' => 'Invalid request'], Http::STATUS_BAD_REQUEST); - $this->assertEquals($expected, $resp); - } -}