Add admin user customization kill switch

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ 2022-10-13 14:22:04 +02:00
parent bbbc1d4a16
commit ef760e0337
No known key found for this signature in database
GPG key ID: 60C25B8C072916CF
24 changed files with 115 additions and 35 deletions

View file

@ -1,6 +1,5 @@
:root {
--color-main-background: #ffffff;
--color-main-background-not-plain: #0082c9;
--color-main-background-rgb: 255,255,255;
--color-main-background-translucent: rgba(var(--color-main-background-rgb), .97);
--color-main-background-blur: rgba(var(--color-main-background-rgb), .8);

View file

@ -26,6 +26,8 @@
}
#theming form.uploadButton {
width: 411px;
display: flex;
align-items: center;
}
#theming form .theme-undo,
#theming .theme-remove-bg {
@ -41,6 +43,10 @@
visibility: visible;
height: 32px;
width: 32px;
margin-left: auto;
}
#theming form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
margin-left: 0;
}
#theming input[type=text]:hover + .theme-undo,
#theming input[type=text] + .theme-undo:hover,
@ -55,6 +61,8 @@
#theming label span {
display: inline-block;
min-width: 175px;
max-width: 175px;
white-space: wrap;
padding: 8px 0px;
vertical-align: top;
}
@ -120,6 +128,14 @@
#theming #theming-preview-favicon {
background-image: var(--image-favicon);
}
#theming #user-theming {
margin-top: 44px;
display: flex;
}
#theming #user-theming > div {
max-width: 400px;
margin-bottom: 44px;
}
/* transition effects for theming value changes */
#header {

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["settings-admin.scss"],"names":[],"mappings":"AACI;EACI;;AAGJ;AAAA;EAEI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;EACI;;AAEJ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGP;EAEO;;AAGP;EACO;;;AAIR;AACA;EACI;;AACA;EACI","file":"settings-admin.css"}
{"version":3,"sourceRoot":"","sources":["settings-admin.scss"],"names":[],"mappings":"AACI;EACI;;AAGJ;AAAA;EAEI;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;;AAEJ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;;AAEJ;EAEI;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGJ;AAAA;EAEI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAGP;EAEO;;AAGP;EACO;;AAGJ;EACI;EACA;;AACD;EACK;EACA;;;AAKZ;AACA;EACI;;AACA;EACI","file":"settings-admin.css"}

View file

@ -31,6 +31,8 @@
}
form.uploadButton {
width: 411px;
display: flex;
align-items: center;
}
form .theme-undo,
.theme-remove-bg {
@ -46,7 +48,14 @@
visibility: visible;
height: 32px;
width: 32px;
// right align
margin-left: auto;
}
form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
// Only align the undo button if both are shown
margin-left: 0;
}
input[type='text']:hover + .theme-undo,
input[type='text'] + .theme-undo:hover,
input[type='text']:focus + .theme-undo,
@ -61,6 +70,8 @@
label span {
display: inline-block;
min-width: 175px;
max-width: 175px;
white-space: wrap;
padding: 8px 0px;
vertical-align: top;
}
@ -137,6 +148,15 @@
#theming-preview-favicon {
background-image: var(--image-favicon);
}
#user-theming {
margin-top: 44px;
display: flex;
& > div {
max-width: 400px;
margin-bottom: 44px;
}
}
}
/* transition effects for theming value changes */

View file

