client page theme, lead form theme
This commit is contained in:
@@ -31,6 +31,7 @@ namespace Espo\Core\Portal\Utils;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Theme\MetadataProvider;
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\Core\Utils\ThemeManager as BaseThemeManager;
|
||||
|
||||
@@ -38,9 +39,13 @@ class ThemeManager extends BaseThemeManager
|
||||
{
|
||||
private Portal $portal;
|
||||
|
||||
public function __construct(Config $config, Metadata $metadata, Portal $portal)
|
||||
{
|
||||
parent::__construct($config, $metadata);
|
||||
public function __construct(
|
||||
Config $config,
|
||||
Metadata $metadata,
|
||||
MetadataProvider $metadataProvider,
|
||||
Portal $portal,
|
||||
) {
|
||||
parent::__construct($config, $metadata, $metadataProvider);
|
||||
|
||||
$this->portal = $portal;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ class ActionRenderer
|
||||
initAuth: $params->initAuth(),
|
||||
scripts: $params->getScripts(),
|
||||
pageTitle: $params->getPageTitle(),
|
||||
theme: $params->getTheme(),
|
||||
);
|
||||
|
||||
$securityParams = new SecurityParams(
|
||||
@@ -76,6 +77,7 @@ class ActionRenderer
|
||||
bool $initAuth,
|
||||
array $scripts,
|
||||
?string $pageTitle,
|
||||
?string $theme,
|
||||
): string {
|
||||
|
||||
$encodedData = Json::encode($data);
|
||||
@@ -96,6 +98,7 @@ class ActionRenderer
|
||||
runScript: $script,
|
||||
scripts: $scripts,
|
||||
pageTitle: $pageTitle,
|
||||
theme: $theme,
|
||||
);
|
||||
|
||||
return $this->clientManager->render($params);
|
||||
|
||||
@@ -44,6 +44,7 @@ class Params
|
||||
/** @var Script[] */
|
||||
private array $scripts = [];
|
||||
private ?string $pageTitle = null;
|
||||
private ?string $theme = null;
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $data
|
||||
@@ -118,6 +119,17 @@ class Params
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function withTheme(?string $theme): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->theme = $theme;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function getController(): string
|
||||
{
|
||||
return $this->controller;
|
||||
@@ -166,4 +178,12 @@ class Params
|
||||
{
|
||||
return $this->pageTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getTheme(): ?string
|
||||
{
|
||||
return $this->theme;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ readonly class RenderParams
|
||||
* @param ?string $runScript A JS run-script.
|
||||
* @param Script[] $scripts Scripts to include on the page.
|
||||
* @param ?string $pageTitle A page title. Since 9.1.0.
|
||||
* @param ?string $theme A page theme name.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $runScript = null,
|
||||
public array $scripts = [],
|
||||
public ?string $pageTitle = null,
|
||||
public ?string $theme = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ use Espo\Core\Utils\Config\ApplicationConfig;
|
||||
use Espo\Core\Utils\Config\SystemConfig;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
|
||||
use Espo\Core\Utils\Theme\MetadataProvider as ThemeMetadataProvider;
|
||||
use Slim\Psr7\Response as Psr7Response;
|
||||
use Slim\ResponseEmitter;
|
||||
|
||||
@@ -70,6 +71,7 @@ class ClientManager
|
||||
private LoaderParamsProvider $loaderParamsProvider,
|
||||
private SystemConfig $systemConfig,
|
||||
private ApplicationConfig $applicationConfig,
|
||||
private ThemeMetadataProvider $themeMetadataProvider,
|
||||
) {
|
||||
$this->nonce = Util::generateKey();
|
||||
}
|
||||
@@ -168,6 +170,7 @@ class ClientManager
|
||||
runScript: $params->runScript,
|
||||
additionalScripts: $params->scripts,
|
||||
pageTitle: $params->pageTitle,
|
||||
theme: $params->theme,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,6 +184,7 @@ class ClientManager
|
||||
array $vars = [],
|
||||
array $additionalScripts = [],
|
||||
?string $pageTitle = null,
|
||||
?string $theme = null,
|
||||
): string {
|
||||
|
||||
$runScript ??= $this->runScript;
|
||||
@@ -232,6 +236,10 @@ class ClientManager
|
||||
$this->module->getInternalList()
|
||||
);
|
||||
|
||||
$stylesheet = $theme ?
|
||||
$this->themeMetadataProvider->getStylesheet($theme) :
|
||||
$this->themeManager->getStylesheet();
|
||||
|
||||
$data = [
|
||||
'applicationId' => $this->applicationId,
|
||||
'apiUrl' => $this->apiUrl,
|
||||
@@ -239,7 +247,8 @@ class ClientManager
|
||||
'cacheTimestamp' => $cacheTimestamp,
|
||||
'appTimestamp' => $appTimestamp,
|
||||
'loaderCacheTimestamp' => Json::encode($loaderCacheTimestamp),
|
||||
'stylesheet' => $this->themeManager->getStylesheet(),
|
||||
'stylesheet' => $stylesheet,
|
||||
'theme' => Json::encode($theme),
|
||||
'runScript' => $runScript,
|
||||
'basePath' => $this->basePath,
|
||||
'useCache' => $useCache ? 'true' : 'false',
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Theme;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 9.1.0
|
||||
*/
|
||||
class MetadataProvider
|
||||
{
|
||||
private string $defaultStylesheet = 'client/css/espo/espo.css';
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
public function getStylesheet(string $theme): string
|
||||
{
|
||||
return $this->metadata->get("themes.$theme.stylesheet") ?? $this->defaultStylesheet;
|
||||
}
|
||||
|
||||
public function isDark(string $theme): bool
|
||||
{
|
||||
return (bool) $this->metadata->get("themes.$theme.isDark");
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,18 @@
|
||||
|
||||
namespace Espo\Core\Utils;
|
||||
|
||||
use Espo\Core\Utils\Theme\MetadataProvider;
|
||||
|
||||
class ThemeManager
|
||||
{
|
||||
private string $defaultName = 'Espo';
|
||||
private string $defaultStylesheet = 'client/css/espo/espo.css';
|
||||
|
||||
private string $defaultLogoSrc = 'client/img/logo.svg';
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private Metadata $metadata
|
||||
private Metadata $metadata,
|
||||
private MetadataProvider $metadataProvider,
|
||||
) {}
|
||||
|
||||
public function getName(): string
|
||||
@@ -47,7 +50,7 @@ class ThemeManager
|
||||
|
||||
public function getStylesheet(): string
|
||||
{
|
||||
return $this->metadata->get(['themes', $this->getName(), 'stylesheet']) ?? $this->defaultStylesheet;
|
||||
return $this->metadataProvider->getStylesheet($this->getName());
|
||||
}
|
||||
|
||||
public function getLogoSrc(): string
|
||||
@@ -57,6 +60,6 @@ class ThemeManager
|
||||
|
||||
public function isDark(): bool
|
||||
{
|
||||
return (bool) $this->metadata->get("themes.{$this->getName()}.isDark");
|
||||
return $this->metadataProvider->isDark($this->getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +221,19 @@ class LeadCapture extends Entity
|
||||
return (bool) $this->get('formEnabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getFormTitle(): ?string
|
||||
{
|
||||
return $this->get('formTitle');
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getFormTheme(): ?string
|
||||
{
|
||||
return $this->get('formTheme');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ class LeadCaptureForm implements EntryPoint
|
||||
|
||||
$params = $params
|
||||
->withFrameAncestors($leadCapture->getFormFrameAncestors())
|
||||
->withPageTitle($leadCapture->getFormTitle());
|
||||
->withPageTitle($leadCapture->getFormTitle())
|
||||
->withTheme($leadCapture->getFormTheme());
|
||||
|
||||
if ($captchaScript) {
|
||||
$params = $params->withScripts([new Script(source: $captchaScript)]);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"formEnabled": "Web Form",
|
||||
"formUrl": "Form URL",
|
||||
"formTitle": "Form Title",
|
||||
"formTheme": "Form Theme",
|
||||
"formSuccessText": "Text to display after form submission",
|
||||
"formText": "Text to display on form",
|
||||
"formSuccessRedirectUrl": "URL to redirect to after form submission",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
[{"name": "formEnabled"}, {"name": "formTitle"}],
|
||||
[{"name": "formText"}, {"name": "formSuccessText"}],
|
||||
[{"name": "formSuccessRedirectUrl"}, {"name": "formLanguage"}],
|
||||
[{"name": "formFrameAncestors"}, {"name": "formCaptcha"}]
|
||||
[{"name": "formFrameAncestors"}, {"name": "formCaptcha"}],
|
||||
[{"name": "formTheme"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -164,6 +164,16 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"formTheme": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "isTrue",
|
||||
"attribute": "formEnabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"formText": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
|
||||
@@ -136,6 +136,12 @@
|
||||
"type": "varchar",
|
||||
"maxLength": 80
|
||||
},
|
||||
"formTheme": {
|
||||
"type": "enum",
|
||||
"maxLength": 64,
|
||||
"view": "views/lead-capture/fields/form-theme",
|
||||
"translation": "Global.themes"
|
||||
},
|
||||
"formText": {
|
||||
"type": "text",
|
||||
"tooltip": "optInConfirmationSuccessMessage"
|
||||
|
||||
@@ -36,6 +36,7 @@ use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DataCache;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Theme\MetadataProvider as ThemeMetadataProvider;
|
||||
use Espo\Core\Utils\ThemeManager;
|
||||
use Espo\Entities\Integration;
|
||||
use Espo\Entities\LeadCapture;
|
||||
@@ -60,6 +61,7 @@ class FormService
|
||||
private DataCache $dataCache,
|
||||
private ThemeManager $themeManager,
|
||||
private Config\SystemConfig $systemConfig,
|
||||
private ThemeMetadataProvider $themeMetadataProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -75,7 +77,6 @@ class FormService
|
||||
$data = $this->getDataInternal($leadCapture);
|
||||
|
||||
$data['captchaKey'] = $captchaKey;
|
||||
$data['isDark'] = $this->themeManager->isDark();
|
||||
|
||||
return [$leadCapture, $data, $captchaScript];
|
||||
}
|
||||
@@ -413,6 +414,7 @@ class FormService
|
||||
'regExpPatterns' => $this->metadata->get("app.regExpPatterns"),
|
||||
],
|
||||
],
|
||||
'isDark' => $this->isDark($leadCapture),
|
||||
'detailLayout' => $detailLayout,
|
||||
'language' => $languageData,
|
||||
'successText' => $successText,
|
||||
@@ -465,4 +467,13 @@ class FormService
|
||||
|
||||
return 'https://www.google.com/recaptcha/api.js?render=' . $siteKey;
|
||||
}
|
||||
|
||||
private function isDark(LeadCapture $leadCapture): bool
|
||||
{
|
||||
if (!$leadCapture->getFormTheme()) {
|
||||
return $this->themeManager->isDark();
|
||||
}
|
||||
|
||||
return $this->themeMetadataProvider->isDark($leadCapture->getFormTheme());
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -116,6 +116,11 @@ class App {
|
||||
*/
|
||||
this.internalModuleList = options.internalModuleList || [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.themeName = options.theme || null;
|
||||
|
||||
/**
|
||||
* A list of bundled modules.
|
||||
*
|
||||
@@ -433,7 +438,7 @@ class App {
|
||||
|
||||
this.fieldManager.acl = this.acl;
|
||||
|
||||
this.themeManager = new ThemeManager(this.settings, this.preferences, this.metadata);
|
||||
this.themeManager = new ThemeManager(this.settings, this.preferences, this.metadata, this.themeName);
|
||||
this.modelFactory = new ModelFactory(this.metadata);
|
||||
this.collectionFactory = new CollectionFactory(this.modelFactory, this.settings, this.metadata);
|
||||
|
||||
@@ -1561,6 +1566,7 @@ class App {
|
||||
* @property {string} [bundledModuleList] A list of bundled modules.
|
||||
* @property {Number|null} [cacheTimestamp] A cache timestamp.
|
||||
* @property {Number|null} [appTimestamp] An application timestamp.
|
||||
* @property {string|null} [theme] A theme name.
|
||||
*/
|
||||
|
||||
Object.assign(App.prototype, Events);
|
||||
|
||||
@@ -37,7 +37,7 @@ class ThemeManager {
|
||||
* @param {module:models/settings} config A config.
|
||||
* @param {module:models/preferences} preferences Preferences.
|
||||
* @param {module:metadata} metadata Metadata.
|
||||
* @param {?string} [name] A name. If not set, then will be obtained from config and preferences.
|
||||
* @param {string|null} [name] A name. If not set, then will be obtained from config and preferences.
|
||||
*/
|
||||
constructor(config, preferences, metadata, name) {
|
||||
/**
|
||||
@@ -60,7 +60,13 @@ class ThemeManager {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {?string}
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.useConfig = !name;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string|null}
|
||||
*/
|
||||
this.name = name || null;
|
||||
}
|
||||
@@ -220,11 +226,15 @@ class ThemeManager {
|
||||
|
||||
let values = null;
|
||||
|
||||
if (!this.config.get('userThemesDisabled') && this.preferences.get('theme')) {
|
||||
if (
|
||||
this.useConfig &&
|
||||
!this.config.get('userThemesDisabled') &&
|
||||
this.preferences.get('theme')
|
||||
) {
|
||||
values = this.preferences.get('themeParams');
|
||||
}
|
||||
|
||||
if (!values) {
|
||||
if (!values && this.useConfig) {
|
||||
values = this.config.get('themeParams');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import EnumFieldView from 'views/fields/enum';
|
||||
|
||||
export default class FormThemeFieldView extends EnumFieldView {
|
||||
|
||||
setupOptions() {
|
||||
const list = Object.keys(this.getMetadata().get('themes') || {})
|
||||
.sort((v1, v2) => {
|
||||
if (v2 === 'EspoRtl') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.translate(v1, 'theme')
|
||||
.localeCompare(this.translate(v2, 'theme'));
|
||||
});
|
||||
|
||||
this.params.options = ['', ...list];
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
ajaxTimeout: {{ajaxTimeout}},
|
||||
internalModuleList: {{internalModuleList}},
|
||||
bundledModuleList: {{bundledModuleList}},
|
||||
theme: {{theme}},
|
||||
}, app => {
|
||||
loadedApp = app;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user