Completed
Push — master ( 196d9a...6ecdc7 )
by Cheren
02:20
created

LessHelper   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 273
Duplicated Lines 3.3 %

Coupling/Cohesion

Components 2
Dependencies 11

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 26
c 5
b 1
f 1
lcom 2
cbo 11
dl 9
loc 273
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A initialize() 0 13 1
B process() 0 28 3
A _compress() 0 22 1
A _findWebRoot() 0 12 3
A _getAssetUrl() 0 4 1
A _setForce() 0 6 2
A _write() 0 6 1
B _getPlgAssetUrl() 9 27 5
A _normalizeContent() 0 16 3
A _normalizePlgAssetUrl() 0 13 2
A _replaceUrl() 0 5 1
A _replaceUrlCallback() 0 21 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * CakeCMS Core
4
 *
5
 * This file is part of the of the simple cms based on CakePHP 3.
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 *
9
 * @package   Core
10
 * @license   MIT
11
 * @copyright MIT License http://www.opensource.org/licenses/mit-license.php
12
 * @link      https://github.com/CakeCMS/Core".
13
 * @author    Sergey Kalistratov <[email protected]>
14
 */
15
16
namespace Core\View\Helper;
17
18
use Core\Plugin;
19
use JBZoo\Utils\FS;
20
use JBZoo\Utils\Str;
21
use Core\Lib\Less\Less;
22
use Cake\Routing\Router;
23
use Cake\Core\Configure;
24
use Cake\Filesystem\File;
25
use Cake\Utility\Inflector;
26
27
/**
28
 * Class LessHelper
29
 *
30
 * @package Core\View\Helper
31
 * @property \Core\View\Helper\UrlHelper $Url
32
 */
