Passed
Push — master ( fa3ee6...5bff81 )
by Fran
04:54
created

AssetsParser   D

Complexity

Total Complexity 98

Size/Duplication

Total Lines 468
Duplicated Lines 14.74 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 54.77%

Importance

Changes 0
Metric Value
dl 69
loc 468
ccs 149
cts 272
cp 0.5477
rs 4.8717
c 0
b 0
f 0
wmc 98
lcom 1
cbo 7

20 Methods

Rating   Name   Duplication   Size   Complexity  
A calculateResourcePathname() 8 14 3
A putDebugJs() 0 11 3
B findDomainPath() 9 16 5
A __construct() 0 7 1
B addFile() 0 14 7
A setHash() 0 6 2
A compile() 0 16 3
B compileCss() 0 15 5
D compileJs() 0 27 10
A storeContents() 7 7 3
A printHtml() 0 12 3
A printJs() 10 10 4
A printCss() 10 10 4
B extractCssResources() 0 20 7
C processCssLine() 0 23 8
A putProductionJs() 0 10 2
C calculateAssetPath() 16 61 16
A extractCssLineResource() 0 16 4
A extractSourceFilename() 9 14 3
B loopCssLines() 0 16 5

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 JShrink\Minifier;
6
use PSFS\base\config\Config;
7
use PSFS\base\exception\ConfigException;
8
use PSFS\base\Logger;
9
use PSFS\base\Template;
10
use PSFS\base\types\helpers\GeneratorHelper;
11
12
/**
13
 * Class AssetsParser
14
 * @package PSFS\base\extension
15
 */
