Passed
Push — master ( be88e3...87c6b6 )
by Fran
04:17
created

AssetsParser   F

Complexity

Total Complexity 99

Size/Duplication

Total Lines 472
Duplicated Lines 0 %

Test Coverage

Coverage 66.12%

Importance

Changes 0
Metric Value
eloc 234
dl 0
loc 472
ccs 160
cts 242
cp 0.6612
rs 2
c 0
b 0
f 0
wmc 99

19 Methods

Rating   Name   Duplication   Size   Complexity  
A loopCssLines() 0 14 5
B compileCss() 0 26 6
A printJs() 0 9 5
A setHash() 0 5 2
A calculateResourcePathname() 0 13 3
B extractCssResources() 0 18 7
A storeContents() 0 5 3
A putDebugJs() 0 10 3
A extractCssLineResource() 0 16 5
B addFile() 0 13 7
A extractSourceFilename() 0 13 3
C calculateAssetPath() 0 60 16
B compileJs() 0 32 9
A printHtml() 0 10 3
A compile() 0 15 3
A printCss() 0 9 5
A findDomainPath() 0 15 5
A __construct() 0 7 1
B processCssLine() 0 22 8

How to fix   Complexity   

Complex Class

Complex classes like AssetsParser 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.

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 AssetsParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PSFS\base\extension;
4
5
use MatthiasMullie\Minify\CSS;
6
use MatthiasMullie\Minify\JS;
7
use PSFS\base\config\Config;
8
use PSFS\base\exception\ConfigException;
9
use PSFS\base\Logger;
10
use PSFS\base\Request;
11
use PSFS\base\Template;
12
use PSFS\base\types\helpers\GeneratorHelper;
13
14
/**
15
 * Class AssetsParser
16
 * @package PSFS\base\extension
17
 */
