Issues (493)

lib/SP/Html/Minify.php (1 issue)

Severity
1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2019, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Html;
26
27
use Klein\Klein;
28
use Psr\Container\ContainerExceptionInterface;
29
use Psr\Container\NotFoundExceptionInterface;
30
use SP\Http\Request;
31
32
defined('APP_ROOT') || die();
33
34
/**
35
 * Class Minify para la gestión de archivos JS y CSS
36
 *
37
 * @package SP
38
 */
39
final class Minify
40
{
41
    /**
42
     * Constantes para tipos de archivos
43
     */
44
    const FILETYPE_JS = 1;
45
    const FILETYPE_CSS = 2;
46
    const OFFSET = 3600 * 24 * 30;
47
    /**
48
     * @var Klein
49
     */
50
    protected $router;
51
52
    /**
53
     * Array con los archivos a procesar
54
     *
55
     * @var array
56
     */
57
    private $files = [];
58
    /**
59
     * Tipos de archivos a procesar
60
     *
61
     * @var int
62
     */
63
    private $type = 0;
64
    /**
65
     * Base relativa de búsqueda de los archivos
66
     *
67
     * @var string
68
     */
69
    private $base = '';
70
71
    /**
72
     * Minify constructor.
73
     *
74
     * @param Klein $router
75
     */
76
    public function __construct(Klein $router)
77
    {
78
        $this->router = $router;
79
    }
80
81
    /**
82
     * @param string $path
83
     * @param bool   $checkPath
84
     *
85
     * @return $this
86
     */
87
    public function setBase($path, $checkPath = false)
88
    {
89
        $this->base = $checkPath === true ? Request::getSecureAppPath($path) : $path;
90
91
        return $this;
92
    }
93
94
    /**
95
     * Devolver al navegador archivos CSS y JS comprimidos
96
     * Método que devuelve un recurso CSS o JS comprimido. Si coincide el ETAG se
97
     * devuelve el código HTTP/304
98
     *
99
     * @param bool $disableMinify Deshabilitar minimizar
100
     *
101
     * @throws ContainerExceptionInterface
102
     * @throws NotFoundExceptionInterface
103
     */
104
    public function getMinified($disableMinify = false)
105
    {
106
        if (count($this->files) === 0) {
107
            return;
108
        }
109
110
        $this->setHeaders();
111
112
        $data = '';
113
114
        foreach ($this->files as $file) {
115
            $filePath = $file['base'] . DIRECTORY_SEPARATOR . $file['name'];
116
117
            // Obtener el recurso desde una URL
118
            if ($file['type'] === 'url') {
119
                logger('URL:' . $file['name']);
120
121
//                    $data .= '/* URL: ' . $file['name'] . ' */' . PHP_EOL . Util::getDataFromUrl($file['name']);
122
            } else {
123
                if ($file['min'] === true && $disableMinify === false) {
124
                    $data .= '/* MINIFIED FILE: ' . $file['name'] . ' */' . PHP_EOL;
125
126
                    if ($this->type === self::FILETYPE_JS) {
127
                        $data .= $this->jsCompress(file_get_contents($filePath));
128
                    }
129
                } else {
130
                    $data .= PHP_EOL . '/* FILE: ' . $file['name'] . ' */' . PHP_EOL . file_get_contents($filePath);
131
                }
132
            }
133
        }
134
135
        $this->router->response()->body($data);
136
    }
137
138
    /**
139
     * Sets HTTP headers
140
     */
141
    protected function setHeaders()
142
    {
143
        $response = $this->router->response();
144
        $headers = $this->router->request()->headers();
145
146
        $etag = $this->getEtag();
147
148
        // Devolver código 304 si la versión es la misma y no se solicita refrescar
149
        if ($etag === $headers->get('If-None-Match')
150
            && !($headers->get('Cache-Control') === 'no-cache'
151
                || $headers->get('Cache-Control') === 'max-age=0'
152
                || $headers->get('Pragma') === 'no-cache')
153
        ) {
154
            $response->header($_SERVER['SERVER_PROTOCOL'], '304 Not Modified');
155
            $response->send();
156
            exit();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
157
        }
158
159
        $response->header('Etag', $etag);
160
        $response->header('Cache-Control', 'public, max-age={' . self::OFFSET . '}, must-revalidate');
161
        $response->header('Pragma', 'public; maxage={' . self::OFFSET . '}');
162
        $response->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + self::OFFSET));
163
164
        switch ($this->type) {
165
            case self::FILETYPE_JS;
166
                $response->header('Content-type', 'application/javascript; charset: UTF-8');
167
                break;
168
            case self::FILETYPE_CSS:
169
                $response->header('Content-type', 'text/css; charset: UTF-8');
170
                break;
171
        }
