Issues (3627)

CoreBundle/Helper/AssetGenerationHelper.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\CoreBundle\Helper;
13
14
use Symfony\Component\Finder\Finder;
15
16
/**
17
 * Class AssetGenerationHelper.
18
 */
19
class AssetGenerationHelper
20
{
21
    /**
22
     * @var BundleHelper
23
     */
24
    private $bundleHelper;
25
26
    /**
27
     * @var PathsHelper
28
     */
29
    private $pathsHelper;
30
31
    /**
32
     * @var string
33
     */
34
    private $version;
35
36
    /**
37
     * AssetGenerationHelper constructor.
38
     */
39
    public function __construct(CoreParametersHelper $coreParametersHelper, BundleHelper $bundleHelper, PathsHelper $pathsHelper, AppVersion $version)
40
    {
41
        $this->bundleHelper         = $bundleHelper;
42
        $this->pathsHelper          = $pathsHelper;
43
        $this->version              = substr(hash('sha1', $coreParametersHelper->get('secret_key').$version->getVersion()), 0, 8);
44
    }
45
46
    /**
47
     * Generates and returns assets.
48
     *
49
     * @param bool $forceRegeneration
50
     *
51
     * @return array
52
     */
53
    public function getAssets($forceRegeneration = false)
54
    {
55
        static $assets = [];
56
57
        if (empty($assets)) {
58
            $loadAll    = true;
59
            $env        = ($forceRegeneration) ? 'prod' : MAUTIC_ENV;
60
            $rootPath   = $this->pathsHelper->getSystemPath('assets_root');
61
            $assetsPath = $this->pathsHelper->getSystemPath('assets');
62
63
            $assetsFullPath = "$rootPath/$assetsPath";
64
            if ('prod' == $env) {
65
                $loadAll = false; //by default, loading should not be required
66
67
                //check for libraries and app files and generate them if they don't exist if in prod environment
68
                $prodFiles = [
69
                    'css/libraries.css',
70
                    'css/app.css',
71
                    'js/libraries.js',
72
                    'js/app.js',
73
                ];
74
75
                foreach ($prodFiles as $file) {
76
                    if (!file_exists("$assetsFullPath/$file")) {
77
                        $loadAll = true; //it's missing so compile it
78
                        break;
79
                    }
80
                }
81
            }
82
83
            if ($loadAll || $forceRegeneration) {
84
                if ('prod' == $env) {
85
                    ini_set('max_execution_time', 300);
86
87
                    $inProgressFile = "$assetsFullPath/generation_in_progress.txt";
88
89
                    if (!$forceRegeneration) {
90
                        while (file_exists($inProgressFile)) {
91
                            //dummy loop to prevent conflicts if one process is actively regenerating assets
92
                        }
93
                    }
94
                    file_put_contents($inProgressFile, date('r'));
95
                }
96
97
                $modifiedLast = [];
98
99
                //get a list of all core asset files
100
                $bundles = $this->bundleHelper->getMauticBundles();
101
102
                $fileTypes = ['css', 'js'];
103
                foreach ($bundles as $bundle) {
104
                    foreach ($fileTypes as $ft) {
105
                        if (!isset($modifiedLast[$ft])) {
106
                            $modifiedLast[$ft] = [];
107
                        }
108
                        $dir = "{$bundle['directory']}/Assets/$ft";
109
                        if (file_exists($dir)) {
110
                            $modifiedLast[$ft] = array_merge($modifiedLast[$ft], $this->findAssets($dir, $ft, $env, $assets));
111
                        }
112
                    }
113
                }
114
                $modifiedLast = array_merge($modifiedLast, $this->findOverrides($env, $assets));
115
116
                //combine the files into their corresponding name and put in the root media folder
117
                if ('prod' == $env) {
118
                    $checkPaths = [
119
                        $assetsFullPath,
120
                        "$assetsFullPath/css",
121
                        "$assetsFullPath/js",
122
                    ];
123
                    array_walk($checkPaths, function ($path) {
124
                        if (!file_exists($path)) {
125
                            mkdir($path);
126
                        }
127
                    });
128
129
                    $useMinify = class_exists('\Minify');
130
131
                    foreach ($assets as $type => $groups) {
132
                        foreach ($groups as $group => $files) {
133
                            $assetFile = "$assetsFullPath/$type/$group.$type";
134
135
                            //only refresh if a change has occurred
136
                            $modified = ($forceRegeneration || !file_exists($assetFile)) ? true : filemtime($assetFile) < $modifiedLast[$type][$group];
137
138
                            if ($modified) {
139
                                if (file_exists($assetFile)) {
140
                                    //delete it
141
                                    unlink($assetFile);
142
                                }
143
144
                                if ('css' == $type) {
145
                                    $out = fopen($assetFile, 'w');
146
147
                                    foreach ($files as $relPath => $details) {
148
                                        $cssRel = '../../'.dirname($relPath).'/';
149
                                        if ($useMinify) {
150
                                            $content = \Minify::combine([$details['fullPath']], [
151
                                                'rewriteCssUris'  => false,
152
                                                'minifierOptions' => [
153
                                                    'text/css' => [
154
                                                        'currentDir'          => '',
155
                                                        'prependRelativePath' => $cssRel,
156
                                                    ],
157
                                                ],
158
                                            ]);
159
                                        } else {
160
                                            $content = file_get_contents($details['fullPath']);
161
                                            $search  = '#url\((?!\s*([\'"]?(((?:https?:)?//)|(?:data\:?:))))\s*([\'"])?#';
162
                                            $replace = "url($4{$cssRel}";
163
                                            $content = preg_replace($search, $replace, $content);
164
                                        }
165
166
                                        fwrite($out, $content);
167
                                    }
168
169
                                    fclose($out);
170
                                } else {
171
                                    array_walk($files, function (&$file) {
172
                                        $file = $file['fullPath'];
173
                                    });
174
                                    file_put_contents($assetFile, \Minify::combine($files));
175
                                }
176
                            }
177
                        }
178
                    }
179
180
                    unlink($inProgressFile);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $inProgressFile does not seem to be defined for all execution paths leading up to this point.
Loading history...
181
                }
182
            }
183
184
            if ('prod' == $env) {
185
                //return prod generated assets
186
                $assets = [
187
                    'css' => [
188
                        "{$assetsPath}/css/libraries.css?v{$this->version}",
189
                        "{$assetsPath}/css/app.css?v{$this->version}",
190
                    ],
191
                    'js' => [
192
                        "{$assetsPath}/js/libraries.js?v{$this->version}",
193
                        "{$assetsPath}/js/app.js?v{$this->version}",
194
                    ],
195
                ];
196
            } else {
197
                foreach ($assets as &$typeAssets) {
198
                    $typeAssets = array_keys($typeAssets);
199
                }
200
            }
201
        }
202
203
        return $assets;
204
    }
205
206
    /**
207
     * Finds directory assets.
208
     *
209
     * @param string $dir
210
     * @param string $ext
211
     * @param string $env
212
     * @param array  $assets
213
     *
214
     * @return array
215
     */
216
    protected function findAssets($dir, $ext, $env, &$assets)
217
    {
218
        $rootPath    = str_replace('\\', '/', $this->pathsHelper->getSystemPath('assets_root').'/');
219
        $directories = new Finder();
220
        $directories->directories()->exclude('*less')->depth('0')->ignoreDotFiles(true)->in($dir);
221
222
        $modifiedLast = [];
223
224
        if (count($directories)) {
225
            foreach ($directories as $directory) {
226
                $group = $directory->getBasename();
227
228
                // Only auto load directories app or libraries
229
                if (!in_array($group, ['app', 'libraries'])) {
230
                    continue;
231
                }
232
233
                $files         = new Finder();
234
                $thisDirectory = str_replace('\\', '/', $directory->getRealPath());
235
                $files->files()->depth('0')->name('*.'.$ext)->in($thisDirectory);
236
237
                $sort = function (\SplFileInfo $a, \SplFileInfo $b) {
238
                    return strnatcmp($a->getRealpath(), $b->getRealpath());
239
                };
240
                $files->sort($sort);
241
242
                foreach ($files as $file) {
243
                    $fullPath = $file->getPathname();
244
                    $relPath  = str_replace($rootPath, '', $file->getPathname());
245
                    if (0 === strpos($relPath, '/')) {
246
                        $relPath = substr($relPath, 1);
247
                    }
248
249
                    $details = [
250
                        'fullPath' => $fullPath,
251
                        'relPath'  => $relPath,
252
                    ];
253
254
                    if ('prod' == $env) {
255
                        $lastModified = filemtime($fullPath);
256
                        if (!isset($modifiedLast[$group]) || $lastModified > $modifiedLast[$group]) {
257
                            $modifiedLast[$group] = $lastModified;
258
                        }
259
                        $assets[$ext][$group][$relPath] = $details;
260
                    } else {
261
                        $assets[$ext][$relPath] = $details;
262
                    }
263
                }
264
                unset($files);
265
            }
266
        }
267
268
        unset($directories);
269
        $files = new Finder();
270
        $files->files()->depth('0')->ignoreDotFiles(true)->name('*.'.$ext)->in($dir);
271
272
        $sort = function (\SplFileInfo $a, \SplFileInfo $b) {
273
            return strnatcmp($a->getRealpath(), $b->getRealpath());
274
        };
275
        $files->sort($sort);
276
277
        foreach ($files as $file) {
278
            $fullPath = str_replace('\\', '/', $file->getPathname());
279
            $relPath  = str_replace($rootPath, '', $fullPath);
280
281
            $details = [
282
                'fullPath' => $fullPath,
283
                'relPath'  => $relPath,
284
            ];
285
286
            if ('prod' == $env) {
287
                $lastModified = filemtime($fullPath);
288
                if (!isset($modifiedLast['app']) || $lastModified > $modifiedLast['app']) {
289
                    $modifiedLast['app'] = $lastModified;
290
                }
291
                $assets[$ext]['app'][$relPath] = $details;
292
            } else {
293
                $assets[$ext][$relPath] = $details;
294
            }
295
        }
296
        unset($files);
297
298
        return $modifiedLast;
299
    }
300
301
    /**
302
     * Find asset overrides in the template.
303
     *
304
     * @param $env
305
     * @param $assets
306
     *
307
     * @return array
308
     */
309
    protected function findOverrides($env, &$assets)
310
    {
311
        $rootPath      = $this->pathsHelper->getSystemPath('assets_root');
312
        $currentTheme  = $this->pathsHelper->getSystemPath('current_theme');
313
        $modifiedLast  = [];
314
        $types         = ['css', 'js'];
315
        $overrideFiles = [
316
            'libraries' => 'libraries_custom',
317
            'app'       => 'app_custom',
318
        ];
319
320
        foreach ($types as $ext) {
321
            foreach ($overrideFiles as $group => $of) {
322
                if (file_exists("$rootPath/$currentTheme/$ext/$of.$ext")) {
323
                    $fullPath = "$rootPath/$currentTheme/$ext/$of.$ext";
324
                    $relPath  = "$currentTheme/$ext/$of.$ext";
325
326
                    $details = [
327
                        'fullPath' => $fullPath,
328
                        'relPath'  => $relPath,
329
                    ];
330
331
                    if ('prod' == $env) {
332
                        $lastModified = filemtime($fullPath);
333
                        if (!isset($modifiedLast[$ext][$group]) || $lastModified > $modifiedLast[$ext][$group]) {
334
                            $modifiedLast[$ext][$group] = $lastModified;
335
                        }
336
                        $assets[$ext][$group][$relPath] = $details;
337
                    } else {
338
                        $assets[$ext][$relPath] = $details;
339
                    }
340
                }
341
            }
342
        }
343
344
        return $modifiedLast;
345
    }
346
}
347