18
class AssetsParser
19
{
20
21
    protected $files = [];
22
    protected $hash = [];
23
    protected $compiled_files;
24
    protected $type;
25
    protected $path;
26
    protected $domains = [];
27
    private $debug = false;
28
    private $cdnPath = null;
29
30
    /**
31
     * Constructor por defecto
32
     *
33
     * @param string $type
34
     */
35 1
    public function __construct($type = 'js')
36
    {
37 1
        $this->type = $type;
38 1
        $this->path = WEB_DIR . DIRECTORY_SEPARATOR;
39 1
        $this->domains = Template::getDomains(true);
40 1
        $this->debug = Config::getParam('debug');
41 1
        $this->cdnPath = Config::getParam('resources.cdn.url', Request::getInstance()->getRootUrl());
42 1
    }
43
44
    /**
45
     * Método que calcula el path completo a copiar un recurso
46
     * @param string $filename_path
47
     * @param string[] $source
48
     * @return string
49
     */
50
    protected static function calculateResourcePathname($filename_path, $source)
51
    {
52
        $source_file = preg_replace("/'/", "", $source[1]);
53
        if (preg_match('/\#/', $source_file)) {
54
            $source_file = explode("#", $source_file);
55
            $source_file = $source_file[0];
56
        }
57
        if (preg_match('/\?/', $source_file)) {
58
            $source_file = explode("?", $source_file);
59
            $source_file = $source_file[0];
60
        }
61
        $orig = realpath(dirname($filename_path) . DIRECTORY_SEPARATOR . $source_file);
62
        return $orig;
63
    }
64
65
    /**
66
     * Método que añade un nuevo fichero al proceso de generación de los assets
67
     * @param $filename
68
     * @return AssetsParser
69
     * @internal param string $type
70
     *
71
     */
72 1
    public function addFile($filename)
73
    {
74 1
        if (file_exists($this->path . $filename) && preg_match('/\.' . $this->type . '$/i', $filename)) {
75
            $this->files[] = $filename;
76 1
        } elseif (!empty($this->domains)) {
77 1
            foreach ($this->domains as $domain => $paths) {
78 1
                $domain_filename = str_replace($domain, $paths["public"], $filename);
79 1
                if (file_exists($domain_filename) && preg_match('/\.' . $this->type . '$/i', $domain_filename)) {
80 1
                    $this->files[] = $domain_filename;
81
                }
82
            }
83
        }
84 1
        return $this;
85
    }
86
87
    /**
88
     * Método que establece el hash con el que compilar los assets
89
     * @param string $hash
90
     *
91
     * @return AssetsParser
92
     */
93 1
    public function setHash($hash)
94
    {
95 1
        $cache = Config::getParam('cache.var', '');
96 1
        $this->hash = $hash . (strlen($cache) ? '.' : '') . $cache;
97 1
        return $this;
98
    }
99
100
    /**
101
     * Método que procesa los ficheros solicitados en función del modo de ejecución
102
     * @return AssetsParser
103
     * @internal param string $type
104
     * @throws ConfigException
105
     */
106 1
    public function compile()
107
    {
108
        //Unificamos ficheros para que no se retarde mucho el proceso
109 1
        $this->files = array_unique($this->files);
110 1
        switch ($this->type) {
111
            default:
112 1
            case "js":
113 1
                $this->compileJs();
114 1
                break;
115 1
            case "css":
116 1
                $this->compileCss();
117 1
                break;
118
        }
119
120 1
        return $this;
121
    }
122
123
    /**
124
     * Método que compila los ficheros css y los procesa en función del modo de ejecución
125
     * @return AssetsParser
126
     * @throws ConfigException
127
     */
128 1
    protected function compileCss()
129
    {
130 1
        $base = $this->path . "css" . DIRECTORY_SEPARATOR;
131 1
        if ($this->debug || !file_exists($base . $this->hash . ".css")) {
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

131
        if ($this->debug || !file_exists($base . /** @scrutinizer ignore-type */ $this->hash . ".css")) {
Loading history...
132 1
            $data = '';
133 1
            if (0 < count($this->files)) {
134 1
                $minifier = new CSS();
0 ignored issues
show
Unused Code introduced by
The assignment to $minifier is dead and can be removed.
Loading history...
135 1
                foreach ($this->files as $file) {
136 1
                    $data = $this->processCssLine($file, $base, $data);
137
                }
138
            }
139 1
            if($this->debug) {
140 1
                $this->storeContents($base . $this->hash . ".css", $data);
141
            } else {
142
                $minifier = new CSS();
143
                $minifier->add($data);
144
                ini_set('max_execution_time', -1);
145
                ini_set('memory_limit', -1);
146
                GeneratorHelper::createDir($base);
147
                $minifier->minify($base . $this->hash . ".css");
148
                unset($cssMinifier);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cssMinifier seems to be never defined.
Loading history...
149
                ini_restore('memory_limit');
150
                ini_restore('max_execution_time');
151
            }
152
        }
153 1
        return $this;
154
    }
155
156
    /**
157
     * Método que compila los ficheros javascript en función del modo de ejecución
158
     * @return $this
159
     * @throws ConfigException
160
     */
161 1
    protected function compileJs()
162
    {
163 1
        $base = $this->path . "js" . DIRECTORY_SEPARATOR;
164 1
        if ($this->debug || !file_exists($base . $this->hash . ".js")) {
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
        if ($this->debug || !file_exists($base . /** @scrutinizer ignore-type */ $this->hash . ".js")) {
Loading history...
165 1
            $data = '';
166 1
            if (0 < count($this->files)) {
167 1
                $minifier = new JS();
168 1
                foreach ($this->files as $file) {
169 1
                    $path_parts = explode("/", $file);
170 1
                    if (file_exists($file)) {
171 1
                        if ($this->debug) {
172 1
                            $data = $this->putDebugJs($path_parts, $base, $file);
173
                        } elseif (!file_exists($base . $this->hash . ".js")) {
174 1
                            $minifier->add($file);
175
                            //$data = $this->putProductionJs($base, $file, $data);
176
                        }
177
                    }
178
                }
179 1
                if($this->debug) {
180 1
                    $this->storeContents($base . $this->hash . ".js", $data);
181
                } else {
182
                    ini_set('max_execution_time', -1);
183
                    ini_set('memory_limit', -1);
184
                    GeneratorHelper::createDir($base);
185
                    $minifier->minify($base . $this->hash . ".js");
186
                    ini_restore('memory_limit');
187
                    ini_restore('max_execution_time');
188
                }
189 1
                unset($minifier);
190
            }
191
        }
192 1
        return $this;
193
    }
194
195
    /**
196
     * Método para guardar cualquier contenido y controlar que existe el directorio y se guarda correctamente
197
     * @param string $path
198
     * @param string $content
199
     * @throws ConfigException
200
     */
201 1
    private function storeContents($path, $content = "")
202
    {
203 1
        GeneratorHelper::createDir(dirname($path));
204 1
        if ("" !== $content && false === file_put_contents($path, $content)) {
205
            throw new ConfigException(_('No se tienen permisos para escribir en ' . $path));
206
        }
207 1
    }
208
209
    /**
210
     * Método que imprime el resultado de la generación de los assets
211
     */
212 1
    public function printHtml()
213
    {
214 1
        switch ($this->type) {
215
            default:
216 1
            case "js":
217 1
                $this->printJs();
218 1
                break;
219 1
            case "css":
220 1
                $this->printCss();
221 1
                break;
222
        }
223 1
    }
224
225
    /**
226
     * Método que devuelve el html con la ruta compilada del recurso javascript
227
     */
228 1
    protected function printJs()
229
    {
230 1
        if ($this->debug && 0 < count($this->compiled_files)) {
231 1
            foreach ($this->compiled_files as $file) {
232 1
                echo "\t\t<script type='text/javascript' src='{$file}'></script>\n";
233
            }
234
        } else {
235
            $basePath = $this->cdnPath ?: '';
236
            echo "\t\t<script type='text/javascript' src='" . $basePath . "/js/" . $this->hash . ".js'></script>\n";
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

236
            echo "\t\t<script type='text/javascript' src='" . $basePath . "/js/" . /** @scrutinizer ignore-type */ $this->hash . ".js'></script>\n";
Loading history...
237
        }
238 1
    }
239
240
    /**
241
     * Método que devuelve el html con la ruta compilada del recurso css
242
     */
243 1
    protected function printCss()
244
    {
245 1
        if ($this->debug && 0 < count($this->compiled_files)) {
246 1
            foreach ($this->compiled_files as $file) {
247 1
                echo "\t\t<link href='{$file}' rel='stylesheet' media='screen, print'>";
248
            }
249
        } else {
250
            $basePath = $this->cdnPath ?: '';
251
            echo "\t\t<link href='" . $basePath . "/css/" . $this->hash . ".css' rel='stylesheet'>";
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

251
            echo "\t\t<link href='" . $basePath . "/css/" . /** @scrutinizer ignore-type */ $this->hash . ".css' rel='stylesheet'>";
Loading history...
252
        }
253 1
    }
254
255
    /**
256
     * @param string $source
257
     * @param string $file
258
     */
259 1
    protected function extractCssResources($source, $file)
260
    {
261 1
        $source_file = $this->extractSourceFilename($source);
262 1
        $orig = realpath(dirname($file) . DIRECTORY_SEPARATOR . $source_file);
263 1
        $orig_part = preg_split('/(\/|\\\)public(\/|\\\)/i', $orig);
264
        try {
265 1
            if (count($source) > 1 && array_key_exists(1, $orig_part)) {
0 ignored issues
show
Bug introduced by
It seems like $orig_part can also be of type false; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
            if (count($source) > 1 && array_key_exists(1, /** @scrutinizer ignore-type */ $orig_part)) {
Loading history...
Bug introduced by
$source of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
            if (count(/** @scrutinizer ignore-type */ $source) > 1 && array_key_exists(1, $orig_part)) {
Loading history...
266 1
                $dest = $this->path . $orig_part[1];
267 1
                GeneratorHelper::createDir(dirname($dest));
268 1
                if (!file_exists($dest) || filemtime($orig) > filemtime($dest)) {
269 1
                    if (@copy($orig, $dest) === FALSE) {
270
                        throw new \RuntimeException('Can\' copy ' . $dest . '');
271
                    }
272 1
                    Logger::log("$orig copiado a $dest", LOG_INFO);
273
                }
274
            }
275
        } catch (\Exception $e) {
276
            Logger::log($e->getMessage(), LOG_ERR);
277
        }
278 1
    }
279
280
    /**
281
     * Método que procesa cada línea de la hoja de estilos para copiar los recursos asociados
282
     * @param string $file
283
     * @param string $base
284
     * @param string $data
285
     * @return string
286
     * @throws ConfigException
287
     */
288 1
    protected function processCssLine($file, $base, $data)
289
    {
290 1
        if (file_exists($file)) {
291 1
            $path_parts = explode("/", $file);
292 1
            $file_path = $this->hash . "_" . $path_parts[count($path_parts) - 1];
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

292
            $file_path = /** @scrutinizer ignore-type */ $this->hash . "_" . $path_parts[count($path_parts) - 1];
Loading history...
293 1
            if (!file_exists($base . $file_path) || filemtime($base . $file_path) < filemtime($file) || $this->debug) {
294
                //Si tenemos modificaciones tenemos que compilar de nuevo todos los ficheros modificados
295 1
                if (file_exists($base . $this->hash . ".css") && @unlink($base . $this->hash . ".css") === false) {
296
                    throw new ConfigException("Can't unlink file " . $base . $this->hash . ".css");
297
                }
298 1
                $this->loopCssLines($file);
299
            }
300 1
            if ($this->debug) {
301 1
                $data = file_get_contents($file);
302 1
                $this->storeContents($base . $file_path, $data);
303
            } else {
304
                $data .= file_get_contents($file);
305
            }
306 1
            $this->compiled_files[] = "/css/" . $file_path;
307
        }
308
309 1
        return $data;
310
    }
311
312
    /**
313
     * @param $path_parts
314
     * @param string $base
315
     * @param $file
316
     * @return string
317
     * @throws ConfigException
318
     */
319 1
    protected function putDebugJs($path_parts, $base, $file)
320
    {
321 1
        $file_path = $this->hash . "_" . $path_parts[count($path_parts) - 1];
0 ignored issues
show
Bug introduced by
Are you sure $this->hash of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

321
        $file_path = /** @scrutinizer ignore-type */ $this->hash . "_" . $path_parts[count($path_parts) - 1];
Loading history...
322 1
        $this->compiled_files[] = "/js/" . $file_path;
323 1
        $data = "";
324 1
        if (!file_exists($base . $file_path) || filemtime($base . $file_path) < filemtime($file)) {
325 1
            $data = file_get_contents($file);
326 1
            $this->storeContents($base . $file_path, $data);
327
        }
328 1
        return $data;
329
    }
330
331
    /**
332
     * Servicio que busca el path para un dominio dado
333
     * @param $string
334
     * @param string $file_path
335
     *
336
     * @return string
337
     */
338 1
    public static function findDomainPath($string, $file_path)
339
    {
340 1
        $domains = Template::getDomains(TRUE);
341 1
        $filename_path = null;
342 1
        if (!file_exists($file_path) && 0 < count($domains)) {
343 1
            foreach ($domains as $domain => $paths) {
344 1
                $domain_filename = str_replace($domain, $paths["public"], $string);
345 1
                if (file_exists($domain_filename)) {
346 1
                    $filename_path = $domain_filename;
347 1
                    continue;
348
                }
349
            }
350
        }
351
352 1
        return $filename_path;
353
    }
354
355
    /**
356
     * Método que calcula el path de un recurso web
357
     * @param string $string
358
     * @param string $name
359
     * @param boolean $return
360
     * @param string $filename_path
361
     *
362
     * @return string[]
363
     */
364 1
    public static function calculateAssetPath($string, $name, $return, $filename_path)
365
    {
366 1
        $ppath = explode("/", $string);
367 1
        $original_filename = $ppath[count($ppath) - 1];
368 1
        $base = WEB_DIR . DIRECTORY_SEPARATOR;
369 1
        $file = "";
370 1
        $html_base = "";
371 1
        $debug = Config::getInstance()->getDebugMode();
372 1
        $cache = Config::getInstance()->get('cache.var');
373 1
        $cache = $cache ? '.' . $cache : '';
374 1
        $finfo = finfo_open(FILEINFO_MIME_TYPE); // devuelve el tipo mime de su extensión
375 1
        $mime = finfo_file($finfo, $filename_path);
376 1
        finfo_close($finfo);
377 1
        if (preg_match('/\.css$/i', $string)) {
378
            $file = "/" . substr(md5($string), 0, 8) . "$cache.css";
379
            $html_base = "css";
380
            if ($debug) {
381
                $file = str_replace(".css", "_" . $original_filename, $file);
382
            }
383 1
        } elseif (preg_match('/\.js$/i', $string)) {
384 1
            $file = "/" . substr(md5($string), 0, 8) . "$cache.js";
385 1
            $html_base = "js";
386 1
            if ($debug) {
387 1
                $file = str_replace(".js", "_" . $original_filename, $file);
388
            }
389
        } elseif (preg_match("/image/i", $mime)) {
390
            $ext = explode(".", $string);
391
            $file = "/" . substr(md5($string), 0, 8) . "." . $ext[count($ext) - 1];
392
            $html_base = "img";
393
            if ($debug) {
394
                $file = str_replace("." . $ext[count($ext) - 1], "_" . $original_filename, $file);
395
            }
396
        } elseif (preg_match("/(doc|pdf)/i", $mime)) {
397
            $ext = explode(".", $string);
398
            $file = "/" . substr(md5($string), 0, 8) . "." . $ext[count($ext) - 1];
399
            $html_base = "docs";
400
            if ($debug) {
401
                $file = str_replace("." . $ext[count($ext) - 1], "_" . $original_filename, $file);
402
            }
403
        } elseif (preg_match("/(video|audio|ogg)/i", $mime)) {
404
            $ext = explode(".", $string);
405
            $file = "/" . substr(md5($string), 0, 8) . "." . $ext[count($ext) - 1];
406
            $html_base = "media";
407
            if ($debug) {
408
                $file = str_replace("." . $ext[count($ext) - 1], "_" . $original_filename, $file);
409
            }
410
        } elseif (preg_match("/(text|html)/i", $mime)) {
411
            $ext = explode(".", $string);
412
            $file = "/" . substr(md5($string), 0, 8) . "." . $ext[count($ext) - 1];
413
            $html_base = "templates";
414
            if ($debug) {
415
                $file = str_replace("." . $ext[count($ext) - 1], "_" . $original_filename, $file);
416
            }
417
        } elseif (!$return && !is_null($name)) {
418
            $html_base = '';
419
            $file = $name;
420
        }
421 1
        $file_path = $html_base . $file;
422
423 1
        return array($base, $html_base, $file_path);
424
    }
425
426
    /**
427
     * Método que extrae el recurso css de una línea de estilos css
428
     * @param $handle
429
     * @param string $filename_path
430
     * @throws ConfigException
431
     */
432
    public static function extractCssLineResource($handle, $filename_path)
433
    {
434
        $line = fgets($handle);
435
        $urls = array();
436
        if (preg_match_all('#url\((.*?)\)#', $line, $urls, PREG_SET_ORDER)) {
437
            foreach ($urls as $source) {
438
                $orig = self::calculateResourcePathname($filename_path, $source);
439
                if(!empty($orig)) {
440
                    $orig_part = preg_split("/Public/i", $orig);
441
                    $dest = WEB_DIR . $orig_part[1];
442
                    GeneratorHelper::createDir(dirname($dest));
443
                    if (@copy($orig, $dest) === false) {
444
                        throw new ConfigException("Can't copy " . $orig . " to " . $dest);
445
                    }
446
                } else {
447
                    Logger::log($filename_path . ' has an empty origin with the url ' . $source, LOG_WARNING);
448
                }
449
            }
450
        }
451
    }
452
453
    /**
454
     * Método que extrae el nombre del fichero de un recurso
455
     * @param string $source
456
     * @return string
457
     */
458 1
    protected function extractSourceFilename($source)
459
    {
460 1
        $source_file = preg_replace("/'/", "", $source[1]);
461 1
        if (preg_match('/\#/', $source_file)) {
462 1
            $source_file = explode("#", $source_file);
463 1
            $source_file = $source_file[0];
464
        }
465 1
        if (preg_match('/\?/', $source_file)) {
466 1
            $source_file = explode("?", $source_file);
467 1
            $source_file = $source_file[0];
468 1
            return $source_file;
469
        }
470 1
        return $source_file;
471
    }
472
473
    /**
474
     * @param string $file
475
     */
476 1
    protected function loopCssLines($file)
477
    {
478 1
        $handle = @fopen($file, 'r');
479 1
        if ($handle) {
0 ignored issues
show
introduced by
$handle is of type false|resource, thus it always evaluated to false.
Loading history...
480 1
            while (!feof($handle)) {
481 1
                $line = fgets($handle);
482 1
                $urls = array();
483 1
                if (preg_match_all('#url\((.*?)\)#', $line, $urls, PREG_SET_ORDER)) {
484 1
                    foreach ($urls as $source) {
485 1
                        $this->extractCssResources($source, $file);
486
                    }
487
                }
488
            }
489 1
            fclose($handle);
490
        }
491 1
    }
492
}
493