Skip to content

Commit 4914df8

Browse files
authored
Merge pull request #6782 from christianbeeznest/ras-22944
Internal: Apply THEME_FALLBACK for multi-URL portals without theme - refs BT#22944
2 parents 78e3fe3 + 1f8630f commit 4914df8

File tree

1 file changed

+92
-41
lines changed

1 file changed

+92
-41
lines changed

src/CoreBundle/Helpers/ThemeHelper.php

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
final class ThemeHelper
2222
{
23+
/**
24+
* Absolute last resort if nothing else is configured.
25+
* Kept for backward compatibility.
26+
*/
2327
public const DEFAULT_THEME = 'chamilo';
2428

2529
public function __construct(
@@ -31,11 +35,19 @@ public function __construct(
3135
private readonly RouterInterface $router,
3236
#[Autowire(service: 'oneup_flysystem.themes_filesystem')]
3337
private readonly FilesystemOperator $filesystem,
38+
// Injected from services.yaml (.env -> THEME_FALLBACK)
39+
#[Autowire(param: 'theme_fallback')]
40+
private readonly string $themeFallback = '',
3441
) {}
3542

3643
/**
37-
* Returns the name of the color theme configured to be applied on the current page.
38-
* The returned name depends on the platform, course or user settings.
44+
* Returns the slug of the theme that should be applied on the current page.
45+
* Precedence:
46+
* 1) Active theme bound to current AccessUrl (DB relation)
47+
* 2) User-selected theme (if enabled)
48+
* 3) Course/LP theme (if enabled)
49+
* 4) THEME_FALLBACK from .env
50+
* 5) DEFAULT_THEME ('chamilo')
3951
*/
4052
public function getVisualTheme(): string
4153
{
@@ -50,97 +62,125 @@ public function getVisualTheme(): string
5062
$visualTheme = null;
5163
$accessUrl = $this->accessUrlHelper->getCurrent();
5264

65+
// 1) Active theme bound to current AccessUrl (DB relation)
5366
if ($accessUrl instanceof AccessUrl) {
5467
$visualTheme = $accessUrl->getActiveColorTheme()?->getColorTheme()->getSlug();
5568
}
5669

57-
if ('true' == $this->settingsManager->getSetting('profile.user_selected_theme')) {
58-
$visualTheme = $this->userHelper->getCurrent()?->getTheme();
70+
// 2) User-selected theme (if setting is enabled)
71+
if ('true' === $this->settingsManager->getSetting('profile.user_selected_theme')) {
72+
$visualTheme = $this->userHelper->getCurrent()?->getTheme() ?: $visualTheme;
5973
}
6074

61-
if ('true' == $this->settingsManager->getSetting('course.allow_course_theme')) {
75+
// 3) Course theme / Learning path theme (if setting is enabled)
76+
if ('true' === $this->settingsManager->getSetting('course.allow_course_theme')) {
6277
$course = $this->cidReqHelper->getCourseEntity();
6378

6479
if ($course) {
6580
$this->settingsCourseManager->setCourse($course);
6681

67-
$visualTheme = $this->settingsCourseManager->getCourseSettingValue('course_theme');
82+
$courseTheme = (string) $this->settingsCourseManager->getCourseSettingValue('course_theme');
83+
if ($courseTheme !== '') {
84+
$visualTheme = $courseTheme;
85+
}
6886

6987
if (1 === (int) $this->settingsCourseManager->getCourseSettingValue('allow_learning_path_theme')) {
70-
$visualTheme = $lp_theme_css;
88+
if (!empty($lp_theme_css)) {
89+
$visualTheme = $lp_theme_css;
90+
}
7191
}
7292
}
7393
}
7494

75-
if (empty($visualTheme)) {
76-
$visualTheme = self::DEFAULT_THEME;
95+
// 4) .env fallback if still empty
96+
if ($visualTheme === null || $visualTheme === '') {
97+
$fallback = \trim((string) $this->themeFallback);
98+
$visualTheme = $fallback !== '' ? $fallback : self::DEFAULT_THEME;
7799
}
78100

79101
return $visualTheme;
80102
}
81103

82104
/**
83-
* @throws FilesystemException
105+
* Decide the theme in which the requested asset actually exists.
106+
* This prevents 404 when the file is only present in DEFAULT_THEME.
84107
*/
85-
public function getFileLocation(string $path): ?string
108+
private function resolveAssetTheme(string $path): ?string
86109
{
87-
$themeName = $this->getVisualTheme();
88-
89-
$locations = [
90-
$themeName.DIRECTORY_SEPARATOR.$path,
91-
self::DEFAULT_THEME.DIRECTORY_SEPARATOR.$path,
92-
];
110+
$visual = $this->getVisualTheme();
93111

94-
foreach ($locations as $location) {
95-
if ($this->filesystem->fileExists($location)) {
96-
return $location;
112+
try {
113+
if ($this->filesystem->fileExists($visual.DIRECTORY_SEPARATOR.$path)) {
114+
return $visual;
115+
}
116+
if ($this->filesystem->fileExists(self::DEFAULT_THEME.DIRECTORY_SEPARATOR.$path)) {
117+
return self::DEFAULT_THEME;
97118
}
119+
} catch (FilesystemException) {
120+
return null;
98121
}
99122

100123
return null;
101124
}
102125

126+
/**
127+
* Resolves a themed file location checking the selected theme first,
128+
* then falling back to DEFAULT_THEME as a last resort.
129+
*/
130+
public function getFileLocation(string $path): ?string
131+
{
132+
$assetTheme = $this->resolveAssetTheme($path);
133+
if ($assetTheme === null) {
134+
return null;
135+
}
136+
137+
return $assetTheme.DIRECTORY_SEPARATOR.$path;
138+
}
139+
140+
/**
141+
* Build a URL for the theme asset, using the theme where the file actually exists.
142+
*/
103143
public function getThemeAssetUrl(string $path, bool $absoluteUrl = false): string
104144
{
105-
try {
106-
if (!$this->getFileLocation($path)) {
107-
return '';
108-
}
109-
} catch (FilesystemException) {
145+
$assetTheme = $this->resolveAssetTheme($path);
146+
if ($assetTheme === null) {
110147
return '';
111148
}
112149

113-
$themeName = $this->getVisualTheme();
114-
115150
return $this->router->generate(
116151
'theme_asset',
117-
['name' => $themeName, 'path' => $path],
152+
['name' => $assetTheme, 'path' => $path],
118153
$absoluteUrl ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH
119154
);
120155
}
121156

157+
/**
158+
* Convenience helper to emit a <link> tag for a theme asset.
159+
*/
122160
public function getThemeAssetLinkTag(string $path, bool $absoluteUrl = false): string
123161
{
124162
$url = $this->getThemeAssetUrl($path, $absoluteUrl);
125-
126-
if (empty($url)) {
163+
if ($url === '') {
127164
return '';
128165
}
129166

130167
return \sprintf('<link rel="stylesheet" href="%s">', $url);
131168
}
132169

170+
/**
171+
* Read raw contents from the themed filesystem.
172+
*/
133173
public function getAssetContents(string $path): string
134174
{
135175
try {
136-
if ($fullPath = $this->getFileLocation($path)) {
176+
$fullPath = $this->getFileLocation($path);
177+
if ($fullPath) {
137178
$stream = $this->filesystem->readStream($fullPath);
138-
139-
$contents = stream_get_contents($stream);
140-
141-
fclose($stream);
142-
143-
return $contents;
179+
$contents = \is_resource($stream) ? stream_get_contents($stream) : false;
180+
if (\is_resource($stream)) {
181+
fclose($stream);
182+
}
183+
return $contents !== false ? $contents : '';
144184
}
145185
} catch (FilesystemException) {
146186
return '';
@@ -149,14 +189,21 @@ public function getAssetContents(string $path): string
149189
return '';
150190
}
151191

192+
/**
193+
* Return a Base64-encoded data URI for the given themed asset.
194+
*/
152195
public function getAssetBase64Encoded(string $path): string
153196
{
154197
try {
155-
if ($fullPath = $this->getFileLocation($path)) {
198+
$fullPath = $this->getFileLocation($path);
199+
if ($fullPath) {
156200
$detector = new ExtensionMimeTypeDetector();
157201
$mimeType = (string) $detector->detectMimeTypeFromFile($fullPath);
202+
$data = $this->getAssetContents($path);
158203

159-
return 'data:'.$mimeType.';base64,'.base64_encode($this->getAssetContents($path));
204+
return $data !== ''
205+
? 'data:'.$mimeType.';base64,'.base64_encode($data)
206+
: '';
160207
}
161208
} catch (FilesystemException) {
162209
return '';
@@ -165,15 +212,19 @@ public function getAssetBase64Encoded(string $path): string
165212
return '';
166213
}
167214

215+
/**
216+
* Return the preferred logo URL for current theme (header/email),
217+
* falling back to DEFAULT_THEME if needed.
218+
*/
168219
public function getPreferredLogoUrl(string $type = 'header', bool $absoluteUrl = false): string
169220
{
170-
$candidates = 'email' === $type
221+
$candidates = $type === 'email'
171222
? ['images/email-logo.svg', 'images/email-logo.png']
172223
: ['images/header-logo.svg', 'images/header-logo.png'];
173224

174225
foreach ($candidates as $relPath) {
175226
$url = $this->getThemeAssetUrl($relPath, $absoluteUrl);
176-
if ('' !== $url) {
227+
if ($url !== '') {
177228
return $url;
178229
}
179230
}

0 commit comments

Comments
 (0)