2020
2121final 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