Completed
Push — master ( d333e6...196d9a )
by Cheren
02:26
created

LessHelper   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 273
Duplicated Lines 2.93 %

Coupling/Cohesion

Components 2
Dependencies 11

Importance

Changes 4
Bugs 1 Features 1
Metric Value
wmc 26
c 4
b 1
f 1
lcom 2
cbo 11
dl 8
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
B _getPlgAssetUrl() 8 22 5
A _normalizeContent() 0 16 3
A _normalizePlgAssetUrl() 0 15 2
A _replaceUrl() 0 5 1
B _replaceUrlCallback() 0 24 3
A _setForce() 0 6 2
A _write() 0 6 1

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
        foreach ($plgPaths as $plgPath) {
172
            $plgPath = FS::clean($plgPath, '/');
173 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...
174
                $path = str_replace($plgPath, '', $path);
175
                return [true, $path];
176
            }
177
        }
178
179
        foreach (Configure::read('plugins') as $name => $plgPath) {
180
            $plgPath = FS::clean($plgPath, '/');
181 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...
182
                $path = Str::low($name) . '/' . str_replace($plgPath, '', $path);
183
                return [true, $path];
184
            }
185
        }
186
187
        return [$isPlugin, $path];
188
    }
189
190
    /**
191
     * Normalize style file.
192
     *
193
     * @param string $path
194
     * @param string $fileHead
195
     * @return mixed
196
     */
197
    protected function _normalizeContent($path, $fileHead)
198
    {
199
        $css = file_get_contents($path);
200
        if (!$this->_configRead('debug')) {
201
            $css = $this->_compress($css, $fileHead);
202
        } else {
203
            list ($first, $second) = explode(PHP_EOL, $css, 2);
204
            if (preg_match('(\/* cacheid:)', $first)) {
205
                $css = $second;
206
            }
207
208
            $css = implode('', [$fileHead, $css]);
209
        }
210
211
        return $this->_replaceUrl($css);
212
    }
213
214
    /**
215
     * Normalize plugin asset url.
216
     *
217
     * @param string $path
218
     * @return string
219
     */
220
    protected function _normalizePlgAssetUrl($path)
221
    {
222
        $details = explode('/', $path, 3);
223
        dump($details);
224
        $pluginName = Inflector::camelize(trim($details[0], '/'));
225
dump($pluginName);
226
        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 224 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...
227
            unset($details[0]);
228
            $source = $pluginName . '.' . ltrim(implode('/', $details), '/');
229
            dump($source);
230
            return $this->_getAssetUrl($source);
231
        }
232
233
        return $this->_getAssetUrl($path);
234
    }
235
236
    /**
237
     * Preg replace url.
238
     *
239
     * @param string $css
240
     * @return mixed
241
     */
242
    protected function _replaceUrl($css)
243
    {
244
        $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
245
        return preg_replace_callback($pattern, [__CLASS__, '_replaceUrlCallback'], $css);
246
    }
247
248
    /**
249
     * Replace url callback.
250
     *
251
     * @param array $match
252
     * @return string
253
     */
254
    protected function _replaceUrlCallback(array $match)
255
    {
256
        $assetPath = str_replace(Router::fullBaseUrl(), '', $match[2]);
257
        $assetPath = trim(FS::clean($assetPath, '/'), '/');
258
        $appDir    = trim(FS::clean(APP_ROOT, '/'), '/');
259
260
        list ($isPlugin, $assetPath) = $this->_getPlgAssetUrl($assetPath);
261
        dump('|||||||||||||||||');
262
dump($isPlugin);
263
dump($assetPath);
264
        if (!$isPlugin) {
265
            $assetPath = str_replace($appDir, '', $assetPath);
266
        }
267
268
        $assetPath = str_replace(Configure::read('App.webroot'), '', $assetPath);
269
        $assetPath = FS::clean($assetPath, '/');
270
        dump($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