@ -173,6 +173,11 @@ window.addEventListener('DOMContentLoaded', function () {
var el = $(this);
});
$('#userThemingDisabled').change(function(e) {
var checked = e.target.checked
setThemingValue('disable-user-theming', checked ? 'yes' : 'no')
});
function onChange(e) {
var el = $(this);
var setting = el.parent().find('div[data-setting]').data('setting');

View file

@ -33,7 +33,7 @@ use Symfony\Component\Console\Output\OutputInterface;
class UpdateConfig extends Command {
public const SUPPORTED_KEYS = [
'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color'
'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color', 'disable-user-theming'
];
public const SUPPORTED_IMAGE_KEYS = [

View file

@ -151,6 +151,11 @@ class ThemingController extends Controller {
$error = $this->l10n->t('The given color is invalid');
}
break;
case 'disable-user-theming':
if ($value !== "yes" && $value !== "no") {
$error = $this->l10n->t('Disable-user-theming should be true or false');
}
break;
}
if ($error !== null) {
return new DataResponse([

View file

@ -82,6 +82,7 @@ class Admin implements IDelegatedSettings {
'images' => $this->imageManager->getCustomImages(),
'imprintUrl' => $this->themingDefaults->getImprintUrl(),
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
'userThemingDisabled' => $this->themingDefaults->isUserThemingDisabled(),
];
return new TemplateResponse($this->appName, 'settings-admin', $parameters, '');

View file

@ -27,6 +27,7 @@ namespace OCA\Theming\Settings;
use OCA\Theming\ITheme;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
@ -39,15 +40,18 @@ class Personal implements ISettings {
private IConfig $config;
private ThemesService $themesService;
private IInitialState $initialStateService;
private ThemingDefaults $themingDefaults;
public function __construct(string $appName,
IConfig $config,
ThemesService $themesService,
IInitialState $initialStateService) {
IInitialState $initialStateService,
ThemingDefaults $themingDefaults) {
$this->appName = $appName;
$this->config = $config;
$this->themesService = $themesService;
$this->initialStateService = $initialStateService;
$this->themingDefaults = $themingDefaults;
}
public function getForm(): TemplateResponse {
@ -72,6 +76,7 @@ class Personal implements ISettings {
$this->initialStateService->provideInitialState('themes', array_values($themes));
$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
$this->initialStateService->provideInitialState('isUserThemingDisabled', $this->themingDefaults->isUserThemingDisabled());
Util::addScript($this->appName, 'theming-settings');
return new TemplateResponse($this->appName, 'settings-personal');

View file

@ -109,11 +109,9 @@ class DefaultTheme implements ITheme {
$colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
$hasCustomLogoHeader = $this->imageManager->hasImage('logo') || $this->imageManager->hasImage('logoheader');
$hasCustomPrimaryColour = !empty($this->config->getAppValue(Application::APP_ID, 'color'));
$variables = [
'--color-main-background' => $colorMainBackground,
'--color-main-background-not-plain' => $this->themingDefaults->getColorPrimary(),
'--color-main-background-rgb' => $colorMainBackgroundRGB,
'--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), .97)',
'--color-main-background-blur' => 'rgba(var(--color-main-background-rgb), .8)',
@ -202,7 +200,9 @@ class DefaultTheme implements ITheme {
'--background-invert-if-dark' => 'no',
'--background-invert-if-bright' => 'invert(100%)',
// Default last fallback values
'--image-main-background' => "url('" . $this->urlGenerator->imagePath('core', 'app-background.jpg') . "')",
'--color-main-background-plain' => $this->defaultPrimaryColor,
];
// Primary variables
@ -211,8 +211,9 @@ class DefaultTheme implements ITheme {
$backgroundDeleted = $this->config->getAppValue(Application::APP_ID, 'backgroundMime', '') === 'backgroundColor';
// If primary as background has been request or if we have a custom primary colour
// let's not define the background image
if ($backgroundDeleted || $hasCustomPrimaryColour) {
$variables["--image-background-plain"] = 'true';
if ($backgroundDeleted && $this->themingDefaults->isUserThemingDisabled()) {
$variables['--image-background-plain'] = 'true';
$variables['--color-main-background-plain'] = $this->themingDefaults->getColorPrimary();
}
// Register image variables only if custom-defined
@ -237,10 +238,11 @@ class DefaultTheme implements ITheme {
$appManager = Server::get(IAppManager::class);
$user = $this->userSession->getUser();
if ($appManager->isEnabledForUser(Application::APP_ID) && $user !== null) {
if (!$this->themingDefaults->isUserThemingDisabled() && $appManager->isEnabledForUser(Application::APP_ID) && $user !== null) {
$themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default');
$currentVersion = (int)$this->config->getUserValue($user->getUID(), Application::APP_ID, 'userCacheBuster', '0');
if ($themingBackground === 'custom') {
$cacheBuster = substr(sha1($user->getUID() . '_' . $currentVersion), 0, 8);
$variables['--image-main-background'] = "url('" . $this->urlGenerator->linkToRouteAbsolute('theming.userTheme.getBackground') . "?v=$cacheBuster')";

View file

@ -220,6 +220,10 @@ class ThemingDefaults extends \OC_Defaults {
// admin-defined primary color
$defaultColor = $this->getDefaultColorPrimary();
if ($this->isUserThemingDisabled()) {
return $defaultColor;
}
// user-defined primary color
$themingBackground = '';
@ -494,4 +498,11 @@ class ThemingDefaults extends \OC_Defaults {
public function getTextColorPrimary() {
return $this->util->invertTextColor($this->getColorPrimary()) ? '#000000' : '#ffffff';
}
/**
* Has the admin disabled user customization
*/
public function isUserThemingDisabled(): bool {
return $this->config->getAppValue('theming', 'disable-user-theming', 'no') === 'yes';
}
}

View file

@ -64,11 +64,16 @@
<NcSettingsSection :title="t('theming', 'Background')"
class="background">
<p>{{ t('theming', 'Set a custom background') }}</p>
<BackgroundSettings class="background__grid"
:background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
<template v-if="isUserThemingDisabled">
<p>{{ t('theming', 'Customization has been disabled by your administrator') }}</p>
</template>
<template v-else>
<p>{{ t('theming', 'Set a custom background') }}</p>
<BackgroundSettings class="background__grid"
:background="background"
:theming-default-background="themingDefaultBackground"
@update:background="updateBackground" />
</template>
</NcSettingsSection>
</section>
</template>
@ -90,6 +95,7 @@ const shortcutsDisabled = loadState('theming', 'shortcutsDisabled', false)
const background = loadState('theming', 'background')
const themingDefaultBackground = loadState('theming', 'themingDefaultBackground')
const shippedBackgroundList = loadState('theming', 'shippedBackgrounds')
const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')
console.debug('Available themes', availableThemes)
@ -109,6 +115,7 @@ export default {
shortcutsDisabled,
background,
themingDefaultBackground,
isUserThemingDisabled,
}
},

View file

@ -81,7 +81,7 @@ style('theming', 'settings-admin');
<form class="uploadButton" method="post" action="<?php p($_['uploadLogoRoute']) ?>" data-image-key="background">
<input type="hidden" id="theming-backgroundMime" value="<?php p($_['images']['background']['mime']); ?>" />
<input type="hidden" name="key" value="background" />
<label for="upload-login-background"><span><?php p($l->t('Login image')) ?></span></label>
<label for="upload-login-background"><span><?php p($l->t('Background and login image')) ?></span></label>
<input id="upload-login-background" class="fileupload" name="image" type="file">
<label for="upload-login-background" class="button icon-upload svg" id="upload-login-background" title="<?php p($l->t("Upload new login background")) ?>"></label>
<div data-setting="backgroundMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
@ -93,7 +93,6 @@ style('theming', 'settings-admin');
</div>
<h3 class="inlineblock"><?php p($l->t('Advanced options')); ?></h3>
<div class="advanced-options">
<div>
<label>
@ -131,6 +130,16 @@ style('theming', 'settings-admin');
<div data-setting="faviconMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
</form>
</div>
<div class="advanced-options" id="user-theming">
<label><span><?php p($l->t('User settings')); ?></span></label>
<div>
<p class="info">
<?php p($l->t('Although you can select and customize your instance, users can change their background and colors. If you want to enforce your customization, you can check this box.')); ?>
</p>
<input id="userThemingDisabled" class="checkbox" type="checkbox" <?php p($_['userThemingDisabled'] ? 'checked="checked"' : ''); ?> />
<label for="userThemingDisabled"><?php p($l->t('Disable user theming')) ?></label>
</div>
</div>
</div>
<div class="theming-hints">

View file

@ -97,7 +97,7 @@ html {
body {
background-color: var(--color-main-background-plain, var(--color-main-background));
background-image: var(--image-main-background);
background-image: var(--image-background-plain, var(--image-main-background));
background-size: cover;
background-position: center;
position: fixed;

View file

@ -47,7 +47,7 @@ html {
body {
background-color: var(--color-main-background-plain, var(--color-main-background));
background-image: var(--image-main-background);
background-image: var(--image-background-plain, var(--image-main-background));
background-size: cover;
background-position: center;
position: fixed;

View file

@ -2875,7 +2875,7 @@ html {
body {
background-color: var(--color-main-background-plain, var(--color-main-background));
background-image: var(--image-main-background);
background-image: var(--image-background-plain, var(--image-main-background));
background-size: cover;
background-position: center;
position: fixed;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long