summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2022-04-29 11:54:25 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2022-04-30 13:40:27 +0200
commit24c5d994c7652f266f62563f11bab55defc41dae (patch)
tree584ac1f2b3cb3292690fe4c2e767bd6212601f6b /apps
parent77db6ced432953e2f36db28f7a981dbe997e7055 (diff)
downloadnextcloud-server-24c5d994c7652f266f62563f11bab55defc41dae.tar.gz
nextcloud-server-24c5d994c7652f266f62563f11bab55defc41dae.zip
Allow to override the default theme
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/theming/lib/Controller/UserThemeController.php42
-rw-r--r--apps/theming/lib/Service/ThemesService.php8
-rw-r--r--apps/theming/lib/Settings/Personal.php10
-rw-r--r--apps/theming/src/UserThemes.vue11
-rw-r--r--apps/theming/src/components/ItemPreview.vue30
-rw-r--r--apps/theming/tests/Service/ThemesServiceTest.php46
-rw-r--r--apps/theming/tests/Settings/AdminSectionTest.php (renamed from apps/theming/tests/Settings/SectionTest.php)2
-rw-r--r--apps/theming/tests/Settings/PersonalTest.php209
8 files changed, 329 insertions, 29 deletions
diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php
index ec379d2e6fa..71d78db4b3d 100644
--- a/apps/theming/lib/Controller/UserThemeController.php
+++ b/apps/theming/lib/Controller/UserThemeController.php
@@ -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];
}
}
diff --git a/apps/theming/lib/Service/ThemesService.php b/apps/theming/lib/Service/ThemesService.php
index 43977721e76..283b2e9c9ee 100644
--- a/apps/theming/lib/Service/ThemesService.php
+++ b/apps/theming/lib/Service/ThemesService.php
@@ -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 [];
}
diff --git a/apps/theming/lib/Settings/Personal.php b/apps/theming/lib/Settings/Personal.php
index 6dd865b9cf6..790c0fd7f39 100644
--- a/apps/theming/lib/Settings/Personal.php
+++ b/apps/theming/lib/Settings/Personal.php
@@ -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');
diff --git a/apps/theming/src/UserThemes.vue b/apps/theming/src/UserThemes.vue
index 1fd6cb20866..f78e63484d6 100644
--- a/apps/theming/src/UserThemes.vue
+++ b/apps/theming/src/UserThemes.vue
@@ -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,
}
},
diff --git a/apps/theming/src/components/ItemPreview.vue b/apps/theming/src/components/ItemPreview.vue
index 82d588059a2..e7c5866b662 100644
--- a/apps/theming/src/components/ItemPreview.vue
+++ b/apps/theming/src/components/ItemPreview.vue
@@ -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)) {
diff --git a/apps/theming/tests/Service/ThemesServiceTest.php b/apps/theming/tests/Service/ThemesServiceTest.php
index 5865875cbb8..657418db471 100644
--- a/apps/theming/tests/Service/ThemesServiceTest.php
+++ b/apps/theming/tests/Service/ThemesServiceTest.php
@@ -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);
diff --git a/apps/theming/tests/Settings/SectionTest.php b/apps/theming/tests/Settings/AdminSectionTest.php
index c168f13728d..80223664ce4 100644
--- a/apps/theming/tests/Settings/SectionTest.php
+++ b/apps/theming/tests/Settings/AdminSectionTest.php
@@ -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 */
diff --git a/apps/theming/tests/Settings/PersonalTest.php b/apps/theming/tests/Settings/PersonalTest.php
new file mode 100644
index 00000000000..5f0585911bb
--- /dev/null
+++ b/apps/theming/tests/Settings/PersonalTest.php
@@ -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,
+ ];
+ }
+}