aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/lib/Service
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/lib/Service')
-rw-r--r--apps/theming/lib/Service/BackgroundService.php319
-rw-r--r--apps/theming/lib/Service/JSDataService.php50
-rw-r--r--apps/theming/lib/Service/ThemeInjectionService.php57
-rw-r--r--apps/theming/lib/Service/ThemesService.php75
4 files changed, 301 insertions, 200 deletions
diff --git a/apps/theming/lib/Service/BackgroundService.php b/apps/theming/lib/Service/BackgroundService.php
index 1b948c7300a..ee9466c3a36 100644
--- a/apps/theming/lib/Service/BackgroundService.php
+++ b/apps/theming/lib/Service/BackgroundService.php
@@ -3,34 +3,14 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- *
- * @author Jan C. Borchardt <hey@jancborchardt.net>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Christopher Ng <chrng8@gmail.com>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Theming\Service;
use InvalidArgumentException;
use OC\User\NoUserException;
use OCA\Theming\AppInfo\Application;
-use OCA\Theming\ThemingDefaults;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -38,170 +18,202 @@ use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\IAppConfig;
use OCP\IConfig;
+use OCP\Image;
use OCP\Lock\LockedException;
use OCP\PreConditionNotMetException;
+use RuntimeException;
class BackgroundService {
- // true when the background is bright and need dark icons
- public const THEMING_MODE_DARK = 'dark';
- public const DEFAULT_COLOR = '#0082c9';
- public const DEFAULT_ACCESSIBLE_COLOR = '#00679e';
+ public const DEFAULT_COLOR = '#00679e';
+ public const DEFAULT_BACKGROUND_COLOR = '#00679e';
+ /**
+ * One of our shipped background images is used
+ */
public const BACKGROUND_SHIPPED = 'shipped';
+ /**
+ * A custom background image is used
+ */
public const BACKGROUND_CUSTOM = 'custom';
+ /**
+ * The default background image is used
+ */
public const BACKGROUND_DEFAULT = 'default';
- public const BACKGROUND_DISABLED = 'disabled';
+ /**
+ * Just a background color is used
+ */
+ public const BACKGROUND_COLOR = 'color';
+
+ public const DEFAULT_BACKGROUND_IMAGE = 'jenna-kim-the-globe.webp';
- public const DEFAULT_BACKGROUND_IMAGE = 'kamil-porembinski-clouds.jpg';
+ /**
+ * 'attribution': Name, artist and license
+ * 'description': Alternative text
+ * 'attribution_url': URL for attribution
+ * 'background_color': Cached mean color of the top part to calculate app menu colors and use as fallback
+ * 'primary_color': Recommended primary color for this theme / image
+ */
public const SHIPPED_BACKGROUNDS = [
+ 'jenna-kim-the-globe.webp' => [
+ 'attribution' => 'Globe (Jenna Kim - Nextcloud GmbH, CC-BY-SA-4.0)',
+ 'description' => 'Background picture of white clouds on in front of a blue sky',
+ 'attribution_url' => 'https://nextcloud.com/trademarks/',
+ 'dark_variant' => 'jenna-kim-the-globe-dark.webp',
+ 'background_color' => self::DEFAULT_BACKGROUND_COLOR,
+ 'primary_color' => self::DEFAULT_COLOR,
+ ],
+ 'kamil-porembinski-clouds.jpg' => [
+ 'attribution' => 'Clouds (Kamil Porembiński, CC BY-SA)',
+ 'description' => 'Background picture of white clouds on in front of a blue sky',
+ 'attribution_url' => 'https://www.flickr.com/photos/paszczak000/8715851521/',
+ 'background_color' => self::DEFAULT_BACKGROUND_COLOR,
+ 'primary_color' => self::DEFAULT_COLOR,
+ ],
'hannah-maclean-soft-floral.jpg' => [
'attribution' => 'Soft floral (Hannah MacLean, CC0)',
'description' => 'Abstract background picture in yellow and white color whith a flower on it',
'attribution_url' => 'https://stocksnap.io/photo/soft-floral-XOYWCCW5PA',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#D8A06C',
+ 'background_color' => '#e4d2c1',
+ 'primary_color' => '#9f652f',
],
'ted-moravec-morning-fog.jpg' => [
'attribution' => 'Morning fog (Ted Moravec, Public Domain)',
'description' => 'Background picture of a forest shrouded in fog',
'attribution_url' => 'https://flickr.com/photos/tmoravec/52392410261',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#38A084',
+ 'background_color' => '#f6f7f6',
+ 'primary_color' => '#114c3b',
],
'stefanus-martanto-setyo-husodo-underwater-ocean.jpg' => [
'attribution' => 'Underwater ocean (Stefanus Martanto Setyo Husodo, CC0)',
'description' => 'Background picture of an underwater ocean',
'attribution_url' => 'https://stocksnap.io/photo/underwater-ocean-TJA9LBH4WS',
+ 'background_color' => '#003351',
'primary_color' => '#04577e',
],
'zoltan-voros-rhythm-and-blues.jpg' => [
'attribution' => 'Rhythm and blues (Zoltán Vörös, CC BY)',
'description' => 'Abstract background picture of sand dunes during night',
'attribution_url' => 'https://flickr.com/photos/v923z/51634409289/',
+ 'background_color' => '#1c2437',
'primary_color' => '#1c243c',
],
'anatoly-mikhaltsov-butterfly-wing-scale.jpg' => [
'attribution' => 'Butterfly wing scale (Anatoly Mikhaltsov, CC BY-SA)',
'description' => 'Background picture of a red-ish butterfly wing under microscope',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:%D0%A7%D0%B5%D1%88%D1%83%D0%B9%D0%BA%D0%B8_%D0%BA%D1%80%D1%8B%D0%BB%D0%B0_%D0%B1%D0%B0%D0%B1%D0%BE%D1%87%D0%BA%D0%B8.jpg',
+ 'background_color' => '#652e11',
'primary_color' => '#a53c17',
],
'bernie-cetonia-aurata-take-off-composition.jpg' => [
'attribution' => 'Cetonia aurata take off composition (Bernie, Public Domain)',
'description' => 'Montage of a cetonia aurata bug that takes off with white background',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Cetonia_aurata_take_off_composition_05172009.jpg',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#869171',
+ 'background_color' => '#dee0d3',
+ 'primary_color' => '#56633d',
],
'dejan-krsmanovic-ribbed-red-metal.jpg' => [
'attribution' => 'Ribbed red metal (Dejan Krsmanovic, CC BY)',
'description' => 'Abstract background picture of red ribbed metal with two horizontal white elements on top of it',
'attribution_url' => 'https://www.flickr.com/photos/dejankrsmanovic/42971456774/',
+ 'background_color' => '#9b171c',
'primary_color' => '#9c4236',
],
'eduardo-neves-pedra-azul.jpg' => [
'attribution' => 'Pedra azul milky way (Eduardo Neves, CC BY-SA)',
'description' => 'Background picture of the milky way during night with a mountain in front of it',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Pedra_Azul_Milky_Way.jpg',
+ 'background_color' => '#1d242d',
'primary_color' => '#4f6071',
],
'european-space-agency-barents-bloom.jpg' => [
'attribution' => 'Barents bloom (European Space Agency, CC BY-SA)',
'description' => 'Abstract background picture of blooming barents in blue and green colors',
'attribution_url' => 'https://www.esa.int/ESA_Multimedia/Images/2016/08/Barents_bloom',
+ 'background_color' => '#1c383d',
'primary_color' => '#396475',
],
'hannes-fritz-flippity-floppity.jpg' => [
'attribution' => 'Flippity floppity (Hannes Fritz, CC BY-SA)',
'description' => 'Abstract background picture of many pairs of flip flops hanging on a wall in multiple colors',
'attribution_url' => 'http://hannes.photos/flippity-floppity',
+ 'background_color' => '#5b2d53',
'primary_color' => '#98415a',
],
'hannes-fritz-roulette.jpg' => [
'attribution' => 'Roulette (Hannes Fritz, CC BY-SA)',
'description' => 'Background picture of a rotating giant wheel during night',
'attribution_url' => 'http://hannes.photos/roulette',
+ 'background_color' => '#000000',
'primary_color' => '#845334',
],
'hannes-fritz-sea-spray.jpg' => [
'attribution' => 'Sea spray (Hannes Fritz, CC BY-SA)',
'description' => 'Background picture of a stone coast with fog and sea behind it',
'attribution_url' => 'http://hannes.photos/sea-spray',
+ 'background_color' => '#333f47',
'primary_color' => '#4f6071',
],
- 'kamil-porembinski-clouds.jpg' => [
- 'attribution' => 'Clouds (Kamil Porembiński, CC BY-SA)',
- 'description' => 'Background picture of white clouds on in front of a blue sky',
- 'attribution_url' => 'https://www.flickr.com/photos/paszczak000/8715851521/',
- 'primary_color' => self::DEFAULT_COLOR,
- ],
'bernard-spragg-new-zealand-fern.jpg' => [
'attribution' => 'New zealand fern (Bernard Spragg, CC0)',
'description' => 'Abstract background picture of fern leafes',
'attribution_url' => 'https://commons.wikimedia.org/wiki/File:NZ_Fern.(Blechnum_chambersii)_(11263534936).jpg',
+ 'background_color' => '#0c3c03',
'primary_color' => '#316b26',
],
'rawpixel-pink-tapioca-bubbles.jpg' => [
'attribution' => 'Pink tapioca bubbles (Rawpixel, CC BY)',
'description' => 'Abstract background picture of pink tapioca bubbles',
'attribution_url' => 'https://www.flickr.com/photos/byrawpixel/27665140298/in/photostream/',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#b17ab4',
+ 'background_color' => '#c56e95',
+ 'primary_color' => '#7b4e7e',
],
'nasa-waxing-crescent-moon.jpg' => [
'attribution' => 'Waxing crescent moon (NASA, Public Domain)',
'description' => 'Background picture of glowing earth in foreground and moon in the background',
'attribution_url' => 'https://www.nasa.gov/image-feature/a-waxing-crescent-moon',
+ 'background_color' => '#000002',
'primary_color' => '#005ac1',
],
'tommy-chau-already.jpg' => [
'attribution' => 'Cityscape (Tommy Chau, CC BY)',
'description' => 'Background picture of a skyscraper city during night',
'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/16910999368',
+ 'background_color' => '#35229f',
'primary_color' => '#6a2af4',
],
'tommy-chau-lion-rock-hill.jpg' => [
'attribution' => 'Lion rock hill (Tommy Chau, CC BY)',
'description' => 'Background picture of mountains during sunset or sunrise',
'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/17136440246',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#c074a9',
+ 'background_color' => '#cb92b7',
+ 'primary_color' => '#7f4f70',
],
'lali-masriera-yellow-bricks.jpg' => [
'attribution' => 'Yellow bricks (Lali Masriera, CC BY)',
'description' => 'Background picture of yellow bricks with some yellow tubes',
'attribution_url' => 'https://www.flickr.com/photos/visualpanic/3982464447',
- 'theming' => self::THEMING_MODE_DARK,
- 'primary_color' => '#bc8210',
+ 'background_color' => '#c78a19',
+ 'primary_color' => '#7f5700',
],
];
- private IRootFolder $rootFolder;
- private IAppData $appData;
- private IConfig $config;
- private string $userId;
- private ThemingDefaults $themingDefaults;
-
- public function __construct(IRootFolder $rootFolder,
- IAppData $appData,
- IConfig $config,
- ?string $userId,
- ThemingDefaults $themingDefaults) {
- if ($userId === null) {
- return;
- }
-
- $this->rootFolder = $rootFolder;
- $this->config = $config;
- $this->userId = $userId;
- $this->appData = $appData;
- $this->themingDefaults = $themingDefaults;
+ public function __construct(
+ private IRootFolder $rootFolder,
+ private IAppData $appData,
+ private IAppConfig $appConfig,
+ private IConfig $config,
+ private ?string $userId,
+ ) {
}
- public function setDefaultBackground(): void {
- $this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_image');
- $this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_color');
+ public function setDefaultBackground(?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+
+ $this->config->deleteUserValue($userId, Application::APP_ID, 'background_image');
+ $this->config->deleteUserValue($userId, Application::APP_ID, 'background_color');
+ $this->config->deleteUserValue($userId, Application::APP_ID, 'primary_color');
}
/**
@@ -212,46 +224,81 @@ class BackgroundService {
* @throws PreConditionNotMetException
* @throws NoUserException
*/
- public function setFileBackground($path): void {
- $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_CUSTOM);
- $userFolder = $this->rootFolder->getUserFolder($this->userId);
+ public function setFileBackground(string $path, ?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+ $userFolder = $this->rootFolder->getUserFolder($userId);
/** @var File $file */
$file = $userFolder->get($path);
- $image = new \OCP\Image();
+ $handle = $file->fopen('r');
+ if ($handle === false) {
+ throw new InvalidArgumentException('Invalid image file');
+ }
+ $this->getAppDataFolder()->newFile('background.jpg', $handle);
- if ($image->loadFromFileHandle($file->fopen('r')) === false) {
+ $this->recalculateMeanColor();
+ }
+
+ public function recalculateMeanColor(?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+
+ $image = new Image();
+ $handle = $this->getAppDataFolder($userId)->getFile('background.jpg')->read();
+ if ($handle === false || $image->loadFromFileHandle($handle) === false) {
throw new InvalidArgumentException('Invalid image file');
}
- $this->getAppDataFolder()->newFile('background.jpg', $file->fopen('r'));
+ $meanColor = $this->calculateMeanColor($image);
+ if ($meanColor !== false) {
+ $this->setColorBackground($meanColor);
+ }
+ $this->config->setUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_CUSTOM);
}
- public function setShippedBackground($fileName): void {
- if (!array_key_exists($fileName, self::SHIPPED_BACKGROUNDS)) {
+ /**
+ * Set background of user to a shipped background identified by the filename
+ * @param string $filename The shipped background filename
+ * @param null|string $userId The user to set - defaults to currently logged in user
+ * @throws RuntimeException If neither $userId is specified nor a user is logged in
+ * @throws InvalidArgumentException If the specified filename does not match any shipped background
+ */
+ public function setShippedBackground(string $filename, ?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+
+ if (!array_key_exists($filename, self::SHIPPED_BACKGROUNDS)) {
throw new InvalidArgumentException('The given file name is invalid');
}
- $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', $fileName);
- $this->setColorBackground(self::SHIPPED_BACKGROUNDS[$fileName]['primary_color']);
+ $this->setColorBackground(self::SHIPPED_BACKGROUNDS[$filename]['background_color'], $userId);
+ $this->config->setUserValue($userId, Application::APP_ID, 'background_image', $filename);
+ $this->config->setUserValue($userId, Application::APP_ID, 'primary_color', self::SHIPPED_BACKGROUNDS[$filename]['primary_color']);
}
- public function setColorBackground(string $color): void {
+ /**
+ * Set the background to color only
+ * @param string|null $userId The user to set the color - default to current logged-in user
+ */
+ public function setColorBackground(string $color, ?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+
if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) {
throw new InvalidArgumentException('The given color is invalid');
}
- $this->config->setUserValue($this->userId, Application::APP_ID, 'background_color', $color);
+ $this->config->setUserValue($userId, Application::APP_ID, 'background_color', $color);
+ $this->config->setUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR);
}
- public function deleteBackgroundImage(): void {
- $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_DISABLED);
+ public function deleteBackgroundImage(?string $userId = null): void {
+ $userId = $userId ?? $this->getUserId();
+ $this->config->setUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR);
}
- public function getBackground(): ?ISimpleFile {
- $background = $this->config->getUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_DEFAULT);
+ public function getBackground(?string $userId = null): ?ISimpleFile {
+ $userId = $userId ?? $this->getUserId();
+ $background = $this->config->getUserValue($userId, Application::APP_ID, 'background_image', self::BACKGROUND_DEFAULT);
if ($background === self::BACKGROUND_CUSTOM) {
try {
return $this->getAppDataFolder()->getFile('background.jpg');
- } catch (NotFoundException | NotPermittedException $e) {
+ } catch (NotFoundException|NotPermittedException $e) {
return null;
}
}
@@ -259,21 +306,109 @@ class BackgroundService {
}
/**
+ * Called when a new global background (backgroundMime) is uploaded (admin setting)
+ * This sets all necessary app config values
+ * @param resource|string $path
+ * @return string|null The fallback background color - if any
+ */
+ public function setGlobalBackground($path): ?string {
+ $image = new Image();
+ $handle = is_resource($path) ? $path : fopen($path, 'rb');
+
+ if ($handle && $image->loadFromFileHandle($handle) !== false) {
+ $meanColor = $this->calculateMeanColor($image);
+ if ($meanColor !== false) {
+ $this->appConfig->setValueString(Application::APP_ID, 'background_color', $meanColor);
+ return $meanColor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Calculate mean color of an given image
+ * It only takes the upper part into account so that a matching text color can be derived for the app menu
+ */
+ private function calculateMeanColor(Image $image): false|string {
+ /**
+ * Small helper to ensure one channel is returned as 8byte hex
+ */
+ function toHex(int $channel): string {
+ $hex = dechex($channel);
+ return match (strlen($hex)) {
+ 0 => '00',
+ 1 => '0' . $hex,
+ 2 => $hex,
+ default => 'ff',
+ };
+ }
+
+ $tempImage = new Image();
+
+ // Crop to only analyze top bar
+ $resource = $image->cropNew(0, 0, $image->width(), min(max(50, (int)($image->height() * 0.125)), $image->height()));
+ if ($resource === false) {
+ return false;
+ }
+
+ $tempImage->setResource($resource);
+ if (!$tempImage->preciseResize(100, 7)) {
+ return false;
+ }
+
+ $resource = $tempImage->resource();
+ if ($resource === false) {
+ return false;
+ }
+
+ $reds = [];
+ $greens = [];
+ $blues = [];
+ for ($y = 0; $y < 7; $y++) {
+ for ($x = 0; $x < 100; $x++) {
+ $value = imagecolorat($resource, $x, $y);
+ if ($value === false) {
+ continue;
+ }
+ $reds[] = ($value >> 16) & 0xFF;
+ $greens[] = ($value >> 8) & 0xFF;
+ $blues[] = $value & 0xFF;
+ }
+ }
+ $meanColor = '#' . toHex((int)(array_sum($reds) / count($reds)));
+ $meanColor .= toHex((int)(array_sum($greens) / count($greens)));
+ $meanColor .= toHex((int)(array_sum($blues) / count($blues)));
+ return $meanColor;
+ }
+
+ /**
* Storing the data in appdata/theming/users/USERID
*
- * @return ISimpleFolder
+ * @param string|null $userId The user to get the folder - default to current user
* @throws NotPermittedException
*/
- private function getAppDataFolder(): ISimpleFolder {
+ private function getAppDataFolder(?string $userId = null): ISimpleFolder {
+ $userId = $userId ?? $this->getUserId();
+
try {
$rootFolder = $this->appData->getFolder('users');
- } catch (NotFoundException $e) {
+ } catch (NotFoundException) {
$rootFolder = $this->appData->newFolder('users');
}
try {
- return $rootFolder->getFolder($this->userId);
- } catch (NotFoundException $e) {
- return $rootFolder->newFolder($this->userId);
+ return $rootFolder->getFolder($userId);
+ } catch (NotFoundException) {
+ return $rootFolder->newFolder($userId);
+ }
+ }
+
+ /**
+ * @throws RuntimeException Thrown if a method that needs a user is called without any logged-in user
+ */
+ private function getUserId(): string {
+ if ($this->userId === null) {
+ throw new RuntimeException('No currently logged-in user');
}
+ return $this->userId;
}
}
diff --git a/apps/theming/lib/Service/JSDataService.php b/apps/theming/lib/Service/JSDataService.php
index 5dc7b3fd632..81198f8b3f5 100644
--- a/apps/theming/lib/Service/JSDataService.php
+++ b/apps/theming/lib/Service/JSDataService.php
@@ -3,63 +3,47 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Julien Veyssier <eneiluj@posteo.net>
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Theming\Service;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
-use OCP\IConfig;
class JSDataService implements \JsonSerializable {
- private ThemingDefaults $themingDefaults;
- private Util $util;
- private IConfig $appConfig;
- private ThemesService $themesService;
public function __construct(
- ThemingDefaults $themingDefaults,
- Util $util,
- IConfig $appConfig,
- ThemesService $themesService
+ private ThemingDefaults $themingDefaults,
+ private Util $util,
+ private ThemesService $themesService,
) {
$this->themingDefaults = $themingDefaults;
$this->util = $util;
- $this->appConfig = $appConfig;
$this->themesService = $themesService;
}
public function jsonSerialize(): array {
return [
'name' => $this->themingDefaults->getName(),
- 'url' => $this->themingDefaults->getBaseUrl(),
'slogan' => $this->themingDefaults->getSlogan(),
- 'color' => $this->themingDefaults->getColorPrimary(),
- 'defaultColor' => $this->themingDefaults->getDefaultColorPrimary(),
+
+ 'url' => $this->themingDefaults->getBaseUrl(),
'imprintUrl' => $this->themingDefaults->getImprintUrl(),
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
+
+ 'primaryColor' => $this->themingDefaults->getColorPrimary(),
+ 'backgroundColor' => $this->themingDefaults->getColorBackground(),
+ 'defaultPrimaryColor' => $this->themingDefaults->getDefaultColorPrimary(),
+ 'defaultBackgroundColor' => $this->themingDefaults->getDefaultColorBackground(),
'inverted' => $this->util->invertTextColor($this->themingDefaults->getColorPrimary()),
+
'cacheBuster' => $this->util->getCacheBuster(),
'enabledThemes' => $this->themesService->getEnabledThemes(),
+
+ // deprecated use primaryColor
+ 'color' => $this->themingDefaults->getColorPrimary(),
+ '' => 'color is deprecated since Nextcloud 29, use primaryColor instead'
];
}
}
diff --git a/apps/theming/lib/Service/ThemeInjectionService.php b/apps/theming/lib/Service/ThemeInjectionService.php
index c6ef6b9614d..873d388081c 100644
--- a/apps/theming/lib/Service/ThemeInjectionService.php
+++ b/apps/theming/lib/Service/ThemeInjectionService.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Theming\Service;
@@ -31,25 +15,16 @@ use OCP\IUserSession;
class ThemeInjectionService {
- private IURLGenerator $urlGenerator;
- private ThemesService $themesService;
- private DefaultTheme $defaultTheme;
- private Util $util;
- private IConfig $config;
private ?string $userId;
- public function __construct(IURLGenerator $urlGenerator,
- ThemesService $themesService,
- DefaultTheme $defaultTheme,
- Util $util,
- IConfig $config,
- IUserSession $userSession) {
- $this->urlGenerator = $urlGenerator;
- $this->themesService = $themesService;
- $this->defaultTheme = $defaultTheme;
- $this->util = $util;
- $this->config = $config;
-
+ public function __construct(
+ private IURLGenerator $urlGenerator,
+ private ThemesService $themesService,
+ private DefaultTheme $defaultTheme,
+ private Util $util,
+ private IConfig $config,
+ IUserSession $userSession,
+ ) {
if ($userSession->getUser() !== null) {
$this->userId = $userSession->getUser()->getUID();
} else {
@@ -69,12 +44,12 @@ class ThemeInjectionService {
$this->addThemeHeaders($defaultTheme);
// Themes applied by media queries
- foreach($mediaThemes as $theme) {
+ foreach ($mediaThemes as $theme) {
$this->addThemeHeaders($theme, true, $theme->getMediaQuery());
}
// Themes
- foreach($this->themesService->getThemes() as $theme) {
+ foreach ($this->themesService->getThemes() as $theme) {
// Ignore default theme as already processed first
if ($theme->getId() === $this->defaultTheme->getId()) {
continue;
@@ -116,9 +91,9 @@ class ThemeInjectionService {
$metaHeaders = [];
// Meta headers
- foreach($this->themesService->getThemes() as $theme) {
+ foreach ($this->themesService->getThemes() as $theme) {
if (!empty($theme->getMeta())) {
- foreach($theme->getMeta() as $meta) {
+ foreach ($theme->getMeta() as $meta) {
if (!isset($meta['name']) || !isset($meta['content'])) {
continue;
}
@@ -131,7 +106,7 @@ class ThemeInjectionService {
}
}
- foreach($metaHeaders as $name => $content) {
+ foreach ($metaHeaders as $name => $content) {
\OCP\Util::addHeader('meta', [
'name' => $name,
'content' => join(' ', array_unique($content)),
diff --git a/apps/theming/lib/Service/ThemesService.php b/apps/theming/lib/Service/ThemesService.php
index 0d252e96431..f49524cb62c 100644
--- a/apps/theming/lib/Service/ThemesService.php
+++ b/apps/theming/lib/Service/ThemesService.php
@@ -1,24 +1,8 @@
<?php
+
/**
- * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @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/>.
- *
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Theming\Service;
@@ -33,24 +17,23 @@ use OCA\Theming\Themes\LightTheme;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
+use Psr\Log\LoggerInterface;
class ThemesService {
- private IUserSession $userSession;
- private IConfig $config;
-
/** @var ITheme[] */
private array $themesProviders;
- public function __construct(IUserSession $userSession,
- IConfig $config,
- DefaultTheme $defaultTheme,
+ public function __construct(
+ private IUserSession $userSession,
+ private IConfig $config,
+ private LoggerInterface $logger,
+ private DefaultTheme $defaultTheme,
LightTheme $lightTheme,
- DarkTheme $darkTheme,
+ private DarkTheme $darkTheme,
HighContrastTheme $highContrastTheme,
DarkHighContrastTheme $darkHighContrastTheme,
- DyslexiaFont $dyslexiaFont) {
- $this->userSession = $userSession;
- $this->config = $config;
+ DyslexiaFont $dyslexiaFont,
+ ) {
// Register themes
$this->themesProviders = [
@@ -69,6 +52,28 @@ class ThemesService {
* @return ITheme[]
*/
public function getThemes(): array {
+ // Enforced theme if configured
+ $enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
+ if ($enforcedTheme !== '') {
+ if (!isset($this->themesProviders[$enforcedTheme])) {
+ $this->logger->error('Enforced theme not found', ['theme' => $enforcedTheme]);
+ return $this->themesProviders;
+ }
+
+ $defaultTheme = $this->themesProviders[$this->defaultTheme->getId()];
+ $darkTheme = $this->themesProviders[$this->darkTheme->getId()];
+ $theme = $this->themesProviders[$enforcedTheme];
+ return [
+ // Leave the default theme as a fallback
+ $defaultTheme->getId() => $defaultTheme,
+ // Make sure we also have the dark theme to allow apps
+ // to scope sections of their UI to the dark theme
+ $darkTheme->getId() => $darkTheme,
+ // Finally, the enforced theme
+ $theme->getId() => $theme,
+ ];
+ }
+
return $this->themesProviders;
}
@@ -123,7 +128,7 @@ class ThemesService {
$this->setEnabledThemes($enabledThemes);
return $enabledThemes;
}
-
+
return $themesIds;
}
@@ -144,19 +149,21 @@ class ThemesService {
}
/**
- * Get the list of all enabled themes IDs
- * for the logged-in user
+ * Get the list of all enabled themes IDs for the current user.
*
* @return string[]
*/
public function getEnabledThemes(): array {
+ $enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
$user = $this->userSession->getUser();
if ($user === null) {
+ if ($enforcedTheme !== '') {
+ return [$enforcedTheme];
+ }
return [];
}
- $enforcedTheme = $this->config->getSystemValueString('enforce_theme', '');
- $enabledThemes = json_decode($this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', '[]'));
+ $enabledThemes = json_decode($this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', '["default"]'));
if ($enforcedTheme !== '') {
return array_merge([$enforcedTheme], $enabledThemes);
}