1105 lines
34 KiB
PHP
1105 lines
34 KiB
PHP
<?php
|
|
|
|
namespace Leantime\Core\UI;
|
|
|
|
use Exception;
|
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
|
use Illuminate\Support\Str;
|
|
use Leantime\Core\Configuration\AppSettings;
|
|
use Leantime\Core\Configuration\Environment;
|
|
use Leantime\Core\Events\DispatchesEvents;
|
|
use Leantime\Core\Events\EventDispatcher;
|
|
use Leantime\Core\Files\FileManager;
|
|
use Leantime\Core\Language;
|
|
use Leantime\Domain\Auth\Services\Auth;
|
|
use Leantime\Domain\Setting\Repositories\Setting;
|
|
use Symfony\Component\HttpFoundation\Cookie;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
/**
|
|
* theme - Engine for handling themes
|
|
*/
|
|
class Theme
|
|
{
|
|
use DispatchesEvents;
|
|
|
|
/**
|
|
* Name of default theme
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const DEFAULT = 'default';
|
|
|
|
/**
|
|
* Theme configuration file (excluding .ini extension)
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const DEFAULT_INI = 'theme';
|
|
|
|
/**
|
|
* Theme style file (excluding .css extension)
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const DEFAULT_CSS = 'light';
|
|
|
|
/**
|
|
* Theme JavaScript library (excluding .js extension)
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const DEFAULT_JS = 'theme';
|
|
|
|
/**
|
|
* Theme default logo
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const DEFAULT_LOGO = '/dist/images/logo.svg';
|
|
|
|
/**
|
|
* Theme style customization file (excluding .css extension)
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const CUSTOM_CSS = 'custom';
|
|
|
|
/**
|
|
* Theme JavaScript customization file (excluding .js extension)
|
|
*
|
|
* @var string
|
|
*
|
|
* @static
|
|
*
|
|
* @final
|
|
*/
|
|
public const CUSTOM_JS = 'custom';
|
|
|
|
private Environment $config;
|
|
|
|
private Setting $settingsRepo;
|
|
|
|
private Language $language;
|
|
|
|
private AppSettings $appSettings;
|
|
|
|
private FileManager $fileManager;
|
|
|
|
private array|false $iniData;
|
|
|
|
private array $colorSchemes = [
|
|
'grayscale1' => [
|
|
'name' => 'Grayscale',
|
|
'primaryColor' => '#000000',
|
|
'secondaryColor' => '#757575',
|
|
],
|
|
'grayscale2' => [
|
|
'name' => 'Grayscale Reverse',
|
|
'primaryColor' => '#d1d1d1',
|
|
'secondaryColor' => '#000000',
|
|
],
|
|
'themeDefault' => 'themeDefault',
|
|
'companyColors' => 'companyColors',
|
|
];
|
|
|
|
/**
|
|
* Get color scheme values adjusted for current color mode.
|
|
* Ensures WCAG 2.1 AA contrast compliance in both light and dark modes.
|
|
*
|
|
* @param string $schemeName The identifier of the color scheme
|
|
* @param array $scheme The original color scheme definition
|
|
* @return array The color scheme with mode-appropriate values
|
|
*
|
|
* @api
|
|
*/
|
|
private function getColorSchemeForMode(string $schemeName, array $scheme): array
|
|
{
|
|
$colorMode = $this->getColorMode();
|
|
|
|
// Adjust grayscale schemes for dark mode to ensure readability
|
|
if ($colorMode === 'dark') {
|
|
if ($schemeName === 'grayscale1') {
|
|
return [
|
|
'name' => $scheme['name'],
|
|
'primaryColor' => '#e8e8e8', // Light gray - 8.5:1 contrast on #292929
|
|
'secondaryColor' => '#b0b0b0', // Medium gray - 4.2:1 contrast
|
|
];
|
|
}
|
|
if ($schemeName === 'grayscale2') {
|
|
return [
|
|
'name' => $scheme['name'],
|
|
'primaryColor' => '#a0a0a0', // Light gray - 5.0:1 contrast on #292929
|
|
'secondaryColor' => '#d1d1d1', // Light gray - 9.1:1 contrast
|
|
];
|
|
}
|
|
}
|
|
|
|
// Light mode: use original values
|
|
return $scheme;
|
|
}
|
|
|
|
/**
|
|
* possible font choices
|
|
*/
|
|
public array $fonts = [
|
|
'roboto' => 'Roboto',
|
|
'atkinson' => 'Atkinson Hyperlegible',
|
|
'shantell' => 'Shantell Sans',
|
|
];
|
|
|
|
/**
|
|
* possible font choices
|
|
*/
|
|
public array $fontTooltips = [
|
|
'roboto' => 'Designed to be easy to read on a variety of devices.',
|
|
'atkinson' => 'Atkinson was specifically developed for readers with low vision.',
|
|
'shantell' => 'The shape of the letters and increased spacing makes words less crowded and easier to read.',
|
|
];
|
|
|
|
/**
|
|
* __construct - Constructor
|
|
*/
|
|
public function __construct(
|
|
Environment $config,
|
|
Setting $settingsRepo,
|
|
Language $language,
|
|
AppSettings $appSettings,
|
|
FileManager $fileManager
|
|
) {
|
|
$this->config = $config;
|
|
$this->settingsRepo = $settingsRepo;
|
|
$this->iniData = [];
|
|
$this->language = $language;
|
|
$this->appSettings = $appSettings;
|
|
$this->fileManager = $fileManager;
|
|
}
|
|
|
|
/**
|
|
* Batch-preload all user theme settings into the SettingCache in a single query.
|
|
* Called once on first page load after login to avoid 8-11 individual getSetting() calls
|
|
* as each theme getter (getActive, getColorMode, getColorScheme, getFont, etc.) fires.
|
|
*
|
|
* The preloaded values are cached by SettingCache's in-memory tier, so subsequent
|
|
* individual getSetting() calls within the same request are instant.
|
|
*/
|
|
public function preloadUserSettings(): void
|
|
{
|
|
if (! Auth::isLoggedIn()) {
|
|
return;
|
|
}
|
|
|
|
$userId = session('userdata.id');
|
|
if (! $userId) {
|
|
return;
|
|
}
|
|
|
|
// If session already has theme data, skip preloading
|
|
if (session()->exists('usersettings.theme')) {
|
|
return;
|
|
}
|
|
|
|
$keys = [
|
|
"usersettings.$userId.theme",
|
|
"usersettings.$userId.colorMode",
|
|
"usersettings.$userId.colorScheme",
|
|
"usersettings.$userId.themeFont",
|
|
"usersettings.$userId.backgroundType",
|
|
"usersettings.$userId.backgroundImage",
|
|
'companysettings.primarycolor',
|
|
'companysettings.secondarycolor',
|
|
'companysettings.sitename',
|
|
'companysettings.logoPath',
|
|
];
|
|
|
|
// Single batch query -- results are stored in SettingCache's in-memory tier
|
|
$this->settingsRepo->getSettingsForKeys($keys);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the available color schemes.
|
|
*
|
|
* @return array The available color schemes.
|
|
*/
|
|
public function getAvailableColorSchemes(): array
|
|
{
|
|
|
|
$this->readIniData();
|
|
|
|
// Apply color mode adjustments to predefined schemes
|
|
$parsedColorSchemes = [];
|
|
foreach ($this->colorSchemes as $key => $scheme) {
|
|
if (is_array($scheme)) {
|
|
$parsedColorSchemes[$key] = $this->getColorSchemeForMode($key, $scheme);
|
|
} else {
|
|
$parsedColorSchemes[$key] = $scheme;
|
|
}
|
|
}
|
|
$parsedColorSchemes['themeDefault'] = [
|
|
'name' => 'Leantime',
|
|
'primaryColor' => $this->iniData['general']['primaryColor'] ?? $this->colorSchemes['themeDefault']['primaryColor'],
|
|
'secondaryColor' => $this->iniData['general']['secondaryColor'] ?? $this->colorSchemes['themeDefault']['secondaryColor'],
|
|
];
|
|
|
|
$primaryColor = $this->settingsRepo->getSetting('companysettings.primarycolor') ? $this->settingsRepo->getSetting('companysettings.primarycolor') : null;
|
|
$secondaryColor = $this->settingsRepo->getSetting('companysettings.secondarycolor') ? $this->settingsRepo->getSetting('companysettings.secondarycolor') : null;
|
|
|
|
$parsedColorSchemes['companyColors'] = [
|
|
'name' => 'label.companyColors',
|
|
'primaryColor' => $primaryColor ?? $this->config->primarycolor ?? $parsedColorSchemes['themeDefault']['primaryColor'],
|
|
'secondaryColor' => $secondaryColor ?? $this->config->secondarycolor ?? $parsedColorSchemes['themeDefault']['secondaryColor'],
|
|
];
|
|
|
|
if (
|
|
$parsedColorSchemes['companyColors']['primaryColor'] == $parsedColorSchemes['themeDefault']['primaryColor'] &&
|
|
$parsedColorSchemes['companyColors']['secondaryColor'] == $parsedColorSchemes['themeDefault']['secondaryColor']) {
|
|
unset($parsedColorSchemes['companyColors']);
|
|
}
|
|
|
|
$colorschemes = self::dispatchFilter('colorschemes', $parsedColorSchemes);
|
|
|
|
return $colorschemes;
|
|
}
|
|
|
|
public function getAvailableFonts()
|
|
{
|
|
return self::dispatchFilter('fonts', $this->fonts);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the user's background image URL.
|
|
* Caches the result in session to avoid a DB query on every page load.
|
|
*
|
|
* @return string|null The background image URL, or null if not logged in.
|
|
*/
|
|
public function getBackgroundImage(): ?string
|
|
{
|
|
if (Auth::isLoggedIn()) {
|
|
if (session()->exists('usersettings.backgroundImage')) {
|
|
return session('usersettings.backgroundImage') ?: null;
|
|
}
|
|
|
|
$image = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.backgroundImage');
|
|
session(['usersettings.backgroundImage' => $image ?: '']);
|
|
|
|
return $image ?: null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function setBackgroundImage(string $url): void
|
|
{
|
|
if (Auth::isLoggedIn()) {
|
|
$this->settingsRepo->saveSetting('usersettings.'.session('userdata.id').'.backgroundType', 'image');
|
|
$this->settingsRepo->saveSetting('usersettings.'.session('userdata.id').'.backgroundImage', $url);
|
|
session(['usersettings.backgroundType' => 'image']);
|
|
session(['usersettings.backgroundImage' => $url]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the user's background type (gradient or image).
|
|
* Caches the result in session to avoid a DB query on every page load.
|
|
*
|
|
* @return string The background type.
|
|
*/
|
|
public function getBackgroundType(): string
|
|
{
|
|
if (Auth::isLoggedIn()) {
|
|
if (session()->exists('usersettings.backgroundType')) {
|
|
return session('usersettings.backgroundType') ?: 'gradient';
|
|
}
|
|
|
|
$type = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.backgroundType');
|
|
session(['usersettings.backgroundType' => $type ?: 'gradient']);
|
|
|
|
return $type ?: 'gradient';
|
|
}
|
|
|
|
return 'gradient';
|
|
}
|
|
|
|
public function setBackgroundType(string $type): void
|
|
{
|
|
if (Auth::isLoggedIn()) {
|
|
$this->settingsRepo->saveSetting('usersettings.'.session('userdata.id').'.backgroundType', $type);
|
|
session(['usersettings.backgroundType' => $type]);
|
|
if ($type == 'gradient') {
|
|
$this->settingsRepo->deleteSetting('usersettings.'.session('userdata.id').'.backgroundImage');
|
|
session(['usersettings.backgroundImage' => '']);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* getActive - Return active theme id
|
|
*
|
|
* @return string Active theme identifier
|
|
*/
|
|
public function getActive(): string
|
|
{
|
|
|
|
// Reset .ini data
|
|
$this->iniData = [];
|
|
|
|
if (session()->exists('usersettings.theme') && Auth::isLoggedIn()) {
|
|
return session('usersettings.theme');
|
|
}
|
|
|
|
// Return user specific theme, if active
|
|
// This is an active logged in session.
|
|
if (Auth::isLoggedIn()) {
|
|
// User is logged in, we don't have a theme yet, check settings
|
|
$theme = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.theme');
|
|
if ($theme !== false) {
|
|
$this->setActive($theme);
|
|
|
|
return $theme;
|
|
}
|
|
}
|
|
|
|
// No generic theme set. Check if cookie is set
|
|
if (isset($_COOKIE['theme'])) {
|
|
$this->setActive($_COOKIE['theme']);
|
|
|
|
return $_COOKIE['theme'];
|
|
}
|
|
|
|
// Return configured
|
|
// Nothing set, get default theme from config
|
|
if (isset($this->config->defaultTheme) && ! empty($this->config->defaultTheme)) {
|
|
$this->setActive($this->config->defaultTheme);
|
|
|
|
return $this->config->defaultTheme;
|
|
}
|
|
|
|
// Return default
|
|
return static::DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* getColorMode - Return active color mode
|
|
*
|
|
* @return string Active theme identifier
|
|
*/
|
|
public function getColorMode()
|
|
{
|
|
|
|
// Return generic theme
|
|
if (session()->exists('usersettings.colorMode') && Auth::isLoggedIn()) {
|
|
return session('usersettings.colorMode');
|
|
}
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
// User is logged in, we don't have a theme yet, check settings
|
|
$colorMode = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.colorMode');
|
|
if ($colorMode !== false) {
|
|
$this->setColorMode($colorMode);
|
|
|
|
return $colorMode;
|
|
}
|
|
}
|
|
|
|
// No generic theme set. Check if cookie is set
|
|
if (isset($_COOKIE['colorMode'])) {
|
|
$this->setColorMode($_COOKIE['colorMode']);
|
|
|
|
return $_COOKIE['colorMode'];
|
|
}
|
|
|
|
// Return default
|
|
session(['usersettings.colorMode' => 'light']);
|
|
|
|
return 'light';
|
|
}
|
|
|
|
/**
|
|
* getColorScheme - Return the active color scheme
|
|
* Color schemes can be chosen by the user and can be themedefault, company colors or other predefined schemes
|
|
* The colors that change are accent1 and accent2
|
|
*
|
|
* @return string Active theme identifier
|
|
*/
|
|
public function getColorScheme()
|
|
{
|
|
|
|
// Return generic theme
|
|
if (session()->exists('usersettings.colorScheme') && Auth::isLoggedIn()) {
|
|
$this->setAccentColors(session('usersettings.colorScheme'));
|
|
|
|
return session('usersettings.colorScheme');
|
|
}
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
// User is logged in, we don't have a theme yet, check settings
|
|
|
|
$colorScheme = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.colorScheme');
|
|
if ($colorScheme !== false) {
|
|
$this->setColorScheme($colorScheme);
|
|
|
|
return $colorScheme;
|
|
}
|
|
}
|
|
|
|
if (isset($_COOKIE['colorScheme'])) {
|
|
$this->setColorScheme($_COOKIE['colorScheme']);
|
|
|
|
return $_COOKIE['colorScheme'];
|
|
}
|
|
|
|
if (! empty($this->config->primarycolor) && ! empty($this->config->secondarycolor)) {
|
|
// Return default
|
|
$this->setColorScheme('companyColors');
|
|
|
|
return 'companyColors';
|
|
} else {
|
|
// Return default
|
|
$this->setColorScheme('themeDefault');
|
|
|
|
return 'themeDefault';
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* getFont - Return active font
|
|
*
|
|
* @return string Active theme identifier
|
|
*/
|
|
public function getFont()
|
|
{
|
|
|
|
// Return generic theme
|
|
if (session()->exists('usersettings.themeFont') && Auth::isLoggedIn()) {
|
|
$this->setFont(session('usersettings.themeFont'));
|
|
|
|
return session('usersettings.themeFont');
|
|
}
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
|
|
// User is logged in, we don't have a theme yet, check settings
|
|
$themeFont = $this->settingsRepo->getSetting('usersettings.'.session('userdata.id').'.themeFont');
|
|
if ($themeFont !== false) {
|
|
$this->setFont($themeFont);
|
|
|
|
return $themeFont;
|
|
}
|
|
}
|
|
|
|
if (isset($_COOKIE['themeFont'])) {
|
|
$this->setFont($_COOKIE['themeFont']);
|
|
|
|
return $_COOKIE['themeFont'];
|
|
}
|
|
|
|
// Return default
|
|
$this->setFont('roboto');
|
|
|
|
return 'roboto';
|
|
}
|
|
|
|
/**
|
|
* setActive - Set active theme
|
|
*
|
|
* Note: After setActive, the language settings need to be reloaded/reset, because languages are theme specific
|
|
*
|
|
* @param string $id Active theme identifier.
|
|
*
|
|
* @throws Exception Exception if theme does not exist.
|
|
*/
|
|
public function setActive(string $id): void
|
|
{
|
|
|
|
if ($id == '') {
|
|
$id = static::DEFAULT;
|
|
}
|
|
|
|
// not a valid theme. Use default
|
|
if (! is_dir(ROOT.'/theme/'.$id) || ! file_exists(ROOT.'/theme/'.$id.'/'.static::DEFAULT_INI.'.ini')) {
|
|
$id = static::DEFAULT;
|
|
}
|
|
|
|
// Only set if user is logged in
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.theme' => $id]);
|
|
}
|
|
|
|
EventDispatcher::addFilterListener(
|
|
'leantime.core.http.httpkernel.handle.beforeSendResponse',
|
|
fn ($response) => tap($response, fn (Response $response) => $response->headers->setCookie(
|
|
Cookie::create('theme')
|
|
->withValue($id)
|
|
->withExpires(time() + 60 * 60 * 24 * 30)
|
|
->withPath(Str::finish($this->config->appDir, '/'))
|
|
->withSameSite('strict')
|
|
))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* setColorModel - Set active theme
|
|
*
|
|
*
|
|
* @param string $colorMode color mode of theme (light, dark).
|
|
*/
|
|
public function setColorMode(string $colorMode): void
|
|
{
|
|
if ($colorMode == '') {
|
|
$colorMode = 'light';
|
|
}
|
|
|
|
// Only store colors in session for logged in users
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.colorMode' => $colorMode]);
|
|
|
|
// Clear cached color values to force reload with new mode-aware values
|
|
session()->forget('usersettings.colors.primaryColor');
|
|
session()->forget('usersettings.colors.secondaryColor');
|
|
|
|
// Refresh accent colors for current color scheme with new mode
|
|
$currentScheme = session('usersettings.colorScheme');
|
|
if ($currentScheme) {
|
|
$this->setAccentColors($currentScheme);
|
|
}
|
|
}
|
|
|
|
EventDispatcher::addFilterListener(
|
|
'leantime.core.http.httpkernel.handle.beforeSendResponse',
|
|
fn ($response) => tap($response, fn (Response $response) => $response->headers->setCookie(
|
|
Cookie::create('colorMode')
|
|
->withValue($colorMode)
|
|
->withExpires(time() + 60 * 60 * 24 * 30)
|
|
->withPath(Str::finish($this->config->appDir, '/'))
|
|
->withSameSite('strict')
|
|
))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* setFont - Set active font
|
|
*
|
|
*
|
|
* @param string $font font name key (roboto, atkinson).
|
|
*/
|
|
public function setFont(string $font): void
|
|
{
|
|
|
|
if ($font == '') {
|
|
$font = 'roboto';
|
|
}
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.themeFont' => $font]);
|
|
}
|
|
|
|
EventDispatcher::addFilterListener(
|
|
'leantime.core.http.httpkernel.handle.beforeSendResponse',
|
|
fn ($response) => tap($response, fn (Response $response) => $response->headers->setCookie(
|
|
Cookie::create('themeFont')
|
|
->withValue($font)
|
|
->withExpires(time() + 60 * 60 * 24 * 30)
|
|
->withPath(Str::finish($this->config->appDir, '/'))
|
|
->withSameSite('strict')
|
|
))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* setColorScheme - Set active theme
|
|
*
|
|
*
|
|
* @param string $colorScheme color scheme of theme (themeDefault, companyColors).
|
|
*/
|
|
public function setColorScheme(string $colorScheme): void
|
|
{
|
|
|
|
if ($colorScheme == '') {
|
|
$colorScheme = 'themeDefault';
|
|
}
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.colorScheme' => $colorScheme]);
|
|
$this->setAccentColors($colorScheme);
|
|
}
|
|
|
|
EventDispatcher::addFilterListener(
|
|
'leantime.core.http.httpkernel.handle.beforeSendResponse',
|
|
fn ($response) => tap($response, fn (Response $response) => $response->headers->setCookie(
|
|
Cookie::create('colorScheme')
|
|
->withValue($colorScheme)
|
|
->withExpires(time() + 60 * 60 * 24 * 30)
|
|
->withPath(Str::finish($this->config->appDir, '/'))
|
|
->withSameSite('strict')
|
|
))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* getAll - Return an array of all themes
|
|
*
|
|
* @return array return an array of all themes
|
|
*
|
|
* @throws BindingResolutionException
|
|
*/
|
|
public function getAll(): array
|
|
{
|
|
$theme = $this->getActive();
|
|
|
|
$themes = [];
|
|
|
|
$handle = opendir(ROOT.'/theme');
|
|
if ($handle === false) {
|
|
return $themes;
|
|
}
|
|
|
|
while (false !== ($themeDir = readdir($handle))) {
|
|
if ($themeDir == '.' || $themeDir == '..') {
|
|
continue;
|
|
}
|
|
|
|
// Ready theme ini
|
|
$themeIni = ROOT
|
|
.'/theme/'
|
|
.$themeDir
|
|
.'/theme.ini';
|
|
|
|
if (file_exists($themeIni)) {
|
|
$iniData = parse_ini_file(
|
|
$themeIni,
|
|
true,
|
|
INI_SCANNER_RAW
|
|
);
|
|
|
|
if (isset($iniData['general']['name']) && $iniData['general']['name'] !== null) {
|
|
$themes[$themeDir] = $iniData['general'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $themes;
|
|
}
|
|
|
|
/**
|
|
* getDir - Return the root directory of the currently active theme
|
|
*
|
|
* @return string Root directory of currently active theme
|
|
*/
|
|
public function getDir(): string
|
|
{
|
|
|
|
return ROOT.'/theme/'.$this->getActive();
|
|
}
|
|
|
|
/**
|
|
* getDir - Return the root directory of the default theme
|
|
*
|
|
* @return string Root directory of default theme
|
|
*/
|
|
public function getDefaultDir(): string
|
|
{
|
|
|
|
return ROOT.'/theme/'.static::DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* getUrl() - Return an URL pointing to the root directory of the currently active theme
|
|
*
|
|
* @return string Root URL currently active theme
|
|
*/
|
|
public function getUrl(): string
|
|
{
|
|
|
|
return $this->config->appUrl.'/theme/'.$this->getActive();
|
|
}
|
|
|
|
/**
|
|
* getDefaultUrl() - Return an URL pointing to the root directory of the default theme
|
|
*
|
|
* @return string Root URL default theme
|
|
*/
|
|
public function getDefaultUrl(): string
|
|
{
|
|
|
|
return ROOT.'/theme/'.static::DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* getStyleUrl - Return URL that allows loading the style file of the theme
|
|
*
|
|
* @return string|false URL to the css style file of the current theme or false, if it does not exist
|
|
*/
|
|
public function getStyleUrl(): string|false
|
|
{
|
|
return $this->getAssetPath($this->getColorMode(), 'css');
|
|
}
|
|
|
|
/**
|
|
* getCustomStyleUrl - Return URL that allows loading the customized part of the style file of the theme
|
|
*
|
|
* @return string|false URL to the customized part of the css style file of the current theme or false, if it does not exist
|
|
*/
|
|
public function getCustomStyleUrl(): string|false
|
|
{
|
|
return $this->getAssetPath(static::CUSTOM_CSS, 'css');
|
|
}
|
|
|
|
/**
|
|
* getJsUrl - Return URL that allows loading the JavaScript file of the theme
|
|
*
|
|
* @return string|false URL to the JavaScript file of the current theme or false, if it does not exist
|
|
*/
|
|
public function getJsUrl(): string|false
|
|
{
|
|
return $this->getAssetPath(static::DEFAULT_JS, 'js');
|
|
}
|
|
|
|
/**
|
|
* getCustomJsUrl - Return URL that allows loading the customized part of the JavaScript file of the theme
|
|
*
|
|
* @return string|false URL to the customized part of the JavaScript file of the current theme or false, if it does not exist
|
|
*/
|
|
public function getCustomJsUrl(): string|false
|
|
{
|
|
return $this->getAssetPath(static::CUSTOM_JS, 'js');
|
|
}
|
|
|
|
/**
|
|
* getAssetPath - Get localized name of theme
|
|
*
|
|
* @param string $fileName Filename of asset without extension.
|
|
* @param string $assetType Asset type either js or css.
|
|
* @return string|bool returns file path to asset. false if file does not exist
|
|
*/
|
|
private function getAssetPath(string $fileName, string $assetType): string|bool
|
|
{
|
|
if ($fileName == '' || ($assetType != 'css' && $assetType != 'js')) {
|
|
return false;
|
|
}
|
|
|
|
if (file_exists($this->getDir().'/'.$assetType.'/'.$fileName.'.min.'.$assetType)) {
|
|
return $this->getUrl().'/'.$assetType.'/'.$fileName.'.min.'.$assetType.'?v='.$this->appSettings->appVersion;
|
|
}
|
|
|
|
if (file_exists($this->getDir().'/'.$assetType.'/'.$fileName.'.'.$assetType)) {
|
|
return $this->getUrl().'/'.$assetType.'/'.$fileName.'.'.$assetType.'?v='.$this->appSettings->appVersion;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the name of the theme.
|
|
*
|
|
* First, it checks if the INI data is empty. If it is, the method tries to read the INI data.
|
|
* If an exception occurs during the reading process, it is logged in the error log and the method returns
|
|
* the language translation of the active theme name using the "__" method of the $language object.
|
|
*
|
|
* If the INI data contains a 'name' key, it returns the corresponding value.
|
|
*
|
|
* If none of the above conditions are met, it returns the language translation of the active theme name
|
|
* using the "__" method of the $language object.
|
|
*
|
|
* @return string The name of the theme.
|
|
*/
|
|
public function getName(): string
|
|
{
|
|
|
|
if (empty($this->iniData)) {
|
|
try {
|
|
$this->readIniData();
|
|
} catch (Exception $e) {
|
|
report($e);
|
|
|
|
return $this->language->__('theme.'.$this->getActive().'name');
|
|
}
|
|
}
|
|
|
|
if (isset($this->iniData['name'])) {
|
|
return $this->iniData['name'];
|
|
}
|
|
|
|
return $this->language->__('theme.'.$this->getActive().'name');
|
|
}
|
|
|
|
/**
|
|
* Retrieves the version number from the initialization data or returns an empty string if not available.
|
|
*
|
|
* @return string The version number.
|
|
*/
|
|
public function getVersion(): string
|
|
{
|
|
|
|
if (empty($this->iniData)) {
|
|
try {
|
|
$this->readIniData();
|
|
} catch (Exception $e) {
|
|
report($e);
|
|
|
|
return '';
|
|
}
|
|
}
|
|
|
|
if (isset($this->iniData['general']['version'])) {
|
|
return $this->iniData['general']['version'];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Retrieves the URL of the company logo from the user's settings or the default logo path.
|
|
*
|
|
* @return string|false The URL of the company logo, or false if the company doesn't have a logo.
|
|
*/
|
|
public function getLogoUrl(): string|false
|
|
{
|
|
|
|
// Session Logo Path needs to be set here
|
|
// Logo will be in there. Session will be renewed when new logo is updated or theme is changed
|
|
|
|
$logoPath = false;
|
|
if (session()->exists('companysettings.logoPath') === false
|
|
|| session('companysettings.logoPath') == '') {
|
|
|
|
$logoPath = $this->settingsRepo->getSetting('companysettings.logoPath');
|
|
|
|
if ($logoPath === false) {
|
|
session(['companysettings.logoPath' => false]);
|
|
|
|
return false;
|
|
}
|
|
|
|
// File comes from config
|
|
if (str_starts_with($logoPath, 'http')) {
|
|
session(['companysettings.logoPath' => $logoPath]);
|
|
|
|
return session('companysettings.logoPath');
|
|
}
|
|
|
|
// File was uploaded. Check if we can find it
|
|
$fileUrl = $this->fileManager->getFileUrl($logoPath, 'public', (60 * 24));
|
|
if ($fileUrl) {
|
|
session(['companysettings.logoPath' => $fileUrl]);
|
|
|
|
return session('companysettings.logoPath');
|
|
}
|
|
|
|
// If we can't find a logo in the db, the company doesn't have a logo. Stop trying
|
|
session(['companysettings.logoPath' => false]);
|
|
|
|
}
|
|
|
|
return session('companysettings.logoPath');
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the accent colors for the specified color scheme.
|
|
*
|
|
* @param string $colorScheme The name of the color scheme.
|
|
* @return void
|
|
*/
|
|
public function setAccentColors(string $colorScheme)
|
|
{
|
|
|
|
if ($colorScheme == 'themeDefault') {
|
|
$this->setThemeDefaultColors();
|
|
} elseif ($colorScheme == 'companyColors') {
|
|
$this->setCompanyColors();
|
|
} else {
|
|
$this->setSchemeColors($colorScheme);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the default theme colors in the user's settings.
|
|
*
|
|
* This method sets the primary and secondary colors of the theme to the default values by assigning false to the corresponding session variables.
|
|
*/
|
|
public function setThemeDefaultColors()
|
|
{
|
|
// Using default css values
|
|
session(['usersettings.colors.primaryColor' => false]);
|
|
session(['usersettings.colors.secondaryColor' => false]);
|
|
}
|
|
|
|
/**
|
|
* Sets the company colors in the user's settings.
|
|
*
|
|
* If the primary color setting is not already set in the user's settings,
|
|
* it retrieves the primary color setting from the company settings. If the
|
|
* primary color setting is not found in the company settings, it sets the
|
|
* primary and secondary colors to the values specified in the application's
|
|
* config file.
|
|
*
|
|
* If the secondary color setting is found in the company settings, it
|
|
* sets the secondary color in the user's settings as well.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setCompanyColors()
|
|
{
|
|
|
|
if (! session()->exists('usersettings.colors.primaryColor')) {
|
|
|
|
$primaryColor = $this->settingsRepo->getSetting('companysettings.primarycolor');
|
|
|
|
if ($primaryColor !== false) {
|
|
session(['usersettings.colors.primaryColor' => $primaryColor]);
|
|
session(['usersettings.colors.secondaryColor' => $primaryColor]);
|
|
} else {
|
|
session(['usersettings.colors.primaryColor' => $this->config->primaryColor]);
|
|
session(['usersettings.colors.secondaryColor' => $this->config->secondaryColor]);
|
|
}
|
|
|
|
$secondaryColor = $this->settingsRepo->getSetting('companysettings.secondarycolor');
|
|
if ($secondaryColor !== false) {
|
|
session(['usersettings.colors.secondaryColor' => $secondaryColor]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the primary and secondary colors for the user's color scheme.
|
|
*
|
|
* @param string $colorscheme The color scheme to set. Should be a valid key in the available color schemes array.
|
|
* @return void
|
|
*/
|
|
public function setSchemeColors($colorscheme)
|
|
{
|
|
|
|
$colorSchemes = $this->getAvailableColorSchemes();
|
|
if (isset($colorSchemes[$colorscheme]['primaryColor'])) {
|
|
$primary = $colorSchemes[$colorscheme]['primaryColor'];
|
|
session(['usersettings.colors.primaryColor' => $primary]);
|
|
}
|
|
|
|
if (isset($colorSchemes[$colorscheme]['secondaryColor'])) {
|
|
$secondary = $colorSchemes[$colorscheme]['secondaryColor'];
|
|
session(['usersettings.colors.secondaryColor' => $secondary]);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Retrieves the primary color from the user's settings or the default color scheme.
|
|
*
|
|
* @return string The primary color.
|
|
*/
|
|
public function getPrimaryColor()
|
|
{
|
|
|
|
if (
|
|
session()->exists('usersettings.colors.primaryColor')
|
|
&& session('usersettings.colors.primaryColor') != ''
|
|
&& Auth::isLoggedIn()
|
|
) {
|
|
return session('usersettings.colors.primaryColor');
|
|
}
|
|
|
|
$currentColorScheme = $this->getColorScheme();
|
|
|
|
$colorSchemes = $this->getAvailableColorSchemes();
|
|
|
|
$pickedScheme = $colorSchemes[$currentColorScheme] ?? $colorSchemes['themeDefault'];
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.colors.primaryColor' => $pickedScheme['primaryColor']]);
|
|
}
|
|
|
|
return $pickedScheme['primaryColor'];
|
|
}
|
|
|
|
/**
|
|
* getSecondaryColor - Retrieves the secondary color for the current user's color scheme
|
|
*
|
|
* This method returns the secondary color based on the following conditions:
|
|
* - If the secondary color is set in the user's session and is not empty, and the user is logged in,
|
|
* it will return the value stored in the session.
|
|
* - If the user is logged in, it will set the secondary color from the available color schemes based on the current color scheme in use.
|
|
* - If none of the above conditions are met, it will return the secondary color from the available color schemes based on the current color scheme in use.
|
|
*
|
|
* @return string The secondary color for the current user's color scheme
|
|
*/
|
|
public function getSecondaryColor()
|
|
{
|
|
|
|
if (
|
|
session()->exists('usersettings.colors.secondaryColor')
|
|
&& session('usersettings.colors.secondaryColor') != ''
|
|
&& Auth::isLoggedIn()
|
|
) {
|
|
return session('usersettings.colors.secondaryColor');
|
|
}
|
|
|
|
$colorSchemes = $this->getAvailableColorSchemes();
|
|
$currentColorScheme = $this->getColorScheme();
|
|
|
|
$pickedScheme = $colorSchemes[$currentColorScheme] ?? $colorSchemes['themeDefault'];
|
|
if (Auth::isLoggedIn()) {
|
|
session(['usersettings.colors.secondaryColor' => $pickedScheme['secondaryColor']]);
|
|
}
|
|
|
|
return $pickedScheme['secondaryColor'];
|
|
}
|
|
|
|
/**
|
|
* readIniData - Read theme.ini configuration data
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
private function readIniData(): void
|
|
{
|
|
if (! file_exists(ROOT.'/theme/'.$this->getActive().'/'.static::DEFAULT_INI.'.ini')) {
|
|
report('Configuration file for theme '.$this->getActive().' not found');
|
|
$this->clearCache();
|
|
$this->setActive('default');
|
|
}
|
|
$this->iniData = parse_ini_file(
|
|
ROOT.'/theme/'.$this->getActive().'/'.static::DEFAULT_INI.'.ini',
|
|
true,
|
|
INI_SCANNER_TYPED
|
|
);
|
|
if ($this->iniData === false) {
|
|
$this->iniData = [];
|
|
}
|
|
}
|
|
|
|
public static function clearCache(): void
|
|
{
|
|
session()->forget('usersettings.colors.primaryColor');
|
|
session()->forget('usersettings.colors.secondarycolor');
|
|
session()->forget('usersettings.colorMode');
|
|
session()->forget('usersettings.colorScheme');
|
|
session()->forget('usersettings.themeFont');
|
|
session()->forget('usersettings.theme');
|
|
session()->forget('usersettings.backgroundType');
|
|
session()->forget('usersettings.backgroundImage');
|
|
}
|
|
}
|