aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/lib/Util.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/lib/Util.php')
-rw-r--r--apps/theming/lib/Util.php179
1 files changed, 118 insertions, 61 deletions
diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php
index 9a00bd1d5b1..797456632fc 100644
--- a/apps/theming/lib/Util.php
+++ b/apps/theming/lib/Util.php
@@ -1,65 +1,47 @@
<?php
+
/**
- * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Kesselberg <mail@danielkesselberg.de>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julien Veyssier <eneiluj@posteo.net>
- * @author Julius Haertl <jus@bitgrid.net>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Michael Weimann <mail@michael-weimann.eu>
- *
- * @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: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Theming;
+use Mexitek\PHPColors\Color;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IConfig;
-use Mexitek\PHPColors\Color;
+use OCP\IUserSession;
+use OCP\Server;
+use OCP\ServerVersion;
class Util {
-
- private IConfig $config;
- private IAppManager $appManager;
- private IAppData $appData;
+ public function __construct(
+ private ServerVersion $serverVersion,
+ private IConfig $config,
+ private IAppManager $appManager,
+ private IAppData $appData,
+ private ImageManager $imageManager,
+ ) {
+ }
/**
- * Util constructor.
- *
- * @param IConfig $config
- * @param IAppManager $appManager
- * @param IAppData $appData
+ * Should we invert the text on this background color?
+ * @param string $color rgb color value
+ * @return bool
*/
- public function __construct(IConfig $config, IAppManager $appManager, IAppData $appData) {
- $this->config = $config;
- $this->appManager = $appManager;
- $this->appData = $appData;
+ public function invertTextColor(string $color): bool {
+ return $this->colorContrast($color, '#ffffff') < 4.5;
}
/**
+ * Is this color too bright ?
* @param string $color rgb color value
* @return bool
*/
- public function invertTextColor($color) {
+ public function isBrightColor(string $color): bool {
$l = $this->calculateLuma($color);
if ($l > 0.6) {
return true;
@@ -72,20 +54,43 @@ class Util {
* get color for on-page elements:
* theme color by default, grey if theme color is to bright
* @param string $color
- * @param bool $brightBackground
+ * @param ?bool $brightBackground
* @return string
*/
- public function elementColor($color, bool $brightBackground = true) {
+ public function elementColor($color, ?bool $brightBackground = null, ?string $backgroundColor = null, bool $highContrast = false) {
+ if ($backgroundColor !== null) {
+ $brightBackground = $brightBackground ?? $this->isBrightColor($backgroundColor);
+ // Minimal amount that is possible to change the luminance
+ $epsilon = 1.0 / 255.0;
+ // Current iteration to prevent infinite loops
+ $iteration = 0;
+ // We need to keep blurred backgrounds in mind which might be mixed with the background
+ $blurredBackground = $this->mix($backgroundColor, $brightBackground ? $color : '#ffffff', 66);
+ $contrast = $this->colorContrast($color, $blurredBackground);
+
+ // Min. element contrast is 3:1 but we need to keep hover states in mind -> min 3.2:1
+ $minContrast = $highContrast ? 5.6 : 3.2;
+
+ while ($contrast < $minContrast && $iteration++ < 100) {
+ $hsl = Color::hexToHsl($color);
+ $hsl['L'] = max(0, min(1, $hsl['L'] + ($brightBackground ? -$epsilon : $epsilon)));
+ $color = '#' . Color::hslToHex($hsl);
+ $contrast = $this->colorContrast($color, $blurredBackground);
+ }
+ return $color;
+ }
+
+ // Fallback for legacy calling
$luminance = $this->calculateLuminance($color);
- if ($brightBackground && $luminance > 0.8) {
- // If the color is too bright in bright mode, we fall back to a darker gray
- return '#aaaaaa';
+ if ($brightBackground !== false && $luminance > 0.8) {
+ // If the color is too bright in bright mode, we fall back to a darkened color
+ return $this->darken($color, 30);
}
- if (!$brightBackground && $luminance < 0.2) {
- // If the color is too dark in dark mode, we fall back to a brighter gray
- return '#555555';
+ if ($brightBackground !== true && $luminance < 0.2) {
+ // If the color is too dark in dark mode, we fall back to a brightened color
+ return $this->lighten($color, 30);
}
return $color;
@@ -133,12 +138,38 @@ class Util {
}
/**
+ * Calculate the Luma according to WCAG 2
+ * http://www.w3.org/TR/WCAG20/#relativeluminancedef
* @param string $color rgb color value
* @return float
*/
public function calculateLuma(string $color): float {
- [$red, $green, $blue] = $this->hexToRGB($color);
- return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
+ $rgb = $this->hexToRGB($color);
+
+ // Normalize the values by converting to float and applying the rules from WCAG2.0
+ $rgb = array_map(function (int $color) {
+ $color = $color / 255.0;
+ if ($color <= 0.03928) {
+ return $color / 12.92;
+ } else {
+ return pow((($color + 0.055) / 1.055), 2.4);
+ }
+ }, $rgb);
+
+ [$red, $green, $blue] = $rgb;
+ return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
+ }
+
+ /**
+ * Calculat the color contrast according to WCAG 2
+ * http://www.w3.org/TR/WCAG20/#contrast-ratiodef
+ * @param string $color1 The first color
+ * @param string $color2 The second color
+ */
+ public function colorContrast(string $color1, string $color2): float {
+ $luminance1 = $this->calculateLuma($color1) + 0.05;
+ $luminance2 = $this->calculateLuma($color2) + 0.05;
+ return max($luminance1, $luminance2) / min($luminance1, $luminance2);
}
/**
@@ -156,18 +187,18 @@ class Util {
* @return string base64 encoded radio button svg
*/
public function generateRadioButton($color) {
- $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">' .
- '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="'.$color.'"/></svg>';
+ $radioButtonIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">'
+ . '<path d="M8 1a7 7 0 0 0-7 7 7 7 0 0 0 7 7 7 7 0 0 0 7-7 7 7 0 0 0-7-7zm0 1a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6zm0 2a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" fill="' . $color . '"/></svg>';
return base64_encode($radioButtonIcon);
}
/**
- * @param $app string app name
+ * @param string $app app name
* @return string|ISimpleFile path to app icon / file of logo
*/
public function getAppIcon($app) {
- $app = str_replace(['\0', '/', '\\', '..'], '', $app);
+ $app = $this->appManager->cleanAppId($app);
try {
$appPath = $this->appManager->getAppPath($app);
$icon = $appPath . '/img/' . $app . '.svg';
@@ -184,7 +215,7 @@ class Util {
if ($this->config->getAppValue('theming', 'logoMime', '') !== '') {
$logoFile = null;
try {
- $folder = $this->appData->getFolder('images');
+ $folder = $this->appData->getFolder('global/images');
return $folder->getFile('logo');
} catch (NotFoundException $e) {
}
@@ -193,14 +224,17 @@ class Util {
}
/**
- * @param $app string app name
- * @param $image string relative path to image in app folder
+ * @param string $app app name
+ * @param string $image relative path to image in app folder
* @return string|false absolute path to image
*/
public function getAppImage($app, $image) {
- $app = str_replace(['\0', '/', '\\', '..'], '', $app);
+ $app = $this->appManager->cleanAppId($app);
+ /**
+ * @psalm-taint-escape file
+ */
$image = str_replace(['\0', '\\', '..'], '', $image);
- if ($app === "core") {
+ if ($app === 'core') {
$icon = \OC::$SERVERROOT . '/core/img/' . $image;
if (file_exists($icon)) {
return $icon;
@@ -240,8 +274,8 @@ class Util {
/**
* replace default color with a custom one
*
- * @param $svg string content of a svg file
- * @param $color string color to match
+ * @param string $svg content of a svg file
+ * @param string $color color to match
* @return string
*/
public function colorizeSvg($svg, $color) {
@@ -266,4 +300,27 @@ class Util {
$backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime', '');
return $backgroundLogo !== '' && $backgroundLogo !== 'backgroundColor';
}
+
+ public function isLogoThemed() {
+ return $this->imageManager->hasImage('logo')
+ || $this->imageManager->hasImage('logoheader');
+ }
+
+ public function getCacheBuster(): string {
+ $userSession = Server::get(IUserSession::class);
+ $userId = '';
+ $user = $userSession->getUser();
+ if (!is_null($user)) {
+ $userId = $user->getUID();
+ }
+ $serverVersion = $this->serverVersion->getVersionString();
+ $themingAppVersion = $this->appManager->getAppVersion('theming');
+ $userCacheBuster = '';
+ if ($userId) {
+ $userCacheBusterValue = (int)$this->config->getUserValue($userId, 'theming', 'userCacheBuster', '0');
+ $userCacheBuster = $userId . '_' . $userCacheBusterValue;
+ }
+ $systemCacheBuster = $this->config->getAppValue('theming', 'cachebuster', '0');
+ return substr(sha1($serverVersion . $themingAppVersion . $userCacheBuster . $systemCacheBuster), 0, 8);
+ }
}