Assets::getUrlToAssets()   F
last analyzed

Complexity

Conditions 24
Paths 12024

Size

Total Lines 119
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 600

Importance

Changes 0
Metric Value
cc 24
eloc 84
nc 12024
nop 4
dl 0
loc 119
ccs 0
cts 77
cp 0
crap 600
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
*/
11
12
namespace Xoops\Core;
13
14
use Assetic\AssetManager;
15
use Assetic\FilterManager;
16
use Assetic\Filter;
17
use Assetic\Factory\AssetFactory;
18
use Assetic\Factory\Worker\CacheBustingWorker;
19
use Assetic\AssetWriter;
20
use Assetic\Asset\AssetCollection;
21
use Assetic\Asset\FileAsset;
22
use Assetic\Asset\GlobAsset;
23
use Xmf\Yaml;
24
25
/**
26
 * Provides a standardized asset strategy
27
 *
28
 * @category  Assets
29
 * @package   Assets
30
 * @author    Richard Griffith <[email protected]>
31
 * @copyright 2014-2017 XOOPS Project (http://xoops.org)
32
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
33
 * @version   Release: 1.0
34
 * @link      http://xoops.org
35
 * @since     2.6.0
36
 */
37
class Assets
38
{
39
    /** @var array  */
40
    protected $assetPrefs;
41
42
    /** @var boolean */
43
    private $debug = false;
44
45
    /** @var array of default filter strings - may be overridden by prefs */
46
    private $default_filters = array(
47
            'css' => 'cssimport,cssembed,?cssmin',
48
            'js'  => '?jsqueeze',
49
    );
50
51
    /** @var array of output locations in assets directory */
52
    private $default_output = array(
53
            'css' => 'css/*.css',
54
            'js'  => 'js/*.js',
55
    );
56
57
    /** @var array of asset reference definitions - may be overridden by prefs */
58
    private $default_asset_refs = array(
59
        array(
60
            'name' => 'jquery',
61
            'assets' => array('media/jquery/jquery.js'),
62
            'filters' => null,
63
        ),
64
        array(
65
            'name' => 'jqueryui',
66
            'assets' => array('media/jquery/ui/jquery-ui.js'),
67
            'filters' => null,
68
        ),
69
        array(
70
            'name' => 'jgrowl',
71
            'assets' => array('media/jquery/plugins/jquery.jgrowl.js'),
72
            'filters' => null,
73
        ),
74
        array(
75
            'name' => 'fontawesome',
76
            'assets' => array('media/font-awesome/css/font-awesome.min.css'),
77
            'filters' => null,
78
        ),
79
    );
80
81
    /**
82
     * @var array of file assets to copy to assets
83
     */
84
    private $default_file_assets = array(
85
        array(
86
            'type' => 'fonts',
87
            'path' => 'media/font-awesome/fonts',
88
            'pattern' => '*',
89
        ),
90
    );
91
92
93
    /** @var AssetManager */
94
    private $assetManager = null;
95
96
    /** @var string config file with assets prefs */
97
    private $assetsPrefsFilename = 'var/configs/system_assets_prefs.yml';
98
99
    /** @var string config cache key */
100
    private $assetsPrefsCacheKey = 'system/assets/prefs';
101
102
    /** @var string string to identify Assetic filters using instanceof */
103
    private $filterInterface = '\Assetic\Filter\FilterInterface';
104
105
    /**
106
     * __construct
107
     */
108 3
    public function __construct()
109
    {
110 3
        $this->assetManager = new AssetManager();
111 3
        if (isset($_REQUEST['ASSET_DEBUG'])) {
112
            $this->setDebug();
113
        }
114 3
        $this->assetPrefs = $this->readAssetsPrefs();
115
        // register any asset references
116 3
        foreach ($this->default_asset_refs as $ref) {
117 3
            $this->registerAssetReference($ref['name'], $ref['assets'], $ref['filters']);
118
        }
119 3
    }
120
121
    /**
122
     * readAssetsPrefs - read configured asset preferences
123
     *
124
     * @return array of assets preferences
125
     */
126 3
    protected function readAssetsPrefs()
127
    {
128 3
        $xoops = \Xoops::getInstance();
129
130
        try {
131 3
            $assetsPrefs = $xoops->cache()->read($this->assetsPrefsCacheKey);
132 3
            $file = $xoops->path($this->assetsPrefsFilename);
133 3
            $mtime = filemtime($file);
134 3
            if ($assetsPrefs===false || !isset($assetsPrefs['mtime']) || !$mtime
135 3
                || (isset($assetsPrefs['mtime']) && $assetsPrefs['mtime']<$mtime)) {
136 1
                if ($mtime) {
137
                    $assetsPrefs = Yaml::read($file);
138
                    if (!is_array($assetsPrefs)) {
139
                        $xoops->logger()->error("Invalid config in system_assets_prefs.yml");
140
                        $assetsPrefs = array();
141
                    } else {
142
                        $assetsPrefs['mtime']=$mtime;
143
                        $xoops->cache()->write($this->assetsPrefsCacheKey, $assetsPrefs);
144
                        $this->copyBaseFileAssets();
145
                    }
146
                } else {
147
                    // use defaults to create file
148
                    $assetsPrefs = array(
149 1
                        'default_filters' => $this->default_filters,
150 1
                        'default_asset_refs' => $this->default_asset_refs,
151 1
                        'default_file_assets' => $this->default_file_assets,
152 1
                        'mtime' => time(),
153
                    );
154 1
                    $this->saveAssetsPrefs($assetsPrefs);
155 1
                    $this->copyBaseFileAssets();
156
                }
157
            }
158 3
            if (!empty($assetsPrefs['default_filters']) && is_array($assetsPrefs['default_filters'])) {
159 3
                $this->default_filters = $assetsPrefs['default_filters'];
160
            }
161 3
            if (!empty($assetsPrefs['default_asset_refs']) && is_array($assetsPrefs['default_asset_refs'])) {
162 3
                $this->default_asset_refs = $assetsPrefs['default_asset_refs'];
163
            }
164 3
            if (!empty($assetsPrefs['default_file_assets']) && is_array($assetsPrefs['default_file_assets'])) {
165 3
                $this->default_file_assets = $assetsPrefs['default_file_assets'];
166
            }
167
        } catch (\Exception $e) {
168
            $xoops->events()->triggerEvent('core.exception', $e);
169
            $assetsPrefs = array();
170
        }
171 3
        return $assetsPrefs;
172
    }
173
174
    /**
175
     * saveAssetsPrefs - record array of assets preferences in config file, and
176
     * update cache
177
     *
178
     * @param array $assets_prefs array of asset preferences to save
179
     *
180
     * @return void
181
     */
182 1
    protected function saveAssetsPrefs($assets_prefs)
183
    {
184 1
        if (is_array($assets_prefs)) {
0 ignored issues
show
introduced by
The condition is_array($assets_prefs) is always true.
Loading history...
185 1
            $xoops = \Xoops::getInstance();
186
            try {
187 1
                Yaml::save($assets_prefs, $xoops->path($this->assetsPrefsFilename));
188 1
                $xoops->cache()->write($this->assetsPrefsCacheKey, $assets_prefs);
189
            } catch (\Exception $e) {
190
                $xoops->events()->triggerEvent('core.exception', $e);
191
            }
192
        }
193 1
    }
194
195
196
    /**
197
     * getUrlToAssets
198
     *
199
     * Create an asset file from a list of assets
200
     *
201
     * @param string       $type    type of asset, css or js
202
     * @param array        $assets  list of source files to process
203
     * @param string|array $filters either a comma separated list of known namsed filters
204
     *                              or an array of named filters and/or filter object
205
     * @param string       $target  target path, will default to assets directory
206
     *
207
     * @return string URL to asset file
208
     */
209
    public function getUrlToAssets($type, $assets, $filters = 'default', $target = null)
210
    {
211
        if (is_scalar($assets)) {
0 ignored issues
show
introduced by
The condition is_scalar($assets) is always false.
Loading history...
212
            $assets = array($assets); // just a single path name
213
        }
214
215
        if ($filters==='default') {
216
            if (isset($this->default_filters[$type])) {
217
                $filters = $this->default_filters[$type];
218
            } else {
219
                $filters = '';
220
            }
221
        }
222
223
        if (!is_array($filters)) {
224
            if (empty($filters)) {
225
                $filters = array();
226
            } else {
227
                $filters = explode(',', str_replace(' ', '', $filters));
228
            }
229
        }
230
231
        if (isset($this->default_output[$type])) {
232
            $output = $this->default_output[$type];
233
        } else {
234
            $output = '';
235
        }
236
237
        $xoops = \Xoops::getInstance();
238
239
        if (isset($target)) {
240
            $target_path = $target;
241
        } else {
242
            $target_path = $xoops->path('assets');
243
        }
244
245
        try {
246
            $am = $this->assetManager;
247
            $fm = new FilterManager();
248
249
            foreach ($filters as $filter) {
250
                if (is_object($filter) && $filter instanceof $this->filterInterface) {
251
                    $filterArray[] = $filter;
252
                } else {
253
                    switch (ltrim($filter, '?')) {
254
                        case 'cssembed':
255
                            $fm->set('cssembed', new Filter\PhpCssEmbedFilter());
256
                            break;
257
                        case 'cssmin':
258
                            $fm->set('cssmin', new Filter\CssMinFilter());
259
                            break;
260
                        case 'cssimport':
261
                            $fm->set('cssimport', new Filter\CssImportFilter());
262
                            break;
263
                        case 'cssrewrite':
264
                            $fm->set('cssrewrite', new Filter\CssRewriteFilter());
265
                            break;
266
                        case 'lessphp':
267
                            $fm->set('lessphp', new Filter\LessphpFilter());
268
                            break;
269
                        case 'scssphp':
270
                            $fm->set('scssphp', new Filter\ScssphpFilter());
271
                            break;
272
                        case 'jsmin':
273
                            $fm->set('jsmin', new Filter\JSMinFilter());
274
                            break;
275
                        case 'jsqueeze':
276
                            $fm->set('jsqueeze', new Filter\JSqueezeFilter());
277
                            break;
278
                        default:
279
                            throw new \Exception(sprintf('%s filter not implemented.', $filter));
280
                            break;
281
                    }
282
                }
283
            }
284
285
            // Factory setup
286
            $factory = new AssetFactory($target_path);
287
            $factory->setAssetManager($am);
288
            $factory->setFilterManager($fm);
289
            $factory->setDefaultOutput($output);
290
            $factory->setDebug($this->debug);
291
            $factory->addWorker(new CacheBustingWorker());
292
293
            // Prepare the assets writer
294
            $writer = new AssetWriter($target_path);
295
296
            // Translate asset paths, remove duplicates
297
            $translated_assets = array();
298
            foreach ($assets as $k => $v) {
299
                // translate path if not a reference or absolute path
300
                if (0 == preg_match("/^\\/|^\\\\|^[a-zA-Z]:|^@/", $v)) {
301
                    $v = $xoops->path($v);
302
                }
303
                if (!in_array($v, $translated_assets)) {
304
                    $translated_assets[] = $v;
305
                }
306
            }
307
308
            // Create the asset
309
            $asset = $factory->createAsset(
310
                $translated_assets,
311
                $filters
312
            );
313
            $asset_path = $asset->getTargetPath();
314
            if (!is_readable($target_path . $asset_path)) {
315
                $assetKey = 'Asset '.$asset_path;
316
                $xoops->events()->triggerEvent('debug.timer.start', $assetKey);
317
                $oldumask = umask(0002);
318
                $writer->writeAsset($asset);
319
                umask($oldumask);
320
                $xoops->events()->triggerEvent('debug.timer.stop', $assetKey);
321
            }
322
323
            return $xoops->url('assets/' . $asset_path);
324
325
        } catch (\Exception $e) {
326
            $xoops->events()->triggerEvent('core.exception', $e);
327
            return null;
328
        }
329
    }
330
331
332
    /**
333
     * setDebug enable debug mode, will skip filters prefixed with '?'
334
     *
335
     * @return true
336
     */
337 1
    public function setDebug()
338
    {
339 1
        $this->debug = true;
340 1
        return true;
341
    }
342
343
    /**
344
     * Add an asset reference to the asset manager
345
     *
346
     * @param string       $name    the name of the reference to be added
347
     * @param mixed        $assets  a string asset path, or an array of asset paths,
348
     *                              may include wildcard
349
     * @param string|array $filters either a comma separated list of known named filters
350
     *                              or an array of named filters and/or filter object
351
     *
352
     * @return boolean true if asset registers, false on error
353
     */
354 3
    public function registerAssetReference($name, $assets, $filters = null)
355
    {
356 3
        $xoops = \Xoops::getInstance();
357
358 3
        $assetArray = array();
359 3
        $filterArray = array();
360
361
        try {
362 3
            if (is_scalar($assets)) {
363 1
                $assets = array($assets);  // just a single path name
364
            }
365 3
            foreach ($assets as $a) {
366
                // translate path if not a reference or absolute path
367 3
                if ((substr_compare($a, '@', 0, 1) != 0)
368 3
                    && (substr_compare($a, '/', 0, 1) != 0)) {
369 3
                    $a = $xoops->path($a);
370
                }
371 3
                if (false===strpos($a, '*')) {
372 3
                    $assetArray[] = new FileAsset($a); // single file
373
                } else {
374
                    $assetArray[] = new GlobAsset($a);  // wild card match
375
                }
376
            }
377
378 3
            if (!is_array($filters)) {
379 3
                if (empty($filters)) {
380 3
                    $filters = array();
381
                } else {
382
                    $filters = explode(',', str_replace(' ', '', $filters));
383
                }
384
            }
385 3
            foreach ($filters as $filter) {
386
                if (is_object($filter) && $filter instanceof $this->filterInterface) {
387
                    $filterArray[] = $filter;
388
                } else {
389
                    switch (ltrim($filter, '?')) {
390
                        case 'cssembed':
391
                            $filterArray[] = new Filter\PhpCssEmbedFilter();
392
                            break;
393
                        case 'cssmin':
394
                            $filterArray[] = new Filter\CssMinFilter();
395
                            break;
396
                        case 'cssimport':
397
                            $filterArray[] = new Filter\CssImportFilter();
398
                            break;
399
                        case 'cssrewrite':
400
                            $filterArray[] = new Filter\CssRewriteFilter();
401
                            break;
402
                        case 'lessphp':
403
                            $filterArray[] = new Filter\LessphpFilter();
404
                            break;
405
                        case 'scssphp':
406
                            $filterArray[] = new Filter\ScssphpFilter();
407
                            break;
408
                        case 'jsmin':
409
                            $filterArray[] = new Filter\JSMinFilter();
410
                            break;
411
                        case 'jsqueeze':
412
                            $filterArray[] = new Filter\JSqueezeFilter();
413
                            break;
414
                        default:
415
                            throw new \Exception(sprintf('%s filter not implemented.', $filter));
416
                            break;
417
                    }
418
                }
419
            }
420
421 3
            $collection = new AssetCollection($assetArray, $filterArray);
422 3
            $this->assetManager->set($name, $collection);
423
424 3
            return true;
425
        } catch (\Exception $e) {
426
            $xoops->events()->triggerEvent('core.exception', $e);
427
            return false;
428
        }
429
    }
430
431 1
    public function copyBaseFileAssets()
432
    {
433 1
        foreach ($this->default_file_assets as $fileSpec) {
434 1
            $this->copyFileAssets($fileSpec['path'], trim($fileSpec['pattern']), $fileSpec['type']);
435
        }
436 1
    }
437
438
    /**
439
     * copyFileAssets - copy files to the appropriate asset directory.
440
     *
441
     * Copying is normally only needed for fonts or images when they are referenced by a
442
     * relative url in stylesheet, or are located outside of the web root.
443
     *
444
     * @param string $fromPath path to files to copy
445
     * @param string $pattern  glob pattern to match files to be copied
446
     * @param string $output   output type (css, fonts, images, js)
447
     *
448
     * @return mixed boolean false if target directory is not writable, otherwise
449
     *               integer count of files copied
450
     */
451 2
    public function copyFileAssets($fromPath, $pattern, $output)
452
    {
453 2
        $xoops = \Xoops::getInstance();
454
455 2
        $fromPath = $xoops->path($fromPath) . '/';
456 2
        $toPath = $xoops->path('assets') . '/' . $output . '/';
457 2
        $from = glob($fromPath . '/' . $pattern);
458
459 2
        if (!is_dir($toPath)) {
460 2
            $oldUmask = umask(0);
461 2
            mkdir($toPath, 0775, true);
462 2
            umask($oldUmask);
463
        }
464
465 2
        if (is_writable($toPath)) {
466 2
            $count = 0;
467 2
            $oldUmask = umask(0002);
468 2
            foreach ($from as $filepath) {
469 1
                $xoops->events()->triggerEvent('debug.timer.start', $filepath);
470 1
                $filename = basename($filepath);
471 1
                $status=copy($filepath, $toPath.$filename);
472 1
                if (false===$status) {
473
                    $xoops->logger()->warning('Failed to copy asset '.$filename);
474
                } else {
475
                    //$xoops->logger()->debug('Copied asset '.$filename);
476 1
                    ++$count;
477
                }
478 1
                $xoops->events()->triggerEvent('debug.timer.stop', $filepath);
479
            }
480 2
            umask($oldUmask);
481 2
            return $count;
482
        } else {
483
            $xoops->logger()->warning('Asset directory is not writable. ' . $output);
484
            return false;
485
        }
486
    }
487
}
488