172
    }
173
174
    /**
175
     * Calcular el hash MD5 de varios archivos.
176
     *
177
     * @return string Con el hash
178
     */
179
    private function getEtag()
180
    {
181
        $md5Sum = '';
182
183
        foreach ($this->files as $file) {
184
            $md5Sum .= $file['md5'];
185
        }
186
187
        return md5($md5Sum);
188
    }
189
190
    /**
191
     * Comprimir código javascript.
192
     *
193
     * @param string $buffer código a comprimir
194
     *
195
     * @return string
196
     */
197
    private function jsCompress($buffer)
198
    {
199
        $regexReplace = [
200
            '#/\*[^*]*\*+([^/][^*]*\*+)*/#',
201
            '#^[\s\t]*//.*$#m',
202
            '#[\s\t]+$#m',
203
            '#^[\s\t]+#m',
204
            '#\s*//\s.*$#m'
205
        ];
206
207
        return str_replace(["\r\n", "\r", "\n", "\t"], '', preg_replace($regexReplace, '', $buffer));
208
    }
209
210
    /**
211
     * @param      $files
212
     * @param bool $minify
213
     *
214
     * @return Minify
215
     */
216
    public function addFilesFromString($files, $minify = true)
217
    {
218
        if (strrpos($files, ',')) {
219
            $files = explode(',', $files);
220
221
            foreach ($files as $filename) {
222
                $this->addFile($filename, $minify);
223
            }
224
        } else {
225
            $this->addFile($files, $minify);
226
        }
227
228
        return $this;
229
    }
230
231
    /**
232
     * Añadir un archivo
233
     *
234
     * @param string $file
235
     * @param bool   $minify Si es necesario reducir
236
     * @param string $base
237
     *
238
     * @return $this
239
     */
240
    public function addFile($file, $minify = true, $base = null)
241
    {
242
        if ($base === null) {
243
            $base = $this->base;
244
            $filePath = $this->base . DIRECTORY_SEPARATOR . $file;
245
        } else {
246
            $filePath = $base . DIRECTORY_SEPARATOR . $file;
247
        }
248
249
        if (file_exists($filePath)) {
250
            $this->files[] = [
251
                'type' => 'file',
252
                'base' => $base,
253
                'name' => Request::getSecureAppFile($file, $base),
254
                'min' => $minify === true && $this->needsMinify($file),
255
                'md5' => md5_file($filePath)
256
            ];
257
        } else {
258
            logger('File not found: ' . $filePath);
259
        }
260
261
        return $this;
262
    }
263
264
    /**
265
     * Comprobar si es necesario reducir
266
     *
267
     * @param string $file El nombre del archivo
268
     *
269
     * @return bool
270
     */
271
    private function needsMinify($file)
272
    {
273
        return !preg_match('/\.min|pack\.css|js/', $file);
274
    }
275
276
    /**
277
     * @param array $files
278
     * @param bool  $minify
279
     *
280
     * @return Minify
281
     */
282
    public function addFiles(array $files, $minify = true)
283
    {
284
        foreach ($files as $filename) {
285
            $this->processFile($filename, $minify);
286
        }
287
288
        return $this;
289
    }
290
291
    /**
292
     * @param      $file
293
     * @param bool $minify
294
     */
295
    protected function processFile($file, $minify = true)
296
    {
297
        $filePath = $this->base . DIRECTORY_SEPARATOR . $file;
298
299
        if (file_exists($filePath)) {
300
            $this->files[] = array(
301
                'type' => 'file',
302
                'base' => $this->base,
303
                'name' => Request::getSecureAppFile($file, $this->base),
304
                'min' => $minify === true && $this->needsMinify($file),
305
                'md5' => md5_file($filePath)
306
            );
307
        } else {
308
            logger('File not found: ' . $filePath);
309
        }
310
    }
311
312
    /**
313
     * Añadir un recurso desde URL
314
     *
315
     * @param $url
316
     *
317
     * @return $this
318
     */
319
    public function addUrl($url)
320
    {
321
        $this->files[] = array(
322
            'type' => 'url',
323
            'base' => $this->base,
324
            'name' => $url,
325
            'min' => false,
326
            'md5' => ''
327
        );
328
329
        return $this;
330
    }
331
332
    /**
333
     * Establecer el tipo de recurso a procesar
334
     *
335
     * @param int $type
336
     *
337
     * @return $this
338
     */
339
    public function setType($type)
340
    {
341
        $this->type = (int)$type;
342
343
        return $this;
344
    }
345
}