Completed
Push — master ( 0bff19...1b573a )
by Basil
02:06
created

ThemeManager::registerTheme()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
rs 9.9
cc 2
nc 2
nop 1
1
<?php
2
3
namespace luya\theme;
4
5
use luya\base\PackageConfig;
6
use luya\Exception;
7
use luya\helpers\Json;
8
use Yii;
9
use yii\base\InvalidArgumentException;
10
use yii\base\InvalidConfigException;
11
12
/**
13
 * Core theme manager for LUYA.
14
 *
15
 * This component manage available themes via file system and the actual display themes.
16
 *
17
 * @property bool $hasActiveTheme
18
 * @property Theme $activeTheme
19
 *
20
 * @author Bennet Klarhölter <[email protected]>
21
 * @since  1.1.0
22
 */
23
class ThemeManager extends \yii\base\Component
24
{
25
    /**
26
     * Name of the event before the active theme will be setup.
27
     */
28
    const EVENT_BEFORE_SETUP = 'eventBeforeSetup';
29
    
30
    /**
31
     * Name of the theme which should be activated on setup.
32
     *
33
     * @var string
34
     */
35
    public $activeThemeName;
36
    
37
    /**
38
     * @var ThemeConfig[]
39
     */
40
    private $_themes = [];
41
    
42
    /**
43
     * Read the theme.json and create a new \luya\theme\ThemeConfig for the given base path.
44
     *
45
     * @param string $basePath
46
     *
47
     * @return ThemeConfig
48
     * @throws Exception
49
     * @throws InvalidConfigException
50
     */
51
    protected static function loadThemeConfig(string $basePath)
52
    {
53
        if (strpos($basePath, '@') === 0) {
54
            $dir = Yii::getAlias($basePath);
55
        } elseif (strpos($basePath, '/') !== 0) {
56
            $dir = $basePath = Yii::$app->basePath . DIRECTORY_SEPARATOR . $basePath;
57
        } else {
58
            $dir = $basePath;
59
        }
60
        
61
        if (!is_dir($dir) || !is_readable($dir)) {
62
            throw new Exception('Theme directory not exists or readable: ' . $dir);
63
        }
64
        
65
        $themeFile = $dir . '/theme.json';
66
        if (!file_exists($themeFile)) {
67
            throw new InvalidConfigException('Theme config file missing at: ' . $themeFile);
68
        }
69
        
70
        $config = Json::decode(file_get_contents($themeFile)) ?: [];
71
    
72
        return new ThemeConfig($basePath, $config);
73
    }
74
    
75
    /**
76
     * Setup active theme
77
     *
78
     * @throws Exception
79
     * @throws InvalidConfigException
80
     * @throws \yii\db\Exception
81
     */
82
    final public function setup()
83
    {
84
        if ($this->activeTheme instanceof Theme) {
85
            // Active theme already loaded
86
            return;
87
        }
88
        
89
        $basePath = $this->getActiveThemeBasePath();
90
        if ($basePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basePath of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
91
            $this->beforeSetup($basePath);
92
        
93
            $themeConfig = $this->getThemeByBasePath($basePath);
94
            $theme = new Theme($themeConfig);
95
            $this->activate($theme);
96
        }
97
    }
98
    
99
    /**
100
     * Trigger the {{\luya\theme\ThemeManager::EVENT_BEFORE_SETUP}} event.
101
     *
102
     * @param string $basePath
103
     */
104
    protected function beforeSetup(string &$basePath)
105
    {
106
        $event = new SetupEvent();
107
        $event->basePath = $basePath;
108
        $this->trigger(self::EVENT_BEFORE_SETUP, $event);
109
        
110
        $basePath = $event->basePath;
111
    }
112
    
113
    /**
114
     * Get base path of active theme.
115
     *
116
     * @return string
117
     * @throws \yii\db\Exception
118
     */
119
    protected function getActiveThemeBasePath()
120
    {
121
        if (!empty($this->activeThemeName) && is_string($this->activeThemeName)) {
122
            return $this->activeThemeName;
123
        }
124
        
125
        return false;
126
    }
127
    
128
    /**
129
     * Get all theme configs as array list.
130
     *
131
     * @return ThemeConfig[]
132
     * @throws Exception
133
     * @throws InvalidConfigException
134
     */
135
    public function getThemes()
136
    {
137
        if ($this->_themes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_themes of type luya\theme\ThemeConfig[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
138
            return $this->_themes;
139
        }
140
        
141
        $themeDefinitions = $this->getThemeDefinitions();
142
        
143
        foreach ($themeDefinitions as $themeDefinition) {
144
            $themeConfig = static::loadThemeConfig($themeDefinition);
145
            $this->registerTheme($themeConfig);
146
        }
147
        
148
        return $this->_themes;
149
    }
150
    
151
    /**
152
     * Get theme definitions by search in `@app/themes` and the `Yii::$app->getPackageInstaller()`
153
     *
154
     * @return string[]
155
     */
156
    protected function getThemeDefinitions()
157
    {
158
        $themeDefinitions = [];
159
        
160
        if (file_exists(Yii::getAlias('@app/themes'))) {
161
            foreach (glob(Yii::getAlias('@app/themes/*')) as $dirPath) {
162
                $themeDefinitions[] = "@app/themes/" . basename($dirPath);
163
            }
164
        }
165
        
166
        foreach (Yii::$app->getPackageInstaller()->getConfigs() as $config) {
167
            /** @var PackageConfig $config */
168
            $themeDefinitions = array_merge($themeDefinitions, $config->themes);
169
        }
170
        
171
        return $themeDefinitions;
172
    }
173
    
174
    /**
175
     * @param string $basePath
176
     *
177
     * @return ThemeConfig
178
     * @throws Exception
179
     * @throws InvalidConfigException
180
     */
181
    public function getThemeByBasePath(string $basePath)
182
    {
183
        $themes = $this->getThemes();
184
        
185
        if (!isset($themes[$basePath])) {
186
            throw new InvalidArgumentException("Theme $basePath could not loaded.");
187
        }
188
        
189
        return $themes[$basePath];
190
    }
191
    
192
    /**
193
     * Register a theme config and set the path alias with the name of the theme.
194
     *
195
     * @param ThemeConfig $themeConfig Base path of the theme.
196
     *
197
     * @throws InvalidConfigException
198
     */
199
    protected function registerTheme(ThemeConfig $themeConfig)
200
    {
201
        $basePath = $themeConfig->getBasePath();
202
        if (isset($this->_themes[$basePath])) {
203
            throw new InvalidArgumentException("Theme $basePath already registered.");
204
        }
205
        
206
        $this->_themes[$basePath] = $themeConfig;
207
        
208
        Yii::setAlias('@' . basename($basePath) . 'Theme', $basePath);
209
    }
210
    
211
    /**
212
     * Change the active theme in the \yii\base\View component and set the `activeTheme ` alias to new theme base path.
213
     *
214
     * @param Theme $theme
215
     */
216
    protected function activate(Theme $theme)
217
    {
218
        Yii::$app->view->theme = $theme;
219
        Yii::setAlias('activeTheme', $theme->basePath);
220
        
221
        $this->setActiveTheme($theme);
222
    }
223
    
224
    /**
225
     * @var Theme|null
226
     */
227
    private $_activeTheme;
228
    
229
    /**
230
     * Get the active theme. Null if no theme is activated.
231
     *
232
     * @return Theme|null
233
     */
234
    public function getActiveTheme()
235
    {
236
        return $this->_activeTheme;
237
    }
238
    
239
    /**
240
     * Change the active theme.
241
     *
242
     * @param Theme $theme
243
     */
244
    protected function setActiveTheme(Theme $theme)
245
    {
246
        $this->_activeTheme = $theme;
247
    }
248
    
249
    /**
250
     * Check if a theme is activated.
251
     *
252
     * @return bool
253
     */
254
    public function getHasActiveTheme()
255
    {
256
        return $this->getActiveTheme() !== null;
257
    }
258
}
259