Test Setup Failed
Pull Request — master (#69)
by
unknown
03:57
created

CSS   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 90.07%

Importance

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