|
diff --git a/src/CoreBundle/ServiceHelper/AccessUrlHelper.php b/src/CoreBundle/ServiceHelper/AccessUrlHelper.php
index 2b5d9521bc4..2bb6c6f1ba1 100644
--- a/src/CoreBundle/ServiceHelper/AccessUrlHelper.php
+++ b/src/CoreBundle/ServiceHelper/AccessUrlHelper.php
@@ -25,6 +25,13 @@ public function isMultipleEnabled(): bool
return 1 === (int) $this->parameterBag->get('multiple_access_url');
}
+ public function getFirstAccessUrl(): AccessUrl
+ {
+ $urlId = $this->accessUrlRepository->getFirstId();
+
+ return $this->accessUrlRepository->find($urlId);
+ }
+
public function getCurrent(): AccessUrl
{
static $accessUrl;
@@ -33,8 +40,7 @@ public function getCurrent(): AccessUrl
return $accessUrl;
}
- $urlId = $this->accessUrlRepository->getFirstId();
- $accessUrl = $this->accessUrlRepository->find($urlId);
+ $accessUrl = $this->getFirstAccessUrl();
if ($this->isMultipleEnabled()) {
$url = $this->router->generate('index', [], UrlGeneratorInterface::ABSOLUTE_URL);
diff --git a/src/CoreBundle/ServiceHelper/MailHelper.php b/src/CoreBundle/ServiceHelper/MailHelper.php
index 520c9c2c57b..19b7f3c702e 100644
--- a/src/CoreBundle/ServiceHelper/MailHelper.php
+++ b/src/CoreBundle/ServiceHelper/MailHelper.php
@@ -19,6 +19,7 @@ final class MailHelper
public function __construct(
private readonly MailerInterface $mailer,
private readonly BodyRendererInterface $bodyRenderer,
+ private readonly ThemeHelper $themeHelper,
) {}
public function send(
@@ -98,7 +99,6 @@ public function send(
'link' => $additionalParameters['link'] ?? '',
'automatic_email_text' => $automaticEmailText,
'content' => $body,
- 'theme' => api_get_visual_theme(),
];
if (!empty($recipientEmail)) {
diff --git a/src/CoreBundle/ServiceHelper/ThemeHelper.php b/src/CoreBundle/ServiceHelper/ThemeHelper.php
new file mode 100644
index 00000000000..62354d46376
--- /dev/null
+++ b/src/CoreBundle/ServiceHelper/ThemeHelper.php
@@ -0,0 +1,103 @@
+accessUrlHelper->getCurrent();
+
+ $visualTheme = $accessUrl->getActiveColorTheme()?->getColorTheme()->getSlug();
+
+ if ('true' == $this->settingsManager->getSetting('profile.user_selected_theme')) {
+ $visualTheme = $this->userHelper->getCurrent()?->getTheme();
+ }
+
+ if ('true' == $this->settingsManager->getSetting('course.allow_course_theme')) {
+ $course = $this->cidReqHelper->getCourseEntity();
+
+ if ($course) {
+ $this->settingsCourseManager->setCourse($course);
+
+ $visualTheme = $this->settingsCourseManager->getCourseSettingValue('course_theme');
+
+ if (1 === (int) $this->settingsCourseManager->getCourseSettingValue('allow_learning_path_theme')) {
+ $visualTheme = $lp_theme_css;
+ }
+ }
+ }
+
+ if (empty($visualTheme)) {
+ return self::DEFAULT_THEME;
+ }
+
+ return $visualTheme;
+ }
+
+ public function getThemeAssetUrl(string $path, bool $absolute = false): string
+ {
+ $themeName = $this->getVisualTheme();
+
+ try {
+ if (!$this->filesystem->fileExists($themeName.DIRECTORY_SEPARATOR.$path)) {
+ return '';
+ }
+ } catch (FilesystemException) {
+ return '';
+ }
+
+ return $this->router->generate(
+ 'theme_asset',
+ ['name' => $themeName, 'path' => $path],
+ $absolute ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH
+ );
+ }
+
+ public function getThemeAssetLinkTag(string $path, bool $absoluteUrl = false): string
+ {
+ $url = $this->getThemeAssetUrl($path, $absoluteUrl);
+
+ if (empty($url)) {
+ return '';
+ }
+
+ return sprintf('', $url);
+ }
+}
diff --git a/src/CoreBundle/Settings/PlatformSettingsSchema.php b/src/CoreBundle/Settings/PlatformSettingsSchema.php
index a8cedbb798e..0df3f71ed3d 100644
--- a/src/CoreBundle/Settings/PlatformSettingsSchema.php
+++ b/src/CoreBundle/Settings/PlatformSettingsSchema.php
@@ -40,7 +40,6 @@ public function buildSettings(AbstractSettingsBuilder $builder): void
'institution_address' => '',
'site_name' => 'Chamilo site',
'timezone' => 'Europe/Paris',
- 'theme' => 'chamilo',
'gravatar_enabled' => 'false',
'gravatar_type' => 'mm',
'gamification_mode' => ' ',
@@ -120,7 +119,6 @@ public function buildForm(FormBuilderInterface $builder): void
->add('institution_address')
->add('site_name')
->add('timezone', TimezoneType::class)
- ->add('theme')
->add('gravatar_enabled', YesNoType::class)
->add(
'gravatar_type',
diff --git a/src/CoreBundle/Settings/SettingsManager.php b/src/CoreBundle/Settings/SettingsManager.php
index f598ef9175f..a741083a2ee 100644
--- a/src/CoreBundle/Settings/SettingsManager.php
+++ b/src/CoreBundle/Settings/SettingsManager.php
@@ -139,7 +139,7 @@ public function updateSetting(string $name, $value): void
* Get a specific configuration setting, getting from the previously stored
* PHP session data whenever possible.
*
- * @param string $name The setting name (composed if in a category, i.e. 'platform.theme')
+ * @param string $name The setting name (composed if in a category, i.e. 'platform.institution')
* @param bool $loadFromDb Whether to load from the database
*/
public function getSetting(string $name, bool $loadFromDb = false): mixed
@@ -658,7 +658,6 @@ private function getVariablesAndCategories(): array
// 'donotlistcampus' =>'null',
'show_email_addresses' => 'Platform',
'service_ppt2lp' => 'NULL',
- 'stylesheets' => 'stylesheets',
'upload_extensions_list_type' => 'Security',
'upload_extensions_blacklist' => 'Security',
'upload_extensions_whitelist' => 'Security',
@@ -920,7 +919,6 @@ private function renameVariable($variable)
'siteName' => 'site_name',
'InstitutionUrl' => 'institution_url',
'registration' => 'required_profile_fields',
- 'stylesheets' => 'theme',
'platformLanguage' => 'platform_language',
'languagePriority1' => 'language_priority_1',
'languagePriority2' => 'language_priority_2',
diff --git a/src/CoreBundle/Settings/StylesheetsSettingsSchema.php b/src/CoreBundle/Settings/StylesheetsSettingsSchema.php
deleted file mode 100644
index fd0392f932d..00000000000
--- a/src/CoreBundle/Settings/StylesheetsSettingsSchema.php
+++ /dev/null
@@ -1,76 +0,0 @@
-parameterBag = $parameterBag;
- }
-
- public function buildSettings(AbstractSettingsBuilder $builder): void
- {
- $builder
- ->setDefaults(
- [
- 'stylesheets' => 'chamilo',
- ]
- )
- ;
- $allowedTypes = [
- 'stylesheets' => ['string'],
- ];
- $this->setMultipleAllowedTypes($allowedTypes, $builder);
- }
-
- public function buildForm(FormBuilderInterface $builder): void
- {
- $builder
- ->add('stylesheets', ChoiceType::class, [
- 'choices' => $this->getThemeChoices(),
- 'label' => 'Select Stylesheet Theme',
- ])
- ;
-
- $this->updateFormFieldsFromSettingsInfo($builder);
- }
-
- private function getThemeChoices(): array
- {
- $projectDir = $this->parameterBag->get('kernel.project_dir');
- $themesDirectory = $projectDir.'/assets/css/themes/';
-
- $finder = new Finder();
- $choices = [];
-
- $finder->directories()->in($themesDirectory)->depth('== 0');
- if ($finder->hasResults()) {
- foreach ($finder as $folder) {
- $folderName = $folder->getRelativePathname();
- $choices[$this->formatFolderName($folderName)] = $folderName;
- }
- }
-
- return $choices;
- }
-
- private function formatFolderName(string $name): string
- {
- return ucwords(str_replace('_', ' ', $name));
- }
-}
diff --git a/src/CoreBundle/State/AccessUrlRelColorThemeStateProcessor.php b/src/CoreBundle/State/AccessUrlRelColorThemeStateProcessor.php
new file mode 100644
index 00000000000..4be89c45278
--- /dev/null
+++ b/src/CoreBundle/State/AccessUrlRelColorThemeStateProcessor.php
@@ -0,0 +1,45 @@
+accessUrlHelper->getCurrent();
+ $accessUrl->getActiveColorTheme()?->setActive(false);
+
+ $accessUrlRelColorTheme = $accessUrl->getColorThemeByTheme($data->getColorTheme());
+
+ if ($accessUrlRelColorTheme) {
+ $accessUrlRelColorTheme->setActive(true);
+ } else {
+ $data->setActive(true);
+
+ $accessUrl->addColorTheme($data);
+
+ $accessUrlRelColorTheme = $data;
+ }
+
+ $this->entityManager->flush();
+
+ return $accessUrlRelColorTheme;
+ }
+}
diff --git a/src/CoreBundle/State/AccessUrlRelColorThemeStateProvider.php b/src/CoreBundle/State/AccessUrlRelColorThemeStateProvider.php
new file mode 100644
index 00000000000..c1d3cbab77a
--- /dev/null
+++ b/src/CoreBundle/State/AccessUrlRelColorThemeStateProvider.php
@@ -0,0 +1,32 @@
+
+ */
+class AccessUrlRelColorThemeStateProvider implements ProviderInterface
+{
+ public function __construct(
+ private readonly AccessUrlHelper $accessUrlHelper,
+ ) {}
+
+ /**
+ * @inheritdoc
+ */
+ public function provide(Operation $operation, array $uriVariables = [], array $context = [])
+ {
+ $colorThemes = $this->accessUrlHelper->getCurrent()->getColorThemes();
+
+ if (0 == $colorThemes->count()) {
+ $colorThemes = $this->accessUrlHelper->getFirstAccessUrl()->getColorThemes();
+ }
+
+ return $colorThemes;
+ }
+}
diff --git a/src/CoreBundle/State/ColorThemeStateProcessor.php b/src/CoreBundle/State/ColorThemeStateProcessor.php
index e0a6473c8e8..d70e38f7d6f 100644
--- a/src/CoreBundle/State/ColorThemeStateProcessor.php
+++ b/src/CoreBundle/State/ColorThemeStateProcessor.php
@@ -8,10 +8,13 @@
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
+use Chamilo\CoreBundle\Entity\AccessUrlRelColorTheme;
use Chamilo\CoreBundle\Entity\ColorTheme;
-use Chamilo\CoreBundle\Repository\ColorThemeRepository;
-use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
-use Symfony\Component\Filesystem\Filesystem;
+use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
+use Doctrine\ORM\EntityManagerInterface;
+use League\Flysystem\FilesystemException;
+use League\Flysystem\FilesystemOperator;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
use const PHP_EOL;
@@ -19,23 +22,27 @@ final class ColorThemeStateProcessor implements ProcessorInterface
{
public function __construct(
private readonly ProcessorInterface $persistProcessor,
- private readonly ParameterBagInterface $parameterBag,
- private readonly ColorThemeRepository $colorThemeRepository,
+ private readonly AccessUrlHelper $accessUrlHelper,
+ private readonly EntityManagerInterface $entityManager,
+ #[Autowire(service: 'oneup_flysystem.themes_filesystem')] private readonly FilesystemOperator $filesystem,
) {}
+ /**
+ * @throws FilesystemException
+ */
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
\assert($data instanceof ColorTheme);
- $data->setActive(true);
-
/** @var ColorTheme $colorTheme */
$colorTheme = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
if ($colorTheme) {
- $this->colorThemeRepository->deactivateAllExcept($colorTheme);
+ $accessUrlRelColorTheme = (new AccessUrlRelColorTheme())->setColorTheme($colorTheme);
+
+ $this->accessUrlHelper->getCurrent()->addColorTheme($accessUrlRelColorTheme);
- $projectDir = $this->parameterBag->get('kernel.project_dir');
+ $this->entityManager->flush();
$contentParts = [];
$contentParts[] = ':root {';
@@ -46,12 +53,9 @@ public function process($data, Operation $operation, array $uriVariables = [], a
$contentParts[] = '}';
- $dirName = $projectDir."/var/theme/{$colorTheme->getSlug()}";
-
- $fs = new Filesystem();
- $fs->mkdir($dirName);
- $fs->dumpFile(
- $dirName.'/colors.css',
+ $this->filesystem->createDirectory($colorTheme->getSlug());
+ $this->filesystem->write(
+ $colorTheme->getSlug().DIRECTORY_SEPARATOR.'colors.css',
implode(PHP_EOL, $contentParts)
);
}
diff --git a/src/CoreBundle/Twig/Extension/ChamiloExtension.php b/src/CoreBundle/Twig/Extension/ChamiloExtension.php
index df000b59e61..5fcf06a0733 100644
--- a/src/CoreBundle/Twig/Extension/ChamiloExtension.php
+++ b/src/CoreBundle/Twig/Extension/ChamiloExtension.php
@@ -10,6 +10,7 @@
use Chamilo\CoreBundle\Entity\ResourceIllustrationInterface;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\Node\IllustrationRepository;
+use Chamilo\CoreBundle\ServiceHelper\ThemeHelper;
use Chamilo\CoreBundle\Twig\SettingsHelper;
use Security;
use Sylius\Bundle\SettingsBundle\Model\SettingsInterface;
@@ -28,8 +29,13 @@ class ChamiloExtension extends AbstractExtension
private RouterInterface $router;
private NameConvention $nameConvention;
- public function __construct(IllustrationRepository $illustrationRepository, SettingsHelper $helper, RouterInterface $router, NameConvention $nameConvention)
- {
+ public function __construct(
+ IllustrationRepository $illustrationRepository,
+ SettingsHelper $helper,
+ RouterInterface $router,
+ NameConvention $nameConvention,
+ private readonly ThemeHelper $themeHelper
+ ) {
$this->illustrationRepository = $illustrationRepository;
$this->helper = $helper;
$this->router = $router;
@@ -66,6 +72,8 @@ public function getFunctions(): array
new TwigFunction('chamilo_settings_get', $this->getSettingsParameter(...)),
new TwigFunction('chamilo_settings_has', [$this, 'hasSettingsParameter']),
new TwigFunction('password_checker_js', [$this, 'getPasswordCheckerJs'], ['is_safe' => ['html']]),
+ new TwigFunction('theme_asset', $this->getThemeAssetUrl(...)),
+ new TwigFunction('theme_asset_link_tag', $this->getThemeAssetLinkTag(...), ['is_safe' => ['html']]),
];
}
@@ -219,4 +227,14 @@ public function getName(): string
{
return 'chamilo_extension';
}
+
+ public function getThemeAssetUrl(string $path, bool $absolute = false): string
+ {
+ return $this->themeHelper->getThemeAssetUrl($path, $absolute);
+ }
+
+ public function getThemeAssetLinkTag(string $path, bool $absoluteUrl = false): string
+ {
+ return $this->themeHelper->getThemeAssetLinkTag($path, $absoluteUrl);
+ }
}
diff --git a/tests/CoreBundle/Controller/ThemeControllerTest.php b/tests/CoreBundle/Controller/ThemeControllerTest.php
new file mode 100644
index 00000000000..09b6db30390
--- /dev/null
+++ b/tests/CoreBundle/Controller/ThemeControllerTest.php
@@ -0,0 +1,45 @@
+request('GET', '/themes/chamilo/colors.css');
+
+ $this->assertResponseIsSuccessful();
+ }
+
+ public function testInvalidAccess(): void
+ {
+ $client = static::createClient();
+
+ $client->request('GET', '/themes/chamilo/default.css');
+
+ $this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
+ }
+
+ public function testAccessToSystemFiles(): void
+ {
+ $client = static::createClient();
+ $client->request('GET', '/themes/chamilo/../../../../../../etc/passwd');
+
+ $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
+
+
+ $client->request('GET', 'themes/chamilo/../../../.env');
+
+ $this->assertResponseStatusCodeSame(Response::HTTP_INTERNAL_SERVER_ERROR);
+ }
+}
diff --git a/tests/CoreBundle/Twig/SettingsHelperTest.php b/tests/CoreBundle/Twig/SettingsHelperTest.php
index 321e41a4bcb..59811d33af1 100644
--- a/tests/CoreBundle/Twig/SettingsHelperTest.php
+++ b/tests/CoreBundle/Twig/SettingsHelperTest.php
@@ -26,8 +26,8 @@ public function testGetSettings(): void
$this->assertInstanceOf(SettingsInterface::class, $settings);
$this->assertSame('chamilo_settings', $helper->getName());
- $defaultTheme = $helper->getSettingsParameter('platform.theme');
+ $defaultTheme = $helper->getSettingsParameter('platform.institution');
- $this->assertSame('chamilo', $defaultTheme);
+ $this->assertSame('Chamilo.org', $defaultTheme);
}
}
diff --git a/var/themes/chamilo/colors.css b/var/themes/chamilo/colors.css
new file mode 100644
index 00000000000..9700b2c1ff0
--- /dev/null
+++ b/var/themes/chamilo/colors.css
@@ -0,0 +1,32 @@
+:root {
+ --color-primary-base: 97 53 131;
+ --color-primary-gradient: 36 77 103;
+ --color-primary-button-text: 46 117 163;
+ --color-primary-button-alternative-text: 255 255 255;
+
+ --color-secondary-base: 243 126 47;
+ --color-secondary-gradient: 224 100 16;
+ --color-secondary-button-text: 255 255 255;
+
+ --color-tertiary-base: 51 51 51;
+ --color-tertiary-gradient: 0 0 0;
+ --color-tertiary-button-text: 255 255 255;
+
+ --color-success-base: 119 170 12;
+ --color-success-gradient: 83 127 0;
+ --color-success-button-text: 255 255 255;
+
+ --color-info-base: 13 123 253;
+ --color-info-gradient: 0 84 211;
+ --color-info-button-text: 255 255 255;
+
+ --color-warning-base: 245 206 1;
+ --color-warning-gradient: 186 152 0;
+ --color-warning-button-text: 0 0 0;
+
+ --color-danger-base: 223 59 59;
+ --color-danger-gradient: 180 0 21;
+ --color-danger-button-text: 255 255 255;
+
+ --color-form-base: 46 117 163;
+}
diff --git a/assets/css/themes/chamilo/images/avatar.svg b/var/themes/chamilo/images/avatar.svg
similarity index 100%
rename from assets/css/themes/chamilo/images/avatar.svg
rename to var/themes/chamilo/images/avatar.svg
diff --git a/assets/css/themes/chamilo/images/favicon.ico b/var/themes/chamilo/images/favicon.ico
similarity index 100%
rename from assets/css/themes/chamilo/images/favicon.ico
rename to var/themes/chamilo/images/favicon.ico
diff --git a/assets/css/themes/chamilo/images/header-logo.png b/var/themes/chamilo/images/header-logo.png
similarity index 100%
rename from assets/css/themes/chamilo/images/header-logo.png
rename to var/themes/chamilo/images/header-logo.png
diff --git a/assets/css/themes/chamilo/images/header-logo.svg b/var/themes/chamilo/images/header-logo.svg
similarity index 100%
rename from assets/css/themes/chamilo/images/header-logo.svg
rename to var/themes/chamilo/images/header-logo.svg
diff --git a/webpack.config.js b/webpack.config.js
index 3ba39fe3ddf..24e12a1f529 100755
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -135,18 +135,6 @@ Encore.copyFiles({
to: "libs/select2/js/[name].[ext]",
})
-const themes = ["chamilo"]
-
-// Add Chamilo themes
-themes.forEach(function (theme) {
- Encore.addStyleEntry("css/themes/" + theme + "/default", "./assets/css/themes/" + theme + "/default.css")
- // Copy images from themes into public/build
- Encore.copyFiles({
- from: "assets/css/themes/" + theme + "/images",
- to: "css/themes/" + theme + "/images/[name].[ext]",
- })
-})
-
// Fix free-jqgrid languages files
// Encore.addPlugin(new FileManagerPlugin({
// onEnd: {
|