33
class LessHelper extends AppHelper
34
{
35
36
    /**
37
     * List of helpers used by this helper.
38
     *
39
     * @var array
40
     */
41
    public $helpers = [
42
        'Url' => ['className' => 'Core.Url'],
43
    ];
44
45
    /**
46
     * Constructor hook method.
47
     *
48
     * @param array $config
49
     */
50
    public function initialize(array $config)
51
    {
52
        $corePath  = Plugin::path('Core') . Configure::read('App.webroot') . '/' . Configure::read('App.lessBaseUrl');
53
        $cachePath = WWW_ROOT . Configure::read('App.cssBaseUrl') . Configure::read('App.cacheDir');
54
55
        $this->config('root_path', APP_ROOT);
56
        $this->config('cache_path', $cachePath);
57
        $this->config('root_url', Router::fullBaseUrl());
58
        $this->config('debug', Configure::read('debug'));
59
        $this->config('import_paths', [realpath($corePath) => realpath($corePath)]);
60
61
        parent::initialize($config);
62
    }
63
64
    /**
65
     * Process less file.
66
     *
67
     * @param string $source
68
     * @param bool $force
69
     * @return string|null
70
     * @throws \JBZoo\Less\Exception
71
     */
72
    public function process($source, $force = true)
73
    {
74
        list ($webRoot, $source) = $this->_findWebRoot($source);
75
        $lessFile = FS::clean($webRoot . Configure::read('App.lessBaseUrl') . $source, '/');
76
77
        $this->_setForce($force);
78
        $less = new Less($this->_config);
79
80
        if (!FS::isFile($lessFile)) {
81
            return null;
82
        }
83
84
        list($source, $isExpired) = $less->compile($lessFile);
85
86
        if ($isExpired) {
87
            $cacheId  = FS::firstLine($source);
88
            $comment  = '/* resource:' . str_replace(FS::clean(ROOT, '/'), '', $lessFile) . ' */' . PHP_EOL;
89
            $fileHead = implode('', [$cacheId, Str::low($comment)]);
90
91
            $css = $this->_normalizeContent($source, $fileHead);
92
            $this->_write($source, $css);
93
        }
94
95
        $source = str_replace(FS::clean(APP_ROOT . '/' . Configure::read('App.webroot'), '/'), '', $source);
96
        $source = str_replace(Configure::read('App.cssBaseUrl'), '', $source);
97
98
        return $source;
99
    }
100
101
    /**
102
     * CSS compressing.
103
     *
104
     * @param string $code
105
     * @param string $cacheId
106
     * @return string
107
     */
108
    protected function _compress($code, $cacheId)
109
    {
110
        $code = (string) $code;
111
112
        // remove comments
113
        $code = preg_replace('#/\*[^*]*\*+([^/][^*]*\*+)*/#ius', '', $code);
114
115
        $code = str_replace(
116
            ["\r\n", "\r", "\n", "\t", '  ', '    ', ' {', '{ ', ' }', '; ', ';;', ';;;', ';;;;', ';}'],
117
            ['', '', '', '', '', '', '{', '{', '}', ';', ';', ';', ';', '}'],
118
            $code
119
        ); // remove tabs, spaces, newlines, etc.
120
121
        // remove spaces after and before colons
122
        $code = preg_replace('#([a-z\-])(:\s*|\s*:\s*|\s*:)#ius', '$1:', $code);
123
124
        // spaces before "!important"
125
        $code = preg_replace('#(\s*\!important)#ius', '!important', $code);
126
        $code = Str::trim($code);
127
128
        return implode('', [$cacheId, $code]);
129
    }
130
131
    /**
132
     * Find source webroot dir.
133
     *
134
     * @param string $source
135
     * @return array
136
     */
137
    protected function _findWebRoot($source)
138
    {
139
        $webRootDir = Configure::read('App.webroot');
140
        $webRoot    = APP_ROOT . DS . $webRootDir . DS;
141
142
        list ($plugin, $source) = $this->_View->pluginSplit($source);
143
        if ($plugin !== null && Plugin::loaded($plugin)) {
144
            $webRoot = Plugin::path($plugin) . $webRootDir . DS;
145
        }
146
147
        return [FS::clean($webRoot, '/'), $source];
148
    }
149
150
    /**
151
     * Get asset url by source.
152
     *
153
     * @param string $source (Example: TestPlugin.path/to/image.png)
154
     * @return string
155
     */
156
    protected function _getAssetUrl($source)
157
    {
158
        return 'url("' . $this->Url->assetUrl($source, ['fullBase' => true]) . '")';
159
    }
160
161
    /**
162
     * Get plugin asset url.
163
     *
164
     * @param string $path
165
     * @return array
166
     */
167
    protected function _getPlgAssetUrl($path)
168
    {
169
        $isPlugin = false;
170
        $plgPaths = Configure::read('App.paths.plugins');
171
        dump($plgPaths);
172
        foreach ($plgPaths as $plgPath) {
173
            $plgPath = FS::clean($plgPath, '/');
174
            dump($path);
175
            dump($plgPath);
176
            dump(preg_match('(' . quotemeta($plgPath) . ')', $path));
177 View Code Duplication
            if (preg_match('(' . quotemeta($plgPath) . ')', $path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
                $path = str_replace($plgPath, '', $path);
179
                dump($path);
180
                return [true, $path];
181
            }
182
        }
183
184
        foreach (Configure::read('plugins') as $name => $plgPath) {
185
            $plgPath = FS::clean($plgPath, '/');
186 View Code Duplication
            if (preg_match('(' . quotemeta($plgPath) . ')', $path)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
                $path = Str::low($name) . '/' . str_replace($plgPath, '', $path);
188
                return [true, $path];
189
            }
190
        }
191
192
        return [$isPlugin, $path];
193
    }
194
195
    /**
196
     * Normalize style file.
197
     *
198
     * @param string $path
199
     * @param string $fileHead
200
     * @return mixed
201
     */
202
    protected function _normalizeContent($path, $fileHead)
203
    {
204
        $css = file_get_contents($path);
205
        if (!$this->_configRead('debug')) {
206
            $css = $this->_compress($css, $fileHead);
207
        } else {
208
            list ($first, $second) = explode(PHP_EOL, $css, 2);
209
            if (preg_match('(\/* cacheid:)', $first)) {
210
                $css = $second;
211
            }
212
213
            $css = implode('', [$fileHead, $css]);
214
        }
215
216
        return $this->_replaceUrl($css);
217
    }
218
219
    /**
220
     * Normalize plugin asset url.
221
     *
222
     * @param string $path
223
     * @return string
224
     */
225
    protected function _normalizePlgAssetUrl($path)
226
    {
227
        $details = explode('/', $path, 3);
228
        $pluginName = Inflector::camelize(trim($details[0], '/'));
229
230
        if (Plugin::loaded($pluginName)) {
0 ignored issues
show
Bug introduced by
It seems like $pluginName defined by \Cake\Utility\Inflector:...trim($details[0], '/')) on line 228 can also be of type boolean; however, Cake\Core\Plugin::loaded() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
231
            unset($details[0]);
232
            $source = $pluginName . '.' . ltrim(implode('/', $details), '/');
233
            return $this->_getAssetUrl($source);
234
        }
235
236
        return $this->_getAssetUrl($path);
237
    }
238
239
    /**
240
     * Preg replace url.
241
     *
242
     * @param string $css
243
     * @return mixed
244
     */
245
    protected function _replaceUrl($css)
246
    {
247
        $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
248
        return preg_replace_callback($pattern, [__CLASS__, '_replaceUrlCallback'], $css);
249
    }
250
251
    /**
252
     * Replace url callback.
253
     *
254
     * @param array $match
255
     * @return string
256
     */
257
    protected function _replaceUrlCallback(array $match)
258
    {
259
        $assetPath = str_replace(Router::fullBaseUrl(), '', $match[2]);
260
        $assetPath = trim(FS::clean($assetPath, '/'), '/');
261
        $appDir    = trim(FS::clean(APP_ROOT, '/'), '/');
262
263
        list ($isPlugin, $assetPath) = $this->_getPlgAssetUrl($assetPath);
264
265
        if (!$isPlugin) {
266
            $assetPath = str_replace($appDir, '', $assetPath);
267
        }
268
269
        $assetPath = str_replace(Configure::read('App.webroot'), '', $assetPath);
270
        $assetPath = FS::clean($assetPath, '/');
271
272
        if ($isPlugin) {
273
            return $this->_normalizePlgAssetUrl($assetPath);
274
        }
275
276
        return $this->_getAssetUrl($assetPath);
277
    }
278
279
    /**
280
     * Set less process force.
281
     *
282
     * @param bool $force
283
     * @return void
284
     */
285
    protected function _setForce($force = false)
286
    {
287
        if ($force) {
288
            $this->config('force', true);
289
        }
290
    }
291
292
    /**
293
     * Write content in to the file.
294
     *
295
     * @param string $path
296
     * @param string $content
297
     * @return void
298
     */
299
    protected function _write($path, $content)
300
    {
301
        $File = new File($path);
302
        $File->write($content);
303
        $File->exists();
304
    }
305
}
306