CSS   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 80.42%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 5
dl 0
loc 328
ccs 115
cts 143
cp 0.8042
rs 6.96
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B export() 0 32 6
C process() 0 87 13
B cssFile() 0 29 7
A wrapIntoCondition() 0 8 2
B expandImports() 0 28 6
A collectCharsets() 0 6 1
A collectImports() 0 6 1
A collectFonts() 0 6 1
A _collect() 0 15 2
B _getImportContent() 0 31 9
A _fetchImportFileContent() 0 11 1
A convertMediaTypeAttributeToMediaQuery() 0 6 4

How to fix   Complexity   

Complex Class

Complex classes like CSS often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CSS, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CSS.php
4
 * @author Revin Roman
5
 * @link https://rmrevin.com
6
 */
7
8
namespace rmrevin\yii\minify\components;
9
10
use yii\helpers\Html;
11
use yii\helpers\Url;
12
13
/**
14
 * Class CSS
15
 * @package rmrevin\yii\minify\components
16
 */
17
class CSS extends MinifyComponent
18
{
19
20 9
    public function export()
21
    {
22 9
        $cssFiles = $this->view->cssFiles;
23
24 9
        $this->view->cssFiles = [];
25
26 9
        $toMinify = [];
27
28 9
        foreach ($cssFiles as $file => $html) {
29 8
            if ($this->thisFileNeedMinify($file, $html)) {
30 8
                if ($this->view->concatCss) {
31 8
                    $toMinify[$file] = $html;
32
                } else {
33 8
                    $this->process([$file => $html]);
34
                }
35
            } else {
36 2
                if (!empty($toMinify)) {
37 2
                    $this->process($toMinify);
38
39 2
                    $toMinify = [];
40
                }
41
42 2
                $this->view->cssFiles[$file] = $html;
43
            }
44
        }
45
46 9
        if (!empty($toMinify)) {
47 6
            $this->process($toMinify);
48
        }
49
50 9
        unset($toMinify);
51 9
    }
52
53
    /**
54
     * @param array $files
55
     */
56 8
    protected function process(array $files)
57
    {
58 8
        $minifyPath = $this->view->minifyPath;
59 8
        $hash = $this->_getSummaryFilesHash($files);
60
61 8
        $resultFile = $minifyPath . DIRECTORY_SEPARATOR . $hash . '.css';
62
63 8
        if (!file_exists($resultFile)) {
64 8
            $css = '';
65
66 8
            foreach ($files as $file => $html) {
67 8
                $cacheKey = $this->buildCacheKey($file);
68
69 8
                $content = $this->getFromCache($cacheKey);
70
71 8
                if (false !== $content) {
72
                    $css .= $content;
73
                    continue;
74
                }
75
76 8
                $path = dirname($file);
77 8
                $file = $this->getAbsoluteFilePath($file);
78
79 8
                $content = '';
80
81 8
                if (!file_exists($file)) {
82
                    \Yii::warning(sprintf('Asset file not found `%s`', $file), __METHOD__);
83 8
                } elseif (!is_readable($file)) {
84
                    \Yii::warning(sprintf('Asset file not readable `%s`', $file), __METHOD__);
85
                } else {
86 8
                    $content = file_get_contents($file);
87
                }
88
89 8
                $result = [];
90
91 8
                preg_match_all('|url\(([^)]+)\)|i', $content, $m);
92
93 8
                if (isset($m[0])) {
94 8
                    foreach ((array)$m[0] as $k => $v) {
95 6
                        if (in_array(strpos($m[1][$k], 'data:'), [0, 1], true)) {
96 6
                            continue;
97
                        }
98
99 6
                        $url = str_replace(['\'', '"'], '', $m[1][$k]);
100
101 6
                        if ($this->isUrl($url)) {
102 6
                            $result[$m[1][$k]] = $url;
103
                        } else {
104 6
                            $result[$m[1][$k]] = $path . '/' . $url;
105
                        }
106
                    }
107
108 8
                    $content = strtr($content, $result);
109
                }
110
111 8
                $this->expandImports($content);
112
113 8
                $this->convertMediaTypeAttributeToMediaQuery($html, $content);
114
115 8
                if ($this->view->minifyCss) {
116 8
                    $content = \Minify_CSSmin::minify($content);
117
                }
118
119 8
                $this->saveToCache($cacheKey, $content);
120
121 8
                $css .= $content;
122
            }
123
124 8
            $charsets = $this->collectCharsets($css);
125 8
            $imports = $this->collectImports($css);
126 8
            $fonts = $this->collectFonts($css);
127
128 8
            if (false !== $this->view->forceCharset) {
129 7
                $charsets = '@charset "' . (string)$this->view->forceCharset . '";' . "\n";
130
            }
131
132 8
            file_put_contents($resultFile, $charsets . $imports . $fonts . $css);
133
134 8
            if (false !== $this->view->fileMode) {
135 8
                @chmod($resultFile, $this->view->fileMode);
136
            }
137
        }
138
139 8
        $file = $this->prepareResultFile($resultFile);
140
141 8
        $this->view->cssFiles[$file] = self::cssFile($file, $this->view->cssOptions);
142 8
    }
143
144
    /**
145
     * @param $url
146
     * @param array $options
147
     * @return string
148
     */
149 8
    public static function cssFile($url, $options = [])
150
    {
151 8
        if (!isset($options['rel'])) {
152 8
            $options['rel'] = 'stylesheet';
153
        }
154 8
        $options['href'] = Url::to($url);
155
156 8
        if (isset($options['condition'])) {
157
            $condition = $options['condition'];
158
            unset($options['condition']);
159
            return self::wrapIntoCondition(Html::tag('link', '', $options), $condition);
160 8
        } elseif (isset($options['noscript']) && $options['noscript'] === true) {
161
            unset($options['noscript']);
162
            return '<noscript>' . Html::tag('link', '', $options) . '</noscript>';
163 8
        } elseif (isset($options['preload']) && $options['preload'] === true) {
164
            unset($options['preload']);
165
            $preloadOptions = [
166
                "as" => "style",
167
                "rel" => "preload",
168
                "href" => $options["href"]
169
            ];
170
            $preload = Html::tag('link', '', $preloadOptions);
171
            $preload .= Html::tag('link', '', $options);
172
173
            return $preload;
174
        }
175
176 8
        return Html::tag('link', '', $options);
177
    }
178
179
    /**
180
     * @param $content
181
     * @param $condition
182
     * @return string
183
     */
184
    private static function wrapIntoCondition($content, $condition)
185
    {
186
        if (strpos($condition, '!IE') !== false) {
187
            return "<!--[if $condition]><!-->\n" . $content . "\n<!--<![endif]-->";
188
        }
189
190
        return "<!--[if $condition]>\n" . $content . "\n<![endif]-->";
191
    }
192
193
    /**
194
     * @param string $code
195
     */
196 8
    protected function expandImports(&$code)
197
    {
198 8
        if (true !== $this->view->expandImports) {
199 1
            return;
200
        }
201
202 7
        preg_match_all('|\@import\s([^;]+);|i', str_replace('&amp;', '&', $code), $m);
203
204 7
        if (!isset($m[0])) {
205
            return;
206
        }
207
208 7
        foreach ((array)$m[0] as $k => $v) {
209 5
            $importUrl = $m[1][$k];
210
211 5
            if (empty($importUrl)) {
212
                continue;
213
            }
214
215 5
            $importContent = $this->_getImportContent($importUrl);
216
217 5
            if (null === $importContent) {
218
                continue;
219
            }
220
221 5
            $code = str_replace($m[0][$k], $importContent, $code);
222
        }
223 7
    }
224
225
    /**
226
     * @param string $code
227
     * @return string
228
     */
229
    protected function collectCharsets(&$code)
230
    {
231 8
        return $this->_collect($code, '|\@charset[^;]+|is', function ($string) {
232
            return $string . ';';
233 8
        });
234
    }
235
236
    /**
237
     * @param string $code
238
     * @return string
239
     */
240
    protected function collectImports(&$code)
241
    {
242 8
        return $this->_collect($code, '|\@import[^;]+|is', function ($string) {
243 1
            return $string . ';';
244 8
        });
245
    }
246
247
    /**
248
     * @param string $code
249
     * @return string
250
     */
251
    protected function collectFonts(&$code)
252
    {
253 8
        return $this->_collect($code, '|\@font-face\{[^}]+\}|is', function ($string) {
254 5
            return $string;
255 8
        });
256
    }
257
258
    /**
259
     * @param string $code
260
     * @param string $pattern
261
     * @param callable $handler
262
     * @return string
263
     */
264 8
    protected function _collect(&$code, $pattern, $handler)
265
    {
266 8
        $result = '';
267
268 8
        preg_match_all($pattern, $code, $m);
269
270 8
        foreach ((array)$m[0] as $string) {
271 6
            $string = $handler($string);
272 6
            $code = str_replace($string, '', $code);
273
274 6
            $result .= $string . PHP_EOL;
275
        }
276
277 8
        return $result;
278
    }
279
280
    /**
281
     * @param string|null $url
282
     * @return null|string
283
     */
284 5
    protected function _getImportContent($url)
285
    {
286 5
        if (null === $url || '' === $url) {
287
            return null;
288
        }
289
290 5
        if (0 !== mb_strpos($url, 'url(')) {
291
            return null;
292
        }
293
294 5
        $currentUrl = str_replace(['url(\'', 'url("', 'url(', '\')', '")', ')'], '', $url);
295
296 5
        if (0 === mb_strpos($currentUrl, '//')) {
297 5
            $currentUrl = preg_replace('|^//|', 'http://', $currentUrl, 1);
298
        }
299
300 5
        if (null === $currentUrl || '' === $currentUrl) {
301
            return null;
302
        }
303
304 5
        if (!in_array(mb_substr($currentUrl, 0, 4), ['http', 'ftp:'], true)) {
305
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
306 5
            $currentUrl = $this->view->basePath . $currentUrl;
307
        }
308
309 5
        if (false === $currentUrl) {
310
            return null;
311
        }
312
313 5
        return $this->_fetchImportFileContent($currentUrl);
314
    }
315
316
    /**
317
     * @param string $url
318
     * @return bool|string
319
     */
320 5
    protected function _fetchImportFileContent($url)
321
    {
322
        $context = [
323
            'ssl' => [
324 5
                'verify_peer'      => false,
325
                'verify_peer_name' => false,
326
            ],
327
        ];
328
329 5
        return file_get_contents($url, null, stream_context_create($context));
330
    }
331
332
    /**
333
     * If the <link> tag has a media="type" attribute, wrap the content in an equivalent media query
334
     * @param string $html HTML of the link tag
335
     * @param string $content CSS content
336
     * @return string $content CSS content wrapped with media query, if applicable
337
     */
338 8
    protected function convertMediaTypeAttributeToMediaQuery($html, &$content)
339
    {
340 8
        if (preg_match('/\bmedia=(["\'])([^"\']+)\1/i', $html, $m) && isset($m[2]) && $m[2] !== 'all') {
341 1
            $content = '@media ' . $m[2] . ' {' . $content . '}';
342
        }
343 8
    }
344
}
345