Passed
Push — master ( 70f98a...9ce8e2 )
by Fran
05:26
created

AssetsParser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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
11
/**
12
 * Class AssetsParser
13
 * @package PSFS\base\extension
14
 */
15
class AssetsParser {
16
17
    protected $files = [];
18
    protected $hash = [];
19
    protected $compiled_files;
20
    protected $type;
21
    protected $path;
22
    protected $domains = [];
23
    private $debug = false;
24
25
    /**
26
     * Constructor por defecto
27
     *
28
     * @param string $type
29
     */
30
    public function __construct($type = 'js')
31
    {
32
        $this->type = $type;
33
        $this->path = WEB_DIR.DIRECTORY_SEPARATOR;
34
        $this->domains = Template::getDomains(true);
35
        $this->debug = Config::getInstance()->getDebugMode();
36
    }
37
38
    /**
39
     * Método que calcula el path completo a copiar un recurso
40
     * @param string $filename_path
41
     * @param string[] $source
42
     * @return string
43
     */
44
    protected static function calculateResourcePathname($filename_path, $source)
45
    {
46
        $source_file = preg_replace("/'/", "", $source[1]);
47 View Code Duplication
        if (preg_match('/\#/', $source_file)) {
48
            $source_file = explode("#", $source_file);
49
            $source_file = $source_file[0];
50
        }
51 View Code Duplication
        if (preg_match('/\?/', $source_file)) {
52
            $source_file = explode("?", $source_file);
53
            $source_file = $source_file[0];
54
        }
55
        $orig = realpath(dirname($filename_path).DIRECTORY_SEPARATOR.$source_file);
56
        return $orig;
57
    }
58
59
    /**
60
     * Método que añade un nuevo fichero al proceso de generación de los assets
61
     * @param $filename
62
     * @return AssetsParser
63
     * @internal param string $type
64
     *
65
     */
66
    public function addFile($filename)
67
    {
68
        if (file_exists($this->path.$filename) && preg_match('/\.'.$this->type.'$/i', $filename)) {
69
            $this->files[] = $filename;
70
        } elseif (!empty($this->domains)) {
71
            foreach ($this->domains as $domain => $paths) {
72
                $domain_filename = str_replace($domain, $paths["public"], $filename);
73
                if (file_exists($domain_filename) && preg_match('/\.'.$this->type.'$/i', $domain_filename)) {
74
                    $this->files[] = $domain_filename;
75
                }
76
            }
77
        }
78
        return $this;
79
    }
80
81
    /**
82
     * Método que establece el hash con el que compilar los assets
83
     * @param $hash
84
     *
85
     * @return AssetsParser
86
     */
87
    public function setHash($hash)
88
    {
89
        $this->hash = $hash;
90
        return $this;
91
    }
92
93
    /**
94
     * Método que procesa los ficheros solicitados en función del modo de ejecución
95
     * @return AssetsParser
96
     * @internal param string $type
97
     * @throws ConfigException
98
     */
99
    public function compile()
100
    {
101
        //Unificamos ficheros para que no se retarde mucho el proceso
102
        $this->files = array_unique($this->files);
103
        switch ($this->type) {
104
            default:
105
            case "js": $this->compileJs(); break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
106
            case "css": $this->compileCss(); break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
107
        }
108
109
        return $this;
110
    }
111
112
    /**
113
     * Método que compila los ficheros css y los procesa en función del modo de ejecución
114
     * @return AssetsParser
115
     * @throws ConfigException
116
     */
117
    protected function compileCss()
118
    {
119
        $base = $this->path."css".DIRECTORY_SEPARATOR;
120
        if ($this->debug || !file_exists($base.$this->hash.".css")) {
121
            $data = '';
122
            if (0 < count($this->files)) {
123
                foreach ($this->files as $file) {
124
                    $data = $this->processCssLine($file, $base, $data);
125
                }
126
            }
127
            $this->storeContents($base.$this->hash.".css", \CssMin::minify($data));
128
            unset($cssMinifier);
129
        }
130
        return $this;
131
    }
132
133
    /**
134
     * Método que compila los ficheros javascript en función del modo de ejecución
135
     * @return $this
136
     * @throws ConfigException
137
     */
138
    protected function compileJs() {
139
        $base = $this->path."js".DIRECTORY_SEPARATOR;
140
        if ($this->debug || !file_exists($base.$this->hash.".js")) {
141
            $data = '';
142
            if (0 < count($this->files)) {
143
                foreach ($this->files as $file) {
144
                    $path_parts = explode("/", $file);
145
                    if (file_exists($file)) {
146
                        if ($this->debug) {
147
                            $data = $this->putDebugJs($path_parts, $base, $file);
148
                        } elseif (!file_exists($base.$this->hash.".js")) {
149
                            $data = $this->putProductionJs($base, $file, $data);
150
                        }
151
                    }
152
                }
153
            }
154
            try {
155
                $minifiedJs = Minifier::minify($data);
156
            } catch(\Exception $e) {
157
                Logger::log($e->getMessage(), LOG_ERR);
158
                $minifiedJs = false;
159
            }
160
            $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...
161
        }
162
        return $this;
163
    }
164
165
    /**
166
     * Método para guardar cualquier contenido y controlar que existe el directorio y se guarda correctamente
167
     * @param string $path
168
     * @param string $content
169
     * @throws ConfigException
170
     */
171
    private function storeContents($path, $content = "") {
172
        Config::createDir(dirname($path));
173
        if ("" !== $content && false === file_put_contents($path, $content)) {
174
            throw new ConfigException(_('No se tienen permisos para escribir en '.$path));
175
        }
176
    }
177
178
    /**
179
     * Método que imprime el resultado de la generación de los assets
180
     */
181
    public function printHtml()
182
    {
183
        switch ($this->type) {
184
            default:
185
            case "js": $this->printJs(); break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
186
            case "css": $this->printCss(); break;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
187
        }
188
    }
189
190
    /**
191
     * Método que devuelve el html con la ruta compilada del recurso javascript
192
     */
193 View Code Duplication
    protected function printJs()
194
    {
195
        if ($this->debug && 0 < count($this->compiled_files)) {
196
            foreach ($this->compiled_files as $file) {
197
                echo "\t\t<script type='text/javascript' src='{$file}'></script>\n";
198
            }
199
        } else {
200
            echo "\t\t<script type='text/javascript' src='/js/".$this->hash.".js'></script>\n";
201
        }
202
    }
203
204
    /**
205
     * Método que devuelve el html con la ruta compilada del recurso css
206
     */
207 View Code Duplication
    protected function printCss()
208
    {
209
        if ($this->debug && 0 < count($this->compiled_files)) {
210
            foreach ($this->compiled_files as $file) {
211
                echo "\t\t<link href='{$file}' rel='stylesheet' media='screen, print'>";
212
            }
213
        } else {
214
            echo "\t\t<link href='/css/".$this->hash.".css' rel='stylesheet' media='screen, print'>";
215
        }
216
    }
217
218
    /**
219
     * @param string $source
220
     * @param string $file
221
     */
222
    protected function extractCssResources($source, $file)
223
    {
224
        $source_file = $this->extractSourceFilename($source);
225
        $orig = realpath(dirname($file).DIRECTORY_SEPARATOR.$source_file);
226
        $orig_part = preg_split('/(\/|\\\)public(\/|\\\)/i', $orig);
227
        try {
228
            if (count($source) > 1 && array_key_exists(1, $orig_part)) {
229
                $dest = $this->path.$orig_part[1];
230
                Config::createDir(dirname($dest));
231
                if (!file_exists($dest) || filemtime($orig) > filemtime($dest)) {
232
                    if (@copy($orig, $dest) === FALSE) {
233
                        throw new \RuntimeException('Can\' copy '.$dest.'');
234
                    }
235
                    Logger::log("$orig copiado a $dest", LOG_INFO);
236
                }
237
            }
238
        } catch (\Exception $e) {
239
            Logger::log($e->getMessage(), LOG_ERR);
240
        }
241
    }
242
243
    /**
244
     * Método que procesa cada línea de la hoja de estilos para copiar los recursos asociados
245
     * @param string $file
246
     * @param string $base
247
     * @param string $data
248
     * @return string
249
     * @throws ConfigException
250
     */
251
    protected function processCssLine($file, $base, $data)
252
    {
253
        if (file_exists($file)) {
254
            $path_parts = explode("/", $file);
255
            $file_path = $this->hash."_".$path_parts[count($path_parts) - 1];
256
            if (!file_exists($base.$file_path) || filemtime($base.$file_path) < filemtime($file) || $this->debug) {
257
                //Si tenemos modificaciones tenemos que compilar de nuevo todos los ficheros modificados
258
                if (file_exists($base.$this->hash.".css") && @unlink($base.$this->hash.".css") === false) {
259
                    throw new ConfigException("Can't unlink file ".$base.$this->hash.".css");
260
                }
261
                $this->loopCssLines($file);
262
            }
263
            if ($this->debug) {
264
                $data = file_get_contents($file);
265
                $this->storeContents($base.$file_path, $data);
266
            } else {
267
                $data .= file_get_contents($file);
268
            }
269
            $this->compiled_files[] = "/css/".$file_path;
270
        }
271
272
        return $data;
273
    }
274
275
    /**
276
     * @param $path_parts
277
     * @param string $base
278
     * @param $file
279
     * @return string
280
     * @throws ConfigException
281
     */
282
    protected function putDebugJs($path_parts, $base, $file) {
283
        $file_path = $this->hash."_".$path_parts[count($path_parts) - 1];
284
        $this->compiled_files[] = "/js/".$file_path;
285
        $data = "";
286
        if (!file_exists($base.$file_path) || filemtime($base.$file_path) < filemtime($file)) {
287
            $data = file_get_contents($file);
288
            $this->storeContents($base.$file_path, $data);
289
        }
290
        return $data;
291
    }
292
293
    /**
294
     * @param string $base
295
     * @param $file
296
     * @param string $data
297
     *
298
     * @return string
299
     * @throws ConfigException
300
     */
301
    protected function putProductionJs($base, $file, $data) {
302
        $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...
303
        try {
304
            $data .= ";\n".$js;
305
        }catch (\Exception $e) {
306
            throw new ConfigException($e->getMessage());
307
        }
308
        return $data;
309
    }
310
311
    /**
312
     * Servicio que busca el path para un dominio dado
313
     * @param $string
314
     * @param string $file_path
315
     *
316
     * @return string
317
     */
318
    public static function findDomainPath($string, $file_path)
319
    {
320
        $domains = Template::getDomains(TRUE);
321
        $filename_path = null;
322 View Code Duplication
        if (!file_exists($file_path) && 0 < count($domains)) {
323
            foreach ($domains as $domain => $paths) {
324
                $domain_filename = str_replace($domain, $paths["public"], $string);
325
                if (file_exists($domain_filename)) {
326
                    $filename_path = $domain_filename;
327
                    continue;
328
                }
329
            }
330
        }
331
332
        return $filename_path;
333
    }
334
335
    /**
336
     * Método que calcula el path de un recurso web
337
     * @param string $string
338
     * @param string $name
339
     * @param boolean $return
340
     * @param string $filename_path
341
     *
342
     * @return string[]
343
     */
344
    public static function calculateAssetPath($string, $name, $return, $filename_path)
345
    {
346
        $ppath = explode("/", $string);
347
        $original_filename = $ppath[count($ppath) - 1];
348
        $base = WEB_DIR.DIRECTORY_SEPARATOR;
349
        $file = "";
350
        $html_base = "";
351
        $debug = Config::getInstance()->getDebugMode();
352
        if (preg_match('/\.css$/i', $string)) {
353
            $file = "/".substr(md5($string), 0, 8).".css";
354
            $html_base = "css";
355
            if ($debug) {
356
                $file = str_replace(".css", "_".$original_filename, $file);
357
            }
358
        } elseif (preg_match('/\.js$/i', $string)) {
359
            $file = "/".substr(md5($string), 0, 8).".js";
360
            $html_base = "js";
361
            if ($debug) {
362
                $file = str_replace(".js", "_".$original_filename, $file);
363
            }
364 View Code Duplication
        } elseif (preg_match("/image/i", mime_content_type($filename_path))) {
365
            $ext = explode(".", $string);
366
            $file = "/".substr(md5($string), 0, 8).".".$ext[count($ext) - 1];
367
            $html_base = "img";
368
            if ($debug) {
369
                $file = str_replace(".".$ext[count($ext) - 1], "_".$original_filename, $file);
370
            }
371
        } elseif (preg_match("/(doc|pdf)/i", mime_content_type($filename_path))) {
372
            $ext = explode(".", $string);
373
            $file = "/".substr(md5($string), 0, 8).".".$ext[count($ext) - 1];
374
            $html_base = "docs";
375
            if ($debug) {
376
                $file = str_replace(".".$ext[count($ext) - 1], "_".$original_filename, $file);
377
            }
378 View Code Duplication
        } elseif (preg_match("/(video|audio|ogg)/i", mime_content_type($filename_path))) {
379
            $ext = explode(".", $string);
380
            $file = "/".substr(md5($string), 0, 8).".".$ext[count($ext) - 1];
381
            $html_base = "media";
382
            if ($debug) {
383
                $file = str_replace(".".$ext[count($ext) - 1], "_".$original_filename, $file);
384
            }
385
        } elseif (!$return && !is_null($name)) {
386
            $html_base = '';
387
            $file = $name;
388
        }
389
        $file_path = $html_base.$file;
390
391
        return array($base, $html_base, $file_path);
392
    }
393
394
    /**
395
     * Método que extrae el recurso css de una línea de estilos css
396
     * @param $handle
397
     * @param string $filename_path
398
     * @throws ConfigException
399
     */
400
    public static function extractCssLineResource($handle, $filename_path)
401
    {
402
        $line = fgets($handle);
403
        $urls = array();
404
        if (preg_match_all('#url\((.*?)\)#', $line, $urls, PREG_SET_ORDER)) {
405
            foreach ($urls as $source) {
406
                $orig = self::calculateResourcePathname($filename_path, $source);
407
                $orig_part = explode("Public", $orig);
408
                $dest = WEB_DIR.$orig_part[1];
409
                Config::createDir(dirname($dest));
410
                if (@copy($orig, $dest) === false) {
411
                    throw new ConfigException("Can't copy ".$orig." to ".$dest);
412
                }
413
            }
414
        }
415
    }
416
417
    /**
418
     * Método que extrae el nombre del fichero de un recurso
419
     * @param string $source
420
     * @return string
421
     */
422
    protected function extractSourceFilename($source)
423
    {
424
        $source_file = preg_replace("/'/", "", $source[1]);
425 View Code Duplication
        if (preg_match('/\#/', $source_file)) {
426
            $source_file = explode("#", $source_file);
427
            $source_file = $source_file[0];
428
        }
429 View Code Duplication
        if (preg_match('/\?/', $source_file)) {
430
            $source_file = explode("?", $source_file);
431
            $source_file = $source_file[0];
432
            return $source_file;
433
        }
434
        return $source_file;
435
    }
436
437
    /**
438
     * @param string $file
439
     */
440
    protected function loopCssLines($file)
441
    {
442
        $handle = @fopen($file, 'r');
443
        if ($handle) {
444
            while (!feof($handle)) {
445
                $line = fgets($handle);
446
                $urls = array();
447
                if (preg_match_all('#url\((.*?)\)#', $line, $urls, PREG_SET_ORDER)) {
448
                    foreach ($urls as $source) {
449
                        $this->extractCssResources($source, $file);
450
                    }
451
                }
452
            }
453
            fclose($handle);
454
        }
455
    }
456
}
457