Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>tags/v14.0.0beta1
@@ -718,6 +718,8 @@ input { | |||
border: 1px solid nc-darken($color-main-background, 14%); | |||
background: $color-main-background; | |||
z-index: 50; | |||
max-height: 250px; | |||
overflow-y: auto; | |||
.multiselect__content { | |||
width: 100%; | |||
padding: 5px 0; | |||
@@ -750,7 +752,7 @@ input { | |||
color: nc-lighten($color-main-text, 33%); | |||
width: 100%; | |||
/* selected checkmark icon */ | |||
&::before { | |||
&:not(.multiselect__option--disabled)::before { | |||
content: ' '; | |||
background-image: url('../img/actions/checkmark.svg?v=1'); | |||
background-repeat: no-repeat; | |||
@@ -762,6 +764,9 @@ input { | |||
margin-right: 5px; | |||
visibility: hidden; | |||
} | |||
&.multiselect__option--disabled { | |||
background-color: nc-darken($color-main-background, 8%); | |||
} | |||
/* add the prop tag-placeholder="create" to add the + | |||
* icon on top of an unknown-and-ready-to-be-created entry | |||
*/ |
@@ -59,6 +59,11 @@ class Factory implements IFactory { | |||
*/ | |||
protected $pluralFunctions = []; | |||
const COMMON_LANGUAGE_CODES = [ | |||
'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' | |||
]; | |||
/** @var IConfig */ | |||
protected $config; | |||
@@ -137,9 +142,9 @@ class Factory implements IFactory { | |||
* | |||
* @link https://github.com/owncloud/core/issues/21955 | |||
*/ | |||
if($this->config->getSystemValue('installed', false)) { | |||
if ($this->config->getSystemValue('installed', false)) { | |||
$userId = !is_null($this->userSession->getUser()) ? $this->userSession->getUser()->getUID() : null; | |||
if(!is_null($userId)) { | |||
if (!is_null($userId)) { | |||
$userLang = $this->config->getUserValue($userId, 'core', 'lang', null); | |||
} else { | |||
$userLang = null; | |||
@@ -310,7 +315,7 @@ class Factory implements IFactory { | |||
*/ | |||
private function isSubDirectory($sub, $parent) { | |||
// Check whether $sub contains no ".." | |||
if(strpos($sub, '..') !== false) { | |||
if (strpos($sub, '..') !== false) { | |||
return false; | |||
} | |||
@@ -441,4 +446,74 @@ class Factory implements IFactory { | |||
return $function; | |||
} | |||
} | |||
/** | |||
* returns the common language and other languages in an | |||
* associative array | |||
* | |||
* @return array | |||
*/ | |||
public function getLanguages() { | |||
$forceLanguage = $this->config->getSystemValue('force_language', false); | |||
if ($forceLanguage !== false) { | |||
return []; | |||
} | |||
$languageCodes = $this->findAvailableLanguages(); | |||
$commonLanguages = []; | |||
$languages = []; | |||
foreach($languageCodes as $lang) { | |||
$l = $this->get('lib', $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 && $potentialName[0] !== '_') {//first check if the language name is in the translation file | |||
$ln = array( | |||
'code' => $lang, | |||
'name' => $potentialName | |||
); | |||
} else if ($lang === 'en') { | |||
$ln = array( | |||
'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 | |||
// common languages -> divider -> other languages | |||
if (in_array($lang, self::COMMON_LANGUAGE_CODES)) { | |||
$commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)] = $ln; | |||
} else { | |||
$languages[] = $ln; | |||
} | |||
} | |||
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']); | |||
}); | |||
return [ | |||
// reset indexes | |||
'commonlanguages' => array_values($commonLanguages), | |||
'languages' => $languages | |||
]; | |||
} | |||
} |
@@ -39,6 +39,7 @@ use OCP\L10N\IFactory; | |||
use OCP\Settings\ISettings; | |||
class PersonalInfo implements ISettings { | |||
/** @var IConfig */ | |||
private $config; | |||
/** @var IUserManager */ | |||
@@ -51,12 +52,6 @@ class PersonalInfo implements ISettings { | |||
private $appManager; | |||
/** @var IFactory */ | |||
private $l10nFactory; | |||
const COMMON_LANGUAGE_CODES = [ | |||
'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' | |||
]; | |||
/** @var IL10N */ | |||
private $l; | |||
@@ -198,64 +193,29 @@ class PersonalInfo implements ISettings { | |||
$uid = $user->getUID(); | |||
$userLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage()); | |||
$languageCodes = $this->l10nFactory->findAvailableLanguages(); | |||
$commonLanguages = []; | |||
$languages = []; | |||
$userConfLang = $this->config->getUserValue($uid, 'core', 'lang', $this->l10nFactory->findLanguage()); | |||
$languages = $this->l10nFactory->getLanguages(); | |||
foreach($languageCodes as $lang) { | |||
$l = \OC::$server->getL10N('lib', $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 && $potentialName[0] !== '_') {//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, self::COMMON_LANGUAGE_CODES)) { | |||
$commonLanguages[array_search($lang, self::COMMON_LANGUAGE_CODES)]=$ln; | |||
} else { | |||
$languages[]=$ln; | |||
} | |||
// associate the user language with the proper array | |||
$userLangIndex = array_search($userConfLang, array_column($languages['commonlanguages'], 'code')); | |||
$userLang = $languages['commonlanguages'][$userLangIndex]; | |||
// search in the other languages | |||
if ($userLangIndex === false) { | |||
$userLangIndex = array_search($userConfLang, array_column($languages['languages'], 'code')); | |||
$userLang = $languages['languages'][$userLangIndex]; | |||
} | |||
// if user language is not available but set somehow: show the actual code as name | |||
if (!is_array($userLang)) { | |||
$userLang = [ | |||
'code' => $userLang, | |||
'name' => $userLang, | |||
'code' => $userConfLang, | |||
'name' => $userConfLang, | |||
]; | |||
} | |||
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']); | |||
}); | |||
return [ | |||
'activelanguage' => $userLang, | |||
'commonlanguages' => $commonLanguages, | |||
'languages' => $languages | |||
]; | |||
return array_merge( | |||
array('activelanguage' => $userLang), | |||
$languages | |||
); | |||
} | |||
/** |
@@ -1305,6 +1305,12 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { | |||
.quota { | |||
width: 170px; | |||
} | |||
.languages { | |||
width: 200px; | |||
.multiselect { | |||
width: 100%; | |||
} | |||
} | |||
.storageLocation { | |||
width: 250px; | |||
} |
@@ -16,7 +16,7 @@ | |||
"vue-click-outside": "^1.0.7", | |||
"vue-infinite-loading": "^2.2.3", | |||
"vue-localstorage": "^0.6.2", | |||
"vue-multiselect": "^2.1", | |||
"vue-multiselect": "^2.1.0", | |||
"vue-router": "^3.0.1", | |||
"vuex": "^3.0.1", | |||
"vuex-router-sync": "^5.0.0" |
@@ -10,6 +10,8 @@ | |||
<div id="headerSubAdmins" class="subadmins" | |||
v-if="subAdminsGroups.length>0">{{ t('settings', 'Group admin for') }}</div> | |||
<div id="headerQuota" class="quota">{{ t('settings', 'Quota') }}</div> | |||
<div id="headerLanguages" class="languages" | |||
v-if="showConfig.showLanguages">{{ t('settings', 'Languages') }}</div> | |||
<div class="headerStorageLocation storageLocation" | |||
v-if="showConfig.showStoragePath">{{ t('settings', 'Storage location') }}</div> | |||
<div class="headerUserBackend userBackend" | |||
@@ -71,6 +73,7 @@ | |||
@tag="validateQuota" > | |||
</multiselect> | |||
</div> | |||
<div class="languages" v-if="showConfig.showLanguages"></div> | |||
<div class="storageLocation" v-if="showConfig.showStoragePath"></div> | |||
<div class="userBackend" v-if="showConfig.showUserBackend"></div> | |||
<div class="lastLogin" v-if="showConfig.showLastLogin"></div> |
@@ -53,6 +53,15 @@ | |||
</multiselect> | |||
<progress class="quota-user-progress" :class="{'warn':usedQuota>80}" :value="usedQuota" max="100"></progress> | |||
</div> | |||
<div class="languages" :class="{'icon-loading-small': loading.languages}" | |||
v-if="showConfig.showLanguages"> | |||
<multiselect :value="userLanguage" :options="languages" :disabled="loading.languages||loading.all" | |||
:placeholder="t('settings', 'No language set')" | |||
label="name" track-by="code" class="multiselect-vue" | |||
:allowEmpty="false" group-values="languages" group-label="label" | |||
@input="setUserLanguage"> | |||
</multiselect> | |||
</div> | |||
<div class="storageLocation" v-if="showConfig.showStoragePath">{{user.storageLocation}}</div> | |||
<div class="userBackend" v-if="showConfig.showUserBackend">{{user.backend}}</div> | |||
<div class="lastLogin" v-if="showConfig.showLastLogin" :title="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''"> | |||
@@ -73,7 +82,6 @@ | |||
import popoverMenu from '../popoverMenu'; | |||
import ClickOutside from 'vue-click-outside'; | |||
import Multiselect from 'vue-multiselect'; | |||
//import Multiselect from '../../../node_modules/vue-multiselect/src/index'; | |||
export default { | |||
name: 'userRow', | |||
@@ -86,8 +94,9 @@ export default { | |||
ClickOutside | |||
}, | |||
mounted() { | |||
// prevent click outside event with popupItem. | |||
this.popupItem = this.$el; | |||
// required if popup needs to stay opened after menu click | |||
// since we only have disable/delete actions, let's close it directly | |||
// this.popupItem = this.$el; | |||
}, | |||
data() { | |||
return { | |||
@@ -102,7 +111,8 @@ export default { | |||
subadmins: false, | |||
quota: false, | |||
delete: false, | |||
disable: false | |||
disable: false, | |||
languages: false | |||
} | |||
} | |||
}, | |||
@@ -159,6 +169,33 @@ export default { | |||
/* PASSWORD POLICY? */ | |||
minPasswordLength() { | |||
return this.$store.getters.getPasswordPolicyMinLength; | |||
}, | |||
/* LANGUAGES */ | |||
languages() { | |||
return Array( | |||
{ | |||
label: t('settings', 'Common languages'), | |||
languages: this.settings.languages.commonlanguages | |||
}, | |||
{ | |||
label: t('settings', 'All languages'), | |||
languages: this.settings.languages.languages | |||
} | |||
); | |||
}, | |||
userLanguage() { | |||
let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages); | |||
let userLang = availableLanguages.find(lang => lang.code === this.user.language); | |||
if (typeof userLang !== 'object' && this.user.language !== '') { | |||
return { | |||
code: this.user.language, | |||
name: this.user.language | |||
} | |||
} else if(this.user.language === '') { | |||
return false; | |||
} | |||
return userLang; | |||
} | |||
}, | |||
methods: { | |||
@@ -370,6 +407,24 @@ export default { | |||
return quota; | |||
}, | |||
/** | |||
* Validate quota string to make sure it's a valid human file size | |||
* | |||
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'} | |||
* @returns {string} | |||
*/ | |||
setUserLanguage(lang) { | |||
this.loading.languages = true; | |||
// ensure we only send the preset id | |||
this.$store.dispatch('setUserData', { | |||
userid: this.user.id, | |||
key: 'language', | |||
value: lang.code | |||
}).then(() => this.loading.languages = false); | |||
return lang; | |||
}, | |||
/** | |||
* Validate quota string to make sure it's a valid human file size | |||
* |
@@ -397,12 +397,13 @@ const actions = { | |||
* @returns {Promise} | |||
*/ | |||
setUserData(context, { userid, key, value }) { | |||
if (['email', 'quota', 'displayname', 'password'].indexOf(key) !== -1) { | |||
let allowedEmpty = ['email', 'displayname']; | |||
if (['email', 'language', 'quota', 'displayname', 'password'].indexOf(key) !== -1) { | |||
// We allow empty email or displayname | |||
if (typeof value === 'string' && | |||
( | |||
(['quota', 'password'].indexOf(key) !== -1 && value.length > 0) || | |||
['email', 'displayname'].indexOf(key) !== -1 | |||
(allowedEmpty.indexOf(key) === -1 && value.length > 0) || | |||
allowedEmpty.indexOf(key) !== -1 | |||
) | |||
) { | |||
return api.requireAdmin().then((response) => { |
@@ -2,6 +2,11 @@ | |||
<div id="app"> | |||
<app-navigation :menu="menu"> | |||
<template slot="settings-content"> | |||
<div> | |||
<input type="checkbox" id="showLanguages" class="checkbox" | |||
:checked="showLanguages" v-model="showLanguages"> | |||
<label for="showLanguages">{{t('settings', 'Show Languages')}}</label> | |||
</div> | |||
<div> | |||
<input type="checkbox" id="showLastLogin" class="checkbox" | |||
:checked="showLastLogin" v-model="showLastLogin"> | |||
@@ -50,7 +55,8 @@ export default { | |||
showStoragePath: false, | |||
showUserBackend: false, | |||
showLastLogin: false, | |||
showNewUserForm: false | |||
showNewUserForm: false, | |||
showLanguages: false | |||
} | |||
} | |||
}, | |||
@@ -82,6 +88,14 @@ export default { | |||
usersLimit() { | |||
return this.$store.getters.getUsersLimit; | |||
}, | |||
// Local settings | |||
showLanguages: { | |||
get: function() {return this.getLocalstorage('showLanguages')}, | |||
set: function(status) { | |||
this.setLocalStorage('showLanguages', status); | |||
} | |||
}, | |||
showLastLogin: { | |||
get: function() {return this.getLocalstorage('showLastLogin')}, | |||
set: function(status) { | |||
@@ -100,6 +114,8 @@ export default { | |||
this.setLocalStorage('showStoragePath', status); | |||
} | |||
}, | |||
userCount() { | |||
return this.$store.getters.getUserCount; | |||
}, | |||
@@ -164,6 +180,3 @@ export default { | |||
} | |||
} | |||
</script> | |||
<style lang="scss"> | |||
</style> |
@@ -43,6 +43,7 @@ $userManager = \OC::$server->getUserManager(); | |||
$groupManager = \OC::$server->getGroupManager(); | |||
$appManager = \OC::$server->getAppManager(); | |||
$config = \OC::$server->getConfig(); | |||
$l10nFactory = \OC::$server->getL10NFactory(); | |||
/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */ | |||
$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT; | |||
@@ -129,6 +130,9 @@ function addition($v, $w) { | |||
} | |||
$userCount = array_reduce($userManager->countUsers(), 'addition', 0); | |||
/* LANGUAGES */ | |||
$languages = $l10nFactory->getLanguages(); | |||
/* FINAL DATA */ | |||
$serverData = array(); | |||
// groups | |||
@@ -139,6 +143,7 @@ $serverData['subadmins'] = $subAdmins; | |||
$serverData['sortGroups'] = $sortGroupsBy; | |||
$serverData['quotaPreset'] = $quotaPreset; | |||
$serverData['userCount'] = $userCount-$disabledUsers; | |||
$serverData['languages'] = $languages; | |||
// Settings | |||
$serverData['defaultQuota'] = $defaultQuota; | |||
$serverData['canChangePassword'] = $canChangePassword; | |||
@@ -147,3 +152,4 @@ $serverData['canChangePassword'] = $canChangePassword; | |||
$tmpl = new OC_Template('settings', 'settings', 'user'); | |||
$tmpl->assign('serverData', $serverData); | |||
$tmpl->printPage(); | |||