Passed
Push — master ( 78e3fe...4914df )
by
unknown
11:31
created

ThemeHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 8
dl 0
loc 13
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
declare(strict_types=1);
6
7
namespace Chamilo\CoreBundle\Helpers;
8
9
use Chamilo\CoreBundle\Entity\AccessUrl;
10
use Chamilo\CoreBundle\Settings\SettingsManager;
11
use Chamilo\CourseBundle\Settings\SettingsCourseManager;
12
use League\Flysystem\FilesystemException;
13
use League\Flysystem\FilesystemOperator;
14
use League\MimeTypeDetection\ExtensionMimeTypeDetector;
15
use Symfony\Component\DependencyInjection\Attribute\Autowire;
16
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
17
use Symfony\Component\Routing\RouterInterface;
18
19
use const DIRECTORY_SEPARATOR;
20
21
final class ThemeHelper
22
{
23
    /**
24
     * Absolute last resort if nothing else is configured.
25
     * Kept for backward compatibility.
26
     */
27
    public const DEFAULT_THEME = 'chamilo';
28
29
    public function __construct(
30
        private readonly AccessUrlHelper $accessUrlHelper,
31
        private readonly SettingsManager $settingsManager,
32
        private readonly UserHelper $userHelper,
33
        private readonly CidReqHelper $cidReqHelper,
34
        private readonly SettingsCourseManager $settingsCourseManager,
35
        private readonly RouterInterface $router,
36
        #[Autowire(service: 'oneup_flysystem.themes_filesystem')]
37
        private readonly FilesystemOperator $filesystem,
38
        // Injected from services.yaml (.env -> THEME_FALLBACK)
39
        #[Autowire(param: 'theme_fallback')]
40
        private readonly string $themeFallback = '',
41
    ) {}
42
43
    /**
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')
51
     */
52
    public function getVisualTheme(): string
53
    {
54
        static $visualTheme;
55
56
        global $lp_theme_css;
57
58
        if (isset($visualTheme)) {
59
            return $visualTheme;
60
        }
61
62
        $visualTheme = null;
63
        $accessUrl = $this->accessUrlHelper->getCurrent();
64
65
        // 1) Active theme bound to current AccessUrl (DB relation)
66
        if ($accessUrl instanceof AccessUrl) {
67
            $visualTheme = $accessUrl->getActiveColorTheme()?->getColorTheme()->getSlug();
68
        }
69
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;
73
        }
74
75
        // 3) Course theme / Learning path theme (if setting is enabled)
76
        if ('true' === $this->settingsManager->getSetting('course.allow_course_theme')) {
77
            $course = $this->cidReqHelper->getCourseEntity();
78
79
            if ($course) {
80
                $this->settingsCourseManager->setCourse($course);
81
82
                $courseTheme = (string) $this->settingsCourseManager->getCourseSettingValue('course_theme');
83
                if ($courseTheme !== '') {
84
                    $visualTheme = $courseTheme;
85
                }
86
87
                if (1 === (int) $this->settingsCourseManager->getCourseSettingValue('allow_learning_path_theme')) {
88
                    if (!empty($lp_theme_css)) {
89
                        $visualTheme = $lp_theme_css;
90
                    }
91
                }
92
            }
93
        }
94
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;
99
        }
100
101
        return $visualTheme;
102
    }
103
104
    /**
105
     * Decide the theme in which the requested asset actually exists.
106
     * This prevents 404 when the file is only present in DEFAULT_THEME.
107
     */
108
    private function resolveAssetTheme(string $path): ?string
109
    {
110
        $visual = $this->getVisualTheme();
111
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;
118
            }
119
        } catch (FilesystemException) {
120
            return null;
121
        }
122
123
        return null;
124
    }
125
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
     */
143
    public function getThemeAssetUrl(string $path, bool $absoluteUrl = false): string
144
    {
145
        $assetTheme = $this->resolveAssetTheme($path);
146
        if ($assetTheme === null) {
147
            return '';
148
        }
149
150
        return $this->router->generate(
151
            'theme_asset',
152
            ['name' => $assetTheme, 'path' => $path],
153
            $absoluteUrl ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH
154
        );
155
    }
156
157
    /**
158
     * Convenience helper to emit a <link> tag for a theme asset.
159
     */
160
    public function getThemeAssetLinkTag(string $path, bool $absoluteUrl = false): string
161
    {
162
        $url = $this->getThemeAssetUrl($path, $absoluteUrl);
163
        if ($url === '') {
164
            return '';
165
        }
166
167
        return \sprintf('<link rel="stylesheet" href="%s">', $url);
168
    }
169
170
    /**
171
     * Read raw contents from the themed filesystem.
172
     */
173
    public function getAssetContents(string $path): string
174
    {
175
        try {
176
            $fullPath = $this->getFileLocation($path);
177
            if ($fullPath) {
178
                $stream = $this->filesystem->readStream($fullPath);
179
                $contents = \is_resource($stream) ? stream_get_contents($stream) : false;
180
                if (\is_resource($stream)) {
181
                    fclose($stream);
182
                }
183
                return $contents !== false ? $contents : '';
184
            }
185
        } catch (FilesystemException) {
186
            return '';
187
        }
188
189
        return '';
190
    }
191
192
    /**
193
     * Return a Base64-encoded data URI for the given themed asset.
194
     */
195
    public function getAssetBase64Encoded(string $path): string
196
    {
197
        try {
198
            $fullPath = $this->getFileLocation($path);
199
            if ($fullPath) {
200
                $detector = new ExtensionMimeTypeDetector();
201
                $mimeType = (string) $detector->detectMimeTypeFromFile($fullPath);
202
                $data = $this->getAssetContents($path);
203
204
                return $data !== ''
205
                    ? 'data:'.$mimeType.';base64,'.base64_encode($data)
206
                    : '';
207
            }
208
        } catch (FilesystemException) {
209
            return '';
210
        }
211
212
        return '';
213
    }
214
215
    /**
216
     * Return the preferred logo URL for current theme (header/email),
217
     * falling back to DEFAULT_THEME if needed.
218
     */
219
    public function getPreferredLogoUrl(string $type = 'header', bool $absoluteUrl = false): string
220
    {
221
        $candidates = $type === 'email'
222
            ? ['images/email-logo.svg', 'images/email-logo.png']
223
            : ['images/header-logo.svg', 'images/header-logo.png'];
224
225
        foreach ($candidates as $relPath) {
226
            $url = $this->getThemeAssetUrl($relPath, $absoluteUrl);
227
            if ($url !== '') {
228
                return $url;
229
            }
230
        }
231
232
        return '';
233
    }
234
}
235