16
class AssetsParser
17
{
18
19
    protected $files = [];
20
    protected $hash = [];
21
    protected $compiled_files;
22
    protected $type;
23
    protected $path;
24
    protected $domains = [];
25
    private $debug = false;
26
27
    /**
28
     * Constructor por defecto
29
     *
30
     * @param string $type
31
     */
32 1
    public function __construct($type = 'js')
33
    {
34 1
        $this->type = $type;
35 1
        $this->path = WEB_DIR . DIRECTORY_SEPARATOR;
36 1
        $this->domains = Template::getDomains(true);
37 1
        $this->debug = Config::getInstance()->getDebugMode();
38 1
    }
39
40
    /**
41
     * Método que calcula el path completo a copiar un recurso
42
     * @param string $filename_path
43
     * @param string[] $source
44
     * @return string
45
     */
46
    protected static function calculateResourcePathname($filename_path, $source)
47
    {
48
        $source_file = preg_replace("/'/", "", $source[1]);
49 View Code Duplication
        if (preg_match('/\#/', $source_file)) {
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...
50
            $source_file = explode("#", $source_file);
51
            $source_file = $source_file[0];
52
        }
53 View Code Duplication
        if (preg_match('/\?/', $source_file)) {
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...
54
            $source_file = explode("?", $source_file);
55
            $source_file = $source_file[0];
56
        }
57
        $orig = realpath(dirname($filename_path) . DIRECTORY_SEPARATOR . $source_file);
58
        return $orig;
59
    }
60
61
    /**
62
     * Método que añade un nuevo fichero al proceso de generación de los assets
63
     * @param $filename
64
     * @return AssetsParser
65
     * @internal param string $type
66
     *
67
     */
68 1
    public function addFile($filename)
69
    {
70 1
        if (file_exists($this->path . $filename) && preg_match('/\.' . $this->type . '$/i', $filename)) {
71
            $this->files[] = $filename;
72 1
        } elseif (!empty($this->domains)) {
73 1
            foreach ($this->domains as $domain => $paths) {
74 1
                $domain_filename = str_replace($domain, $paths["public"], $filename);
75 1
                if (file_exists($domain_filename) && preg_match('/\.' . $this->type . '$/i', $domain_filename)) {
76 1
                    $this->files[] = $domain_filename;
77 1
                }
78 1
            }
79 1
        }
80 1
        return $this;
81
    }
82
83
    /**
84
     * Método que establece el hash con el que compilar los assets
85
     * @param string $hash
86
     *
87
     * @return AssetsParser
88
     */
89 1
    public function setHash($hash)
90
    {
91 1
        $cache = Config::getParam('cache.var', '');
92 1
        $this->hash = $hash . (strlen($cache) ? '.' : '') . $cache;
0 ignored issues
show
Documentation Bug introduced by
It seems like $hash . (strlen($cache) ? '.' : '') . $cache of type string is incompatible with the declared type array of property $hash.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
93 1
        return $this;
94
    }
95
96
    /**
97
     * Método que procesa los ficheros solicitados en función del modo de ejecución
98
     * @return AssetsParser
99
     * @internal param string $type
100
     * @throws ConfigException
101
     */
102 1
    public function compile()
103
    {
104
        //Unificamos ficheros para que no se retarde mucho el proceso
105 1
        $this->files = array_unique($this->files);
106 1
        switch ($this->type) {
107 1
            default:
108 1
            case "js":
109 1
                $this->compileJs();
110 1
                break;
111 1
            case "css":
112 1
                $this->compileCss();
113 1
                break;
114 1
        }
115
116 1
        return $this;
117
    }
118
119
    /**
120
     * Método que compila los ficheros css y los procesa en función del modo de ejecución
121
     * @return AssetsParser
122
     * @throws ConfigException
123
     */
124 1
    protected function compileCss()
125
    {
126 1
        $base = $this->path . "css" . DIRECTORY_SEPARATOR;
127 1
        if ($this->debug || !file_exists($base . $this->hash . ".css")) {
128 1
            $data = '';
129 1
            if (0 < count($this->files)) {
130 1
                foreach ($this->files as $file) {
131 1
                    $data = $this->processCssLine($file, $base, $data);
132 1
                }
133 1
            }
134 1
            $this->storeContents($base . $this->hash . ".css", \CssMin::minify($data));
135 1
            unset($cssMinifier);
136 1
        }
137 1
        return $this;
138
    }
139
140
    /**
141
     * Método que compila los ficheros javascript en función del modo de ejecución
142
     * @return $this
143
     * @throws ConfigException
144
     */
145 1
    protected function compileJs()
146
    {
147 1
        $base = $this->path . "js" . DIRECTORY_SEPARATOR;
148 1
        if ($this->debug || !file_exists($base . $this->hash . ".js")) {
149 1
            $data = '';
150 1
            if (0 < count($this->files)) {
151 1
                foreach ($this->files as $file) {
152 1
                    $path_parts = explode("/", $file);
153 1
                    if (file_exists($file)) {
154 1
                        if ($this->debug) {
155
                            $data = $this->putDebugJs($path_parts, $base, $file);
156 1
                        } elseif (!file_exists($base . $this->hash . ".js")) {
157 1
                            $data = $this->putProductionJs($base, $file, $data);
158 1
                        }
159 1
                    }
160 1
                }
161 1
            }
162
            try {
163 1
                $minifiedJs = Minifier::minify($data);
164 1
            } catch (\Exception $e) {
165 1
                Logger::log($e->getMessage(), LOG_ERR);
166 1
                $minifiedJs = false;
167
            }
168 1
            $this->storeContents($base . $this->hash . ".js", (false !== $minifiedJs) ? $minifiedJs : $data);
0 ignored issues
show
Bug introduced by
It seems like false !== $minifiedJs ? $minifiedJs : $data can also be of type boolean; however, PSFS\base\extension\AssetsParser::storeContents() does only seem to accept string, 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...
169 1
        }
170 1
        return $this;
171
    }
172
173
    /**
174
     * Método para guardar cualquier contenido y controlar que existe el directorio y se guarda correctamente
175
     * @param string $path
176
     * @param string $content
177
     * @throws ConfigException
178
     */
179 1 View Code Duplication
    private function storeContents($path, $content = "")
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
180
    {
181 1
        GeneratorHelper::createDir(dirname($path));
182 1
        if ("" !== $content && false === file_put_contents($path, $content)) {
183
            throw new ConfigException(_('No se tienen permisos para escribir en ' . $path));
184
        }
185 1
    }
186
187
    /**
188
     * Método que imprime el resultado de la generación de los assets
189
     */
190 1
    public function printHtml()
191
    {
192 1
        switch ($this->type) {
193 1
            default:
194 1
            case "js":
195 1
                $this->printJs();
196 1
                break;
197 1
            case "css":
198 1
                $this->printCss();
199 1
                break;
200 1
        }
201 1
    }
202
203
    /**
204
     * Método que devuelve el html con la ruta compilada del recurso javascript
205
     */
206 1 View Code Duplication
    protected function printJs()
207
    {
208 1
        if ($this->debug && 0 < count($this->compiled_files)) {
209
            foreach ($this->compiled_files as $file) {
210
                echo "\t\t<script type='text/javascript' src='{$file}'></script>\n";
211
            }
212
        } else {
213 1
            echo "\t\t<script type='text/javascript' src='/js/" . $this->hash . ".js'></script>\n";
214
        }
215 1
    }
216
217
    /**
218
     * Método que devuelve el html con la ruta compilada del recurso css
219
     */
220 1 View Code Duplication
    protected function printCss()
221
    {
222 1
        if ($this->debug && 0 < count($this->compiled_files)) {
223
            foreach ($this->compiled_files as $file) {
224
                echo "\t\t<link href='{$file}' rel='stylesheet' media='screen, print'>";
225
            }
226
        } else {
227 1
            echo "\t\t<link href='/css/" . $this->hash . ".css' rel='stylesheet'>";
228
        }
229 1
    }
230
231
    /**
232
     * @param string $source
233
     * @param string $file
234
     */
235 1
    protected function extractCssResources($source, $file)
236
    {
237 1
        $source_file = $this->extractSourceFilename($source);
238 1
        $orig = realpath(dirname($file) . DIRECTORY_SEPARATOR . $source_file);
239 1
        $orig_part = preg_split('/(\/|\\\)public(\/|\\\)/i', $orig);
240
        try {
241 1
            if (count($source) > 1 && array_key_exists(1, $orig_part)) {
242 1
                $dest = $this->path . $orig_part[1];
243 1
                GeneratorHelper::createDir(dirname($dest));
244 1
                if (!file_exists($dest) || filemtime($orig) > filemtime($dest)) {
245 1
                    if (@copy($orig, $dest) === FALSE) {
246
                        throw new \RuntimeException('Can\' copy ' . $dest . '');
247
                    }
248 1
                    Logger::log("$orig copiado a $dest", LOG_INFO);
249 1
                }
250 1
            }
251 1
        } catch (\Exception $e) {
252
            Logger::log($e->getMessage(), LOG_ERR);
253
        }
254 1
    }
255
256
    /**
257
     * Método que procesa cada línea de la hoja de estilos para copiar los recursos asociados
258
     * @param string $file
259
     * @param string $base
260
     * @param string $data
261
     * @return string
262
     * @throws ConfigException
263
     */
264 1
    protected function processCssLine($file, $base, $data)
265
    {
266 1
        if (file_exists($file)) {
267 1
            $path_parts = explode("/", $file);
268 1
            $file_path = $this->hash . "_" . $path_parts[count($path_parts) - 1];
269 1
            if (!file_exists($base . $file_path) || filemtime($base . $file_path) < filemtime($file) || $this->debug) {
270
                //Si tenemos modificaciones tenemos que compilar de nuevo todos los ficheros modificados
271 1
                if (file_exists($base . $this->hash . ".css") && @unlink($base . $this->hash . ".css") === false) {
272
                    throw new ConfigException("Can't unlink file " . $base . $this->hash . ".css");
273
                }
274 1
                $this->loopCssLines($file);
275 1
            }
276 1
            if ($this->debug) {
277
                $data = file_get_contents($file);
278
                $this->storeContents($base . $file_path, $data);
279
            } else {
280 1
                $data .= file_get_contents($file);
281
            }
282 1
            $this->compiled_files[] = "/css/" . $file_path;
283 1
        }
284
285 1
        return $data;
286
    }
287
288
    /**
289
     * @param $path_parts
290
     * @param string $base
291
     * @param $file
292
     * @return string
293
     * @throws ConfigException
294
     */
295
    protected function putDebugJs($path_parts, $base, $file)
296
    {
297
        $file_path = $this->hash . "_" . $path_parts[count($path_parts) - 1];
298
        $this->compiled_files[] = "/js/" . $file_path;
299
        $data = "";
300
        if (!file_exists($base . $file_path) || filemtime($base . $file_path) < filemtime($file)) {
301
            $data = file_get_contents($file);
302
            $this->storeContents($base . $file_path, $data);
303
        }
304
        return $data;
305
    }
306
307
    /**
308
     * @param string $base
309
     * @param $file
310
     * @param string $data
311
     *
312
     * @return string
313
     * @throws ConfigException
314
     */
315 1
    protected function putProductionJs($base, $file, $data)
316
    {
317 1
        $js = file_get_contents($file);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $js. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

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