LessHelper::_getAssetUrl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\Less\Less;
19
use JBZoo\Utils\FS;
20
use JBZoo\Utils\Str;
21
use Core\Core\Plugin;
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
56
            ->setConfig('root_path', APP_ROOT)
57
            ->setConfig('cache_path', $cachePath)
58
            ->setConfig('root_url', Router::fullBaseUrl())
59
            ->setConfig('debug', Configure::read('debug'))
60
            ->setConfig('import_paths', [realpath($corePath) => realpath($corePath)]);
61
62
        parent::initialize($config);
63
    }
64
65
    /**
66
     * Process less file.
67
     *
68
     * @param string $source
69
     * @param bool $force
70
     * @return string|null
71
     * @throws \JBZoo\Less\Exception
72
     */
73
    public function process($source, $force = true)
74
    {
75
        list ($webRoot, $source) = $this->_findWebRoot($source);
76
        $lessFile = FS::clean($webRoot . Configure::read('App.lessBaseUrl') . $source, '/');
77
78
        $this->_setForce($force);
79
        $less = new Less($this->_config);
80
81
        if (!FS::isFile($lessFile)) {
82
            return null;
83
        }
84
85
        list($source, $isExpired) = $less->compile($lessFile);
86
87
        if ($isExpired) {
88
            $cacheId  = FS::firstLine($source);
89
            $comment  = '/* resource:' . str_replace(FS::clean(ROOT, '/'), '', $lessFile) . ' */' . PHP_EOL;
90
            $fileHead = implode('', [$cacheId, Str::low($comment)]);
91
92
            $css = $this->_normalizeContent($source, $fileHead);
93
            $this->_write($source, $css);
94
        }
95
96
        $source = str_replace(FS::clean(APP_ROOT . '/' . Configure::read('App.webroot'), '/'), '', $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
144
        if ($plugin !== null && Plugin::isLoaded($plugin)) {
145
            $webRoot = Plugin::path($plugin) . $webRootDir . DS;
146
        }
147
148
        return [FS::clean($webRoot, '/'), $source];
149
    }
150
151
    /**
152
     * Get asset url by source.
153
     *
154
     * @param string $source (Example: TestPlugin.path/to/image.png)
155
     * @return string
156
     */
157
    protected function _getAssetUrl($source)
158
    {
159
        return 'url("' . $this->Url->assetUrl($source, ['fullBase' => true]) . '")';
160
    }
161
162
    /**
163
     * Get plugin asset url.
164
     *
165
     * @param string $path
166
     * @return array
167
     */
168
    protected function _getPlgAssetUrl($path)
169
    {
170
        $isPlugin = false;
171
        $plgPaths = Configure::read('App.paths.plugins');
172
        foreach ($plgPaths as $plgPath) {
173
            $plgPath = ltrim(FS::clean($plgPath, '/'), '/');
174 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...
175
                $path = str_replace($plgPath, '', $path);
176
                return [true, $path];
177
            }
178
        }
179
180
        foreach (Configure::read('plugins') as $name => $plgPath) {
181
            $plgPath = FS::clean($plgPath, '/');
182 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...
183
                $path = Str::low($name) . '/' . str_replace($plgPath, '', $path);
184
                return [true, $path];
185
            }
186
        }
187
188
        return [$isPlugin, $path];
189
    }
190
191
    /**
192
     * Normalize style file.
193
     *
194
     * @param string $path
195
     * @param string $fileHead
196
     * @return mixed
197
     */
198
    protected function _normalizeContent($path, $fileHead)
199
    {
200
        $css = file_get_contents($path);
201
        if (!$this->_configRead('debug')) {
202
            $css = $this->_compress($css, $fileHead);
203
        } else {
204
            list ($first, $second) = explode(PHP_EOL, $css, 2);
205
            if (preg_match('(\/* cacheid:)', $first)) {
206
                $css = $second;
207
            }
208
209
            $css = implode('', [$fileHead, $css]);
210
        }
211
212
        return $this->_replaceUrl($css);
213
    }
214
215
    /**
216
     * Normalize plugin asset url.
217
     *
218
     * @param string $path
219
     * @return string
220
     */
221
    protected function _normalizePlgAssetUrl($path)
222
    {
223
        $details = explode('/', $path, 3);
224
        $pluginName = Inflector::camelize(trim($details[0], '/'));
225
226
        if (Plugin::isLoaded($pluginName)) {
227
            unset($details[0]);
228
            $source = $pluginName . '.' . ltrim(implode('/', $details), '/');
229
            return $this->_getAssetUrl($source);
230
        }
231
232
        return $this->_getAssetUrl($path);
233
    }
234
235
    /**
236
     * Preg replace url.
237
     *
238
     * @param string $css
239
     * @return mixed
240
     */
241
    protected function _replaceUrl($css)
242
    {
243
        $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
244
        return preg_replace_callback($pattern, [__CLASS__, '_replaceUrlCallback'], $css);
245
    }
246
247
    /**
248
     * Replace url callback.
249
     *
250
     * @param array $match
251
     * @return string
252
     */
253
    protected function _replaceUrlCallback(array $match)
254
    {
255
        $assetPath = str_replace(Router::fullBaseUrl(), '', $match[2]);
256
        $assetPath = trim(FS::clean($assetPath, '/'), '/');
257
        $appDir    = trim(FS::clean(APP_ROOT, '/'), '/');
258
259
        list ($isPlugin, $assetPath) = $this->_getPlgAssetUrl($assetPath);
260
261
        if (!$isPlugin) {
262
            $assetPath = str_replace($appDir, '', $assetPath);
263
        }
264
265
        $assetPath = str_replace(Configure::read('App.webroot'), '', $assetPath);
266
        $assetPath = FS::clean($assetPath, '/');
267
268
        if ($isPlugin) {
269
            return $this->_normalizePlgAssetUrl($assetPath);
270
        }
271
272
        return $this->_getAssetUrl($assetPath);
273
    }
274
275
    /**
276
     * Set less process force.
277
     *
278
     * @param bool $force
279
     * @return void
280
     */
281
    protected function _setForce($force = false)
282
    {
283
        if ($force) {
284
            $this->setConfig('force', true);
285
        }
286
    }
287
288
    /**
289
     * Write content in to the file.
290
     *
291
     * @param string $path
292
     * @param string $content
293
     * @return void
294
     */
295
    protected function _write($path, $content)
296
    {
297
        $File = new File($path);
298
        $File->write($content);
299
        $File->exists();
300
    }
301
}
302