Allow to override the default theme

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
pull/32217/head
John Molakvoæ 4 years ago
parent 77db6ced43
commit 24c5d994c7
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
  1. 42
      apps/theming/lib/Controller/UserThemeController.php
  2. 8
      apps/theming/lib/Service/ThemesService.php
  3. 10
      apps/theming/lib/Settings/Personal.php
  4. 11
      apps/theming/src/UserThemes.vue
  5. 30
      apps/theming/src/components/ItemPreview.vue
  6. 46
      apps/theming/tests/Service/ThemesServiceTest.php
  7. 2
      apps/theming/tests/Settings/AdminSectionTest.php
  8. 209
      apps/theming/tests/Settings/PersonalTest.php
  9. 6
      config/config.sample.php
  10. 4
      dist/theming-theming-settings.js
  11. 2
      dist/theming-theming-settings.js.map

@ -30,9 +30,11 @@ declare(strict_types=1);
*/
namespace OCA\Theming\Controller;
use OCA\Theming\ITheme;
use OCA\Theming\Service\ThemesService;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\IConfig;
use OCP\IRequest;
@ -71,17 +73,10 @@ class UserThemeController extends OCSController {
* @throws OCSBadRequestException|PreConditionNotMetException
*/
public function enableTheme(string $themeId): DataResponse {
if ($themeId === '' || !$themeId) {
throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
}
$theme = $this->validateTheme($themeId);
$themes = $this->themesService->getThemes();
if (!isset($themes[$themeId])) {
throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
}
// Enable selected theme
$this->themesService->enableTheme($themes[$themeId]);
$this->themesService->enableTheme($theme);
return new DataResponse();
}
@ -95,6 +90,23 @@ class UserThemeController extends OCSController {
* @throws OCSBadRequestException|PreConditionNotMetException
*/
public function disableTheme(string $themeId): DataResponse {
$theme = $this->validateTheme($themeId);
// Enable selected theme
$this->themesService->disableTheme($theme);
return new DataResponse();
}
/**
* Validate and return the matching ITheme
*
* Disable theme
*
* @param string $themeId the theme ID
* @return ITheme
* @throws OCSBadRequestException|PreConditionNotMetException
*/
private function validateTheme(string $themeId): ITheme {
if ($themeId === '' || !$themeId) {
throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
}
@ -103,9 +115,13 @@ class UserThemeController extends OCSController {
if (!isset($themes[$themeId])) {
throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
}
// Enable selected theme
$this->themesService->disableTheme($themes[$themeId]);
return new DataResponse();
// If trying to toggle another theme but this is enforced
if ($this->config->getSystemValueString('enforce_theme', '') !== ''
&& $themes[$themeId]->getType() === ITheme::TYPE_THEME) {
throw new OCSForbiddenException('Theme switching is disabled');
}
return $themes[$themeId];
}
}

@ -155,8 +155,14 @@ class ThemesService {
return [];
}
$enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
$enabledThemes = json_decode($this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', '[]'));
if ($enforcedTheme !== '') {
return array_merge([$enforcedTheme], $enabledThemes);
}
try {
return json_decode($this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', '[]'));
return $enabledThemes;
} catch (\Exception $e) {
return [];
}

@ -25,6 +25,7 @@
*/
namespace OCA\Theming\Settings;
use OCA\Theming\ITheme;
use OCA\Theming\Service\ThemesService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
@ -54,6 +55,8 @@ class Personal implements ISettings {
}
public function getForm(): TemplateResponse {
$enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
$themes = array_map(function($theme) {
return [
'id' => $theme->getId(),
@ -65,7 +68,14 @@ class Personal implements ISettings {
];
}, $this->themesService->getThemes());
if ($enforcedTheme !== '') {
$themes = array_filter($themes, function($theme) use ($enforcedTheme) {
return $theme['type'] !== ITheme::TYPE_THEME || $theme['id'] === $enforcedTheme;
});
}
$this->initialStateService->provideInitialState('themes', array_values($themes));
$this->initialStateService->provideInitialState('enforceTheme', $enforcedTheme);
Util::addScript($this->appName, 'theming-settings');
return new TemplateResponse($this->appName, 'settings-personal');

@ -6,16 +6,17 @@
<div class="theming__preview-list">
<ItemPreview v-for="theme in themes"
:key="theme.id"
:theme="theme"
:enforced="theme.id === enforceTheme"
:selected="selectedTheme.id === theme.id"
:themes="themes"
:theme="theme"
:unique="themes.length === 1"
type="theme"
@change="changeTheme" />
<ItemPreview v-for="theme in fonts"
:key="theme.id"
:theme="theme"
:selected="theme.enabled"
:themes="fonts"
:theme="theme"
:unique="fonts.length === 1"
type="font"
@change="changeFont" />
</div>
@ -31,6 +32,7 @@ import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection'
import ItemPreview from './components/ItemPreview'
const availableThemes = loadState('theming', 'themes', [])
const enforceTheme = loadState('theming', 'enforceTheme', '')
console.debug('Available themes', availableThemes)
@ -44,6 +46,7 @@ export default {
data() {
return {
availableThemes,
enforceTheme,
}
},

@ -4,8 +4,12 @@
<div class="theming__preview-description">
<h3>{{ theme.title }}</h3>
<p>{{ theme.description }}</p>
<span v-if="enforced" class="theming__preview-warning" role="note">
{{ t('theming', 'Theme selection is enforced') }}
</span>
<CheckboxRadioSwitch class="theming__preview-toggle"
:checked.sync="checked"
:disabled="enforced"
:name="name"
:type="switchType">
{{ theme.enableLabel }}
@ -24,30 +28,34 @@ export default {
CheckboxRadioSwitch,
},
props: {
theme: {
type: Object,
required: true,
enforced: {
type: Boolean,
default: false,
},
selected: {
type: Boolean,
default: false,
},
theme: {
type: Object,
required: true,
},
type: {
type: String,
default: '',
},
themes: {
type: Array,
default: () => [],
unique: {
type: Boolean,
default: false,
},
},
computed: {
switchType() {
return this.themes.length === 1 ? 'switch' : 'radio'
return this.unique ? 'switch' : 'radio'
},
name() {
return this.switchType === 'radio' ? this.type : null
return !this.unique ? this.type : null
},
img() {
@ -62,7 +70,7 @@ export default {
console.debug('Selecting theme', this.theme, checked)
// If this is a radio, we can only enable
if (this.switchType === 'radio') {
if (!this.unique) {
this.$emit('change', { enabled: true, id: this.theme.id })
return
}
@ -109,6 +117,10 @@ $ratio: 16;
padding: 12px 0;
}
}
&-warning {
color: var(--color-warning);
}
}
@media (max-width: (1024px / 1.5)) {

@ -177,7 +177,7 @@ class ThemesServiceTest extends TestCase {
* @param string $toEnable
* @param string[] $enabledThemes
*/
public function testisEnabled(string $themeId, array $enabledThemes, $expected) {
public function testIsEnabled(string $themeId, array $enabledThemes, $expected) {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
@ -195,6 +195,50 @@ class ThemesServiceTest extends TestCase {
$this->assertEquals($expected, $this->themesService->isEnabled($this->themes[$themeId]));
}
public function testGetEnabledThemes() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->config->expects($this->once())
->method('getUserValue')
->with('user', Application::APP_ID, 'enabled-themes', '[]')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('getSystemValueString')
->with('enforce_theme', '')
->willReturn('');
$this->assertEquals([], $this->themesService->getEnabledThemes());
}
public function testGetEnabledThemesEnforced() {
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($user);
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$this->config->expects($this->once())
->method('getUserValue')
->with('user', Application::APP_ID, 'enabled-themes', '[]')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('getSystemValueString')
->with('enforce_theme', '')
->willReturn('light');
$this->assertEquals(['light'], $this->themesService->getEnabledThemes());
}
private function initThemes() {
$util = $this->createMock(Util::class);
$urlGenerator = $this->createMock(IURLGenerator::class);

@ -29,7 +29,7 @@ use OCP\IL10N;
use OCP\IURLGenerator;
use Test\TestCase;
class SectionTest extends TestCase {
class AdminSectionTest extends TestCase {
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
private $url;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */

@ -0,0 +1,209 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Theming\Tests\Settings;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\Service\ThemesService;
use OCA\Theming\Settings\Personal;
use OCA\Theming\Themes\DarkHighContrastTheme;
use OCA\Theming\Themes\DarkTheme;
use OCA\Theming\Themes\DefaultTheme;
use OCA\Theming\Themes\DyslexiaFont;
use OCA\Theming\Themes\HighContrastTheme;
use OCA\Theming\Themes\LightTheme;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCA\Theming\ITheme;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Test\TestCase;
class PersonalTest extends TestCase {
private IConfig $config;
private IUserSession $userSession;
private ThemesService $themesService;
private IInitialState $initialStateService;
/** @var ITheme[] */
private $themes;
protected function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->themesService = $this->createMock(ThemesService::class);
$this->initialStateService = $this->createMock(IInitialState::class);
$this->initThemes();
$this->themesService
->expects($this->any())
->method('getThemes')
->willReturn($this->themes);
$this->admin = new Personal(
Application::APP_ID,
$this->config,
$this->userSession,
$this->themesService,
$this->initialStateService
);
}
public function dataTestGetForm() {
return [
['', [
$this->formatThemeForm('default'),
$this->formatThemeForm('light'),
$this->formatThemeForm('dark'),
$this->formatThemeForm('highcontrast'),
$this->formatThemeForm('dark-highcontrast'),
$this->formatThemeForm('opendyslexic'),
]],
['dark', [
$this->formatThemeForm('dark'),
$this->formatThemeForm('opendyslexic'),
]],
];
}
/**
* @dataProvider dataTestGetForm
*
* @param string $toEnable
* @param string[] $enabledThemes
*/
public function testGetForm(string $enforcedTheme, $themesState) {
$this->config->expects($this->once())
->method('getSystemValueString')
->with('enforce_theme', '')
->willReturn($enforcedTheme);
$this->initialStateService->expects($this->at(0))
->method('provideInitialState')
->with('themes', $themesState);
$this->initialStateService->expects($this->at(1))
->method('provideInitialState')
->with('enforceTheme', $enforcedTheme);
$expected = new TemplateResponse('theming', 'settings-personal');
$this->assertEquals($expected, $this->admin->getForm());
}
public function testGetSection() {
$this->assertSame('theming', $this->admin->getSection());
}
public function testGetPriority() {
$this->assertSame(40, $this->admin->getPriority());
}
private function initThemes() {
$util = $this->createMock(Util::class);
$themingDefaults = $this->createMock(ThemingDefaults::class);
$urlGenerator = $this->createMock(IURLGenerator::class);
$imageManager = $this->createMock(ImageManager::class);
$config = $this->createMock(IConfig::class);
$l10n = $this->createMock(IL10N::class);
$themingDefaults->expects($this->any())
->method('getColorPrimary')
->willReturn('#0082c9');
$this->themes = [
'default' => new DefaultTheme(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'light' => new LightTheme(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'dark' => new DarkTheme(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'highcontrast' => new HighContrastTheme(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'dark-highcontrast' => new DarkHighContrastTheme(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
'opendyslexic' => new DyslexiaFont(
$util,
$themingDefaults,
$urlGenerator,
$imageManager,
$config,
$l10n,
),
];
}
private function formatThemeForm(string $themeId): array {
$this->initThemes();
$theme = $this->themes[$themeId];
return [
'id' => $theme->getId(),
'type' => $theme->getType(),
'title' => $theme->getTitle(),
'enableLabel' => $theme->getEnableLabel(),
'description' => $theme->getDescription(),
'enabled' => false,
];
}
}

@ -1789,6 +1789,12 @@ $CONFIG = [
*/
'theme' => '',
/**
* Enforce the user theme. This will disable the user theming settings
* This must be a valid ITheme ID
*/
'enforce_theme' => '',
/**
* The default cipher for encrypting files. Currently supported are:
* - AES-256-CTR

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save