From 8ccada40d15abcf448cda0bbb3a6365ea007af52 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 8 Apr 2021 17:56:11 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B03936=20-=20Add=20user=20preference=20to?= =?UTF-8?q?=20choose=20backoffice=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/themehandler.class.inc.php | 126 ++++++++++++++---- .../dictionaries/en.dict.itop-structure.php | 6 + .../dictionaries/fr.dict.itop-structure.php | 6 + .../pages/en.dictionary.itop.preferences.php | 3 + .../pages/fr.dictionary.itop.preferences.php | 3 + pages/preferences.php | 44 +++++- 6 files changed, 159 insertions(+), 29 deletions(-) diff --git a/application/themehandler.class.inc.php b/application/themehandler.class.inc.php index 3cb5ba577..ebbe06ade 100644 --- a/application/themehandler.class.inc.php +++ b/application/themehandler.class.inc.php @@ -58,22 +58,29 @@ class ThemeHandler /** * Return the ID of the theme currently defined in the config. file * + * @deprecated 3.0.0, will be removed in 3.1, see N°3898 * @return string */ public static function GetCurrentThemeId() { - try - { - if (is_null(MetaModel::GetConfig())) - { + static::GetCurrentUserThemeId(); + } + + /** + * @return string ID of the theme currently defined in the config. file, which applies to all users by default. If non defined, fallback on the default one. + * @since 3.0.0 + */ + public static function GetApplicationThemeId(): string + { + try { + if (is_null(MetaModel::GetConfig())) { throw new CoreException('no config'); } $sThemeId = MetaModel::GetConfig()->Get('backoffice_default_theme'); } - catch(CoreException $oCompileException) - { + catch (CoreException $oCompileException) { // Fallback on our default theme in case the config. is not available yet - $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); + $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); $sThemeId = $aDefaultTheme['name']; } @@ -81,44 +88,115 @@ class ThemeHandler } /** - * Return the absolute path of the compiled theme folder. - * + * @return string ID of the theme to use for the current user as per they preferences. If non defined, fallback on the app. theme ID. + * @since 3.0.0 + */ + public static function GetCurrentUserThemeId(): string + { + try { + $sThemeId = appUserPreferences::GetPref('backoffice_theme', null); + } + catch (Exception $oException) { + $sThemeId = null; + } + + // Fallback on the app. theme + if (is_null($sThemeId)) { + $sThemeId = static::GetApplicationThemeId(); + } + + return $sThemeId; + } + + /** * @param string $sThemeId * - * @return string + * @return string Label of the theme which is either a dict entry ('theme:') or the ID if no localized dict. entry found. + * @since 3.0.0 */ - public static function GetCompiledThemeFolderAbsolutePath($sThemeId) + public static function GetThemeLabel(string $sThemeId): string { - return APPROOT.'env-'.utils::GetCurrentEnvironment().'/branding/themes/'.$sThemeId.'/'; + $sDictEntryCode = 'theme:'.$sThemeId; + $sDictEntryValue = Dict::S('theme:'.$sThemeId); + + return ($sDictEntryCode === $sDictEntryValue) ? $sThemeId : $sDictEntryValue; } - + + /** + * @return array Associative array of => , ordered by labels + * @since 3.0.0 + */ + public static function GetAvailableThemes(): array + { + $aThemes = []; + + foreach (glob(static::GetCompiledThemesFolderAbsolutePath().'/*') as $sPath) { + if (is_dir($sPath)) { + $sThemeId = basename($sPath); + $sThemeLabel = static::GetThemeLabel($sThemeId); + + $aThemes[$sThemeId] = $sThemeLabel; + } + } + asort($aThemes); + + return $aThemes; + } + + /** + * @param string $sThemeId + * + * @return bool True if $sThemeId is a valid theme that can be used. + * @since 3.0.0 + */ + public static function IsValidTheme(string $sThemeId): bool + { + return array_key_exists($sThemeId, static::GetAvailableThemes()); + } + + /** + * @return string Absolute path to the folder containing all the compiled themes + * @since 3.0.0 + */ + public static function GetCompiledThemesFolderAbsolutePath(): string + { + return APPROOT.'env-'.utils::GetCurrentEnvironment().'/branding/themes/'; + } + + /** + * @param string $sThemeId + * + * @return string Absolute path to the folder containing the $sThemeId theme + */ + public static function GetCompiledThemeFolderAbsolutePath(string $sThemeId): string + { + return static::GetCompiledThemesFolderAbsolutePath().$sThemeId.'/'; + } + /** * Return the absolute URL for the current theme CSS file * * @return string * @throws \Exception */ - public static function GetCurrentThemeUrl() + public static function GetCurrentThemeUrl(): string { - try - { + try { // Try to compile theme defined in the configuration - $sThemeId = static::GetCurrentThemeId(); + $sThemeId = static::GetCurrentUserThemeId(); static::CompileTheme($sThemeId); } - catch(CoreException $oCompileException) - { + catch (CoreException $oCompileException) { // Fallback on our default theme (should always be compilable) in case the previous theme doesn't exists - $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); + $aDefaultTheme = ThemeHandler::GetDefaultThemeInformation(); $sThemeId = $aDefaultTheme['name']; $sDefaultThemeDirPath = static::GetCompiledThemeFolderAbsolutePath($sThemeId); - + // Create our theme dir if it doesn't exist (XML theme node removed, renamed etc..) - if(!is_dir($sDefaultThemeDirPath)) - { + if (!is_dir($sDefaultThemeDirPath)) { SetupUtils::builddir($sDefaultThemeDirPath); } - + static::CompileTheme($sThemeId, false, "", $aDefaultTheme['parameters']); } diff --git a/datamodels/2.x/itop-structure/dictionaries/en.dict.itop-structure.php b/datamodels/2.x/itop-structure/dictionaries/en.dict.itop-structure.php index 41dfc0d32..e673cc6e6 100644 --- a/datamodels/2.x/itop-structure/dictionaries/en.dict.itop-structure.php +++ b/datamodels/2.x/itop-structure/dictionaries/en.dict.itop-structure.php @@ -371,3 +371,9 @@ Dict::Add('EN US', 'English', 'English', array( 'Person:personal_info' => 'Personal information', 'Person:notifiy' => 'Notification', )); + +// Themes +Dict::Add('EN US', 'English', 'English', array( + 'theme:fullmoon' => 'Full moon 🌕', + 'theme:test-red' => 'Test instance (Red)', +)); diff --git a/datamodels/2.x/itop-structure/dictionaries/fr.dict.itop-structure.php b/datamodels/2.x/itop-structure/dictionaries/fr.dict.itop-structure.php index 912fce2ce..42090bee7 100644 --- a/datamodels/2.x/itop-structure/dictionaries/fr.dict.itop-structure.php +++ b/datamodels/2.x/itop-structure/dictionaries/fr.dict.itop-structure.php @@ -381,3 +381,9 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Person:personal_info' => 'Informations personnelles', 'Person:notifiy' => 'Notification', )); + +// Themes +Dict::Add('EN US', 'English', 'English', array( + 'theme:fullmoon' => 'Full moon 🌕', + 'theme:test-red' => 'Instance de test (Rouge)', +)); diff --git a/dictionaries/ui/pages/en.dictionary.itop.preferences.php b/dictionaries/ui/pages/en.dictionary.itop.preferences.php index 611ae897a..2d09f3f98 100644 --- a/dictionaries/ui/pages/en.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/en.dictionary.itop.preferences.php @@ -21,6 +21,9 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Preferences:Title' => 'Preferences', 'UI:Preferences:UserInterface:Title' => 'User interface', + 'UI:Preferences:General:Title' => 'General', + 'UI:Preferences:General:Theme' => 'Theme', + 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (default)', 'UI:Preferences:Lists:Title' => 'Lists', 'UI:Preferences:RichText:Title' => 'Rich text editor', 'UI:Preferences:RichText:ToolbarState' => 'Toolbar default state', diff --git a/dictionaries/ui/pages/fr.dictionary.itop.preferences.php b/dictionaries/ui/pages/fr.dictionary.itop.preferences.php index 7486caa87..f92291ce8 100644 --- a/dictionaries/ui/pages/fr.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/fr.dictionary.itop.preferences.php @@ -21,6 +21,9 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Preferences:Title' => 'PrĂ©fĂ©rences', 'UI:Preferences:UserInterface:Title' => 'Interface utilisateur', + 'UI:Preferences:General:Title' => 'GĂ©nĂ©ral', + 'UI:Preferences:General:Theme' => 'ThĂȘme', + 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (dĂ©faut)', 'UI:Preferences:Lists:Title' => 'Listes', 'UI:Preferences:RichText:Title' => 'Éditeur texte riche', 'UI:Preferences:RichText:ToolbarState' => 'Affichage par dĂ©faut de la barre d\'outil', diff --git a/pages/preferences.php b/pages/preferences.php index b5807727d..f2762bae9 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -76,10 +76,11 @@ function DisplayPreferences($oP) $oUISubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply_user_interface', true); $oUIToolbar->AddSubBlock($oUISubmitButton); - // Language - $oLanguageFieldset = FieldSetUIBlockFactory::MakeStandard(Dict::S('UI:FavoriteLanguage'), 'ibo-fieldset-for-language-preferences'); - $oLanguageFieldset->AddSubBlock(GetLanguageFieldBlock()); - $oFirstColumn->AddSubBlock($oLanguageFieldset); + // General + $oGeneralFieldset = FieldSetUIBlockFactory::MakeStandard(Dict::S('UI:Preferences:General:Title'), 'ibo-fieldset-for-language-preferences'); + $oGeneralFieldset->AddSubBlock(GetLanguageFieldBlock()); + $oGeneralFieldset->AddSubBlock(GetThemeFieldBlock()); + $oFirstColumn->AddSubBlock($oGeneralFieldset); // Lists $oListsFieldset = FieldSetUIBlockFactory::MakeStandard(Dict::S('UI:Preferences:Lists:Title'), 'ibo-fieldset-for-lists-preferences'); @@ -467,7 +468,7 @@ function GetLanguageFieldBlock(): iUIBlock } ksort($aSortedLanguages); - $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel('language', Dict::S('UI:Favorites:SelectYourLanguage')); + $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel('language', Dict::S('UI:FavoriteLanguage')); /** @var \Combodo\iTop\Application\UI\Base\Component\Input\Select $oSelectInput */ $oSelectInput = $oSelect->GetInput(); foreach ($aSortedLanguages as $sCode) { @@ -478,6 +479,33 @@ function GetLanguageFieldBlock(): iUIBlock return $oSelect; } +/** + * @return \Combodo\iTop\Application\UI\Base\iUIBlock + * @since 3.0.0 + */ +function GetThemeFieldBlock(): iUIBlock +{ + $aAvailableThemes = ThemeHandler::GetAvailableThemes(); + + $oSelect = SelectUIBlockFactory::MakeForSelectWithLabel('theme', Dict::S('UI:Preferences:General:Theme')); + /** @var \Combodo\iTop\Application\UI\Base\Component\Input\Select $oSelectInput */ + $oSelectInput = $oSelect->GetInput(); + foreach ($aAvailableThemes as $sCode => $sLabel) { + if (MetaModel::GetConfig()->Get('demo_mode') && ($sCode !== ThemeHandler::GetApplicationThemeId())) { + // Demo mode: only the current app. theme is listed in the available choices + continue; + } + + $bSelected = ($sCode === ThemeHandler::GetCurrentUserThemeId()); + if (true === $bSelected) { + $sLabel = Dict::Format('UI:Preferences:General:Theme:DefaultThemeLabel', $sLabel); + } + $oSelectInput->AddSubBlock(SelectOptionUIBlockFactory::MakeForSelectOption($sCode, $sLabel, $bSelected)); + } + + return $oSelect; +} + /** * @return \Combodo\iTop\Application\UI\Base\iUIBlock * @throws \CoreException @@ -689,6 +717,12 @@ try { $oUser->DBUpdate(); utils::PopArchiveMode(); + // Theme + $sThemeId = utils::ReadParam('theme', ''); + if (!empty($sThemeId) && ThemeHandler::IsValidTheme($sThemeId)) { + appUserPreferences::SetPref('backoffice_theme', $sThemeId); + } + // List $iDefaultPageSize = (int)utils::ReadParam('default_page_size', -1); if ($iDefaultPageSize > 0) {