Issues (2882)

src/View/Requirements.php (5 issues)

1
<?php
2
3
namespace SilverStripe\View;
4
5
use InvalidArgumentException;
6
use SilverStripe\Control\HTTPResponse;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Core\Flushable;
9
use SilverStripe\Dev\Deprecation;
10
11
/**
12
 * Requirements tracker for JavaScript and CSS.
13
 */
14
class Requirements implements Flushable
15
{
16
17
    /**
18
     * Flag whether combined files should be deleted on flush.
19
     *
20
     * By default all combined files are deleted on flush. If combined files are stored in source control,
21
     * and thus updated manually, you might want to turn this on to disable this behaviour.
22
     *
23
     * @config
24
     * @var bool
25
     */
26
    private static $disable_flush_combined = false;
0 ignored issues
show
The private property $disable_flush_combined is not used, and could be removed.
Loading history...
27
28
    /**
29
     * Triggered early in the request when a flush is requested
30
     */
31
    public static function flush()
32
    {
33
        $disabled = Config::inst()->get(static::class, 'disable_flush_combined');
34
        if (!$disabled) {
35
            self::delete_all_combined_files();
36
        }
37
    }
38
39
    /**
40
     * Enable combining of css/javascript files.
41
     *
42
     * @param bool $enable
43
     */
44
    public static function set_combined_files_enabled($enable)
45
    {
46
        self::backend()->setCombinedFilesEnabled($enable);
47
    }
48
49
    /**
50
     * Checks whether combining of css/javascript files is enabled.
51
     *
52
     * @return bool
53
     */
54
    public static function get_combined_files_enabled()
55
    {
56
        return self::backend()->getCombinedFilesEnabled();
57
    }
58
59
    /**
60
     * Set the relative folder e.g. 'assets' for where to store combined files
61
     *
62
     * @param string $folder Path to folder
63
     */
64
    public static function set_combined_files_folder($folder)
65
    {
66
        self::backend()->setCombinedFilesFolder($folder);
67
    }
68
69
    /**
70
     * Set whether to add caching query params to the requests for file-based requirements.
71
     * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
72
     * filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
73
     * while automatically busting this cache every time the file is changed.
74
     *
75
     * @param bool
76
     */
77
    public static function set_suffix_requirements($var)
78
    {
79
        self::backend()->setSuffixRequirements($var);
80
    }
81
82
    /**
83
     * Check whether we want to suffix requirements
84
     *
85
     * @return bool
86
     */
87
    public static function get_suffix_requirements()
88
    {
89
        return self::backend()->getSuffixRequirements();
90
    }
91
92
    /**
93
     * Instance of the requirements for storage. You can create your own backend to change the
94
     * default JS and CSS inclusion behaviour.
95
     *
96
     * @var Requirements_Backend
97
     */
98
    private static $backend = null;
99
100
    /**
101
     * @return Requirements_Backend
102
     */
103
    public static function backend()
104
    {
105
        if (!self::$backend) {
106
            self::$backend = Requirements_Backend::create();
107
        }
108
        return self::$backend;
109
    }
110
111
    /**
112
     * Setter method for changing the Requirements backend
113
     *
114
     * @param Requirements_Backend $backend
115
     */
116
    public static function set_backend(Requirements_Backend $backend)
117
    {
118
        self::$backend = $backend;
119
    }
120
121
    /**
122
     * Register the given JavaScript file as required.
123
     *
124
     * @param string $file Relative to docroot
125
     * @param array $options List of options. Available options include:
126
     * - 'provides' : List of scripts files included in this file
127
     * - 'async' : Boolean value to set async attribute to script tag
128
     * - 'defer' : Boolean value to set defer attribute to script tag
129
     */
130
    public static function javascript($file, $options = array())
131
    {
132
        self::backend()->javascript($file, $options);
133
    }
134
135
    /**
136
     * Register the given JavaScript code into the list of requirements
137
     *
138
     * @param string     $script       The script content as a string (without enclosing `<script>` tag)
139
     * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
140
     */
141
    public static function customScript($script, $uniquenessID = null)
142
    {
143
        self::backend()->customScript($script, $uniquenessID);
144
    }
145
146
    /**
147
     * Return all registered custom scripts
148
     *
149
     * @return array
150
     */
151
    public static function get_custom_scripts()
152
    {
153
        return self::backend()->getCustomScripts();
154
    }
155
156
    /**
157
     * Register the given CSS styles into the list of requirements
158
     *
159
     * @param string     $script       CSS selectors as a string (without enclosing `<style>` tag)
160
     * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
161
     */
162
    public static function customCSS($script, $uniquenessID = null)
163
    {
164
        self::backend()->customCSS($script, $uniquenessID);
165
    }
166
167
    /**
168
     * Add the following custom HTML code to the `<head>` section of the page
169
     *
170
     * @param string     $html         Custom HTML code
171
     * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
172
     */
173
    public static function insertHeadTags($html, $uniquenessID = null)
174
    {
175
        self::backend()->insertHeadTags($html, $uniquenessID);
176
    }
177
178
    /**
179
     * Include the content of the given JavaScript file in the list of requirements. Dollar-sign
180
     * variables will be interpolated with values from $vars similar to a .ss template.
181
     *
182
     * @param string         $file         The template file to load, relative to docroot
183
     * @param string[]|int[] $vars         The array of variables to interpolate.
184
     * @param string|int     $uniquenessID A unique ID that ensures a piece of code is only added once
185
     */
186
    public static function javascriptTemplate($file, $vars, $uniquenessID = null)
187
    {
188
        self::backend()->javascriptTemplate($file, $vars, $uniquenessID);
189
    }
190
191
    /**
192
     * Register the given stylesheet into the list of requirements.
193
     *
194
     * @param string $file  The CSS file to load, relative to site root
195
     * @param string $media Comma-separated list of media types to use in the link tag
196
     *                      (e.g. 'screen,projector')
197
     * @param array $options List of options. Available options include:
198
     * - 'integrity' : SubResource Integrity hash
199
     * - 'crossorigin' : Cross-origin policy for the resource
200
     */
201
    public static function css($file, $media = null, $options = [])
202
    {
203
        self::backend()->css($file, $media, $options);
204
    }
205
206
    /**
207
     * Registers the given themeable stylesheet as required.
208
     *
209
     * A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
210
     * and it that doesn't exist and the module parameter is set then a CSS file with that name in
211
     * the module is used.
212
     *
213
     * @param string $name   The name of the file - eg '/css/File.css' would have the name 'File'
214
     * @param string $media  Comma-separated list of media types to use in the link tag
215
     *                       (e.g. 'screen,projector')
216
     */
217
    public static function themedCSS($name, $media = null)
218
    {
219
        self::backend()->themedCSS($name, $media);
220
    }
221
222
    /**
223
     * Registers the given themeable javascript as required.
224
     *
225
     * A javascript file in the current theme path name 'themename/javascript/$name.js' is first searched for,
226
     * and it that doesn't exist and the module parameter is set then a javascript file with that name in
227
     * the module is used.
228
     *
229
     * @param string $name   The name of the file - eg '/javascript/File.js' would have the name 'File'
230
     * @param string $type  Comma-separated list of types to use in the script tag
231
     *                       (e.g. 'text/javascript,text/ecmascript')
232
     */
233
    public static function themedJavascript($name, $type = null)
234
    {
235
        self::backend()->themedJavascript($name, $type);
236
    }
237
238
    /**
239
     * Clear either a single or all requirements
240
     *
241
     * Caution: Clearing single rules added via customCSS and customScript only works if you
242
     * originally specified a $uniquenessID.
243
     *
244
     * @param string|int $fileOrID
245
     */
246
    public static function clear($fileOrID = null)
247
    {
248
        self::backend()->clear($fileOrID);
249
    }
250
251
    /**
252
     * Restore requirements cleared by call to Requirements::clear
253
     */
254
    public static function restore()
255
    {
256
        self::backend()->restore();
257
    }
258
259
    /**
260
     * Block inclusion of a specific file
261
     *
262
     * The difference between this and {@link clear} is that the calling order does not matter;
263
     * {@link clear} must be called after the initial registration, whereas {@link block} can be
264
     * used in advance. This is useful, for example, to block scripts included by a superclass
265
     * without having to override entire functions and duplicate a lot of code.
266
     *
267
     * Note that blocking should be used sparingly because it's hard to trace where an file is
268
     * being blocked from.
269
     *
270
     * @param string|int $fileOrID
271
     */
272
    public static function block($fileOrID)
273
    {
274
        self::backend()->block($fileOrID);
275
    }
276
277
    /**
278
     * Remove an item from the block list
279
     *
280
     * @param string|int $fileOrID
281
     */
282
    public static function unblock($fileOrID)
283
    {
284
        self::backend()->unblock($fileOrID);
285
    }
286
287
    /**
288
     * Removes all items from the block list
289
     */
290
    public static function unblock_all()
291
    {
292
        self::backend()->unblockAll();
293
    }
294
295
    /**
296
     * Update the given HTML content with the appropriate include tags for the registered
297
     * requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
298
     * including a head and body tag.
299
     *
300
     * @param string $content      HTML content that has already been parsed from the $templateFile
301
     *                             through {@link SSViewer}
302
     * @return string HTML content augmented with the requirements tags
303
     */
304
    public static function includeInHTML($content)
305
    {
306
        if (func_num_args() > 1) {
307
            Deprecation::notice(
308
                '5.0',
309
                '$templateFile argument is deprecated. includeInHTML takes a sole $content parameter now.'
310
            );
311
            $content = func_get_arg(1);
312
        }
313
314
        return self::backend()->includeInHTML($content);
315
    }
316
317
    /**
318
     * Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
319
     * HTTP Response
320
     *
321
     * @param HTTPResponse $response
322
     */
323
    public static function include_in_response(HTTPResponse $response)
324
    {
325
        self::backend()->includeInResponse($response);
326
    }
327
328
    /**
329
     * Add i18n files from the given javascript directory. SilverStripe expects that the given
330
     * directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
331
     * etc.
332
     *
333
     * @param string $langDir  The JavaScript lang directory, relative to the site root, e.g.,
334
     *                         'framework/javascript/lang'
335
     * @param bool   $return   Return all relative file paths rather than including them in
336
     *                         requirements
337
     * @param bool $langOnly @deprecated 4.1.0:5.0.0 as i18n.js should be included manually in your project
338
     *
339
     * @return array
340
     */
341
    public static function add_i18n_javascript($langDir, $return = false, $langOnly = false)
0 ignored issues
show
The parameter $langOnly is not used and could be removed. ( Ignorable by Annotation )

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

341
    public static function add_i18n_javascript($langDir, $return = false, /** @scrutinizer ignore-unused */ $langOnly = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
342
    {
343
        return self::backend()->add_i18n_javascript($langDir, $return);
344
    }
345
346
    /**
347
     * Concatenate several css or javascript files into a single dynamically generated file. This
348
     * increases performance by fewer HTTP requests.
349
     *
350
     * The combined file is regenerated based on every file modification time. Optionally a
351
     * rebuild can be triggered by appending ?flush=1 to the URL.
352
     *
353
     * All combined files will have a comment on the start of each concatenated file denoting their
354
     * original position.
355
     *
356
     * CAUTION: You're responsible for ensuring that the load order for combined files is
357
     * retained - otherwise combining JavaScript files can lead to functional errors in the
358
     * JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
359
     * only include each file once across all includes and comibinations in a single page load.
360
     *
361
     * CAUTION: Combining CSS Files discards any "media" information.
362
     *
363
     * Example for combined JavaScript:
364
     * <code>
365
     * Requirements::combine_files(
366
     *  'foobar.js',
367
     *  array(
368
     *        'mysite/javascript/foo.js',
369
     *        'mysite/javascript/bar.js',
370
     *    )
371
     * );
372
     * </code>
373
     *
374
     * Example for combined CSS:
375
     * <code>
376
     * Requirements::combine_files(
377
     *  'foobar.css',
378
     *    array(
379
     *        'mysite/javascript/foo.css',
380
     *        'mysite/javascript/bar.css',
381
     *    )
382
     * );
383
     * </code>
384
     *
385
     * @param string $combinedFileName Filename of the combined file relative to docroot
386
     * @param array  $files            Array of filenames relative to docroot
387
     * @param array  $options          Array of options for combining files. Available options are:
388
     * - 'media' : If including CSS Files, you can specify a media type
389
     * - 'async' : If including JavaScript Files, boolean value to set async attribute to script tag
390
     * - 'defer' : If including JavaScript Files, boolean value to set defer attribute to script tag
391
     */
392
    public static function combine_files($combinedFileName, $files, $options = array())
393
    {
394
        if (is_string($options)) {
0 ignored issues
show
The condition is_string($options) is always false.
Loading history...
395
            throw new InvalidArgumentException("Invalid $options");
396
        }
397
        self::backend()->combineFiles($combinedFileName, $files, $options);
398
    }
399
400
    /**
401
     * Return all combined files; keys are the combined file names, values are lists of
402
     * associative arrays with 'files', 'type', and 'media' keys for details about this
403
     * combined file.
404
     *
405
     * @return array
406
     */
407
    public static function get_combine_files()
408
    {
409
        return self::backend()->getCombinedFiles();
410
    }
411
412
    /**
413
     * Deletes all generated combined files in the configured combined files directory,
414
     * but doesn't delete the directory itself
415
     */
416
    public static function delete_all_combined_files()
417
    {
418
        self::backend()->deleteAllCombinedFiles();
419
    }
420
421
    /**
422
     * Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
423
     */
424
    public static function clear_combined_files()
425
    {
426
        self::backend()->clearCombinedFiles();
427
    }
428
429
    /**
430
     * Do the heavy lifting involved in combining the combined files.
431
     */
432
    public static function process_combined_files()
433
    {
434
        self::backend()->processCombinedFiles();
435
    }
436
437
    /**
438
     * Set whether you want to write the JS to the body of the page rather than at the end of the
439
     * head tag.
440
     *
441
     * @return bool
442
     */
443
    public static function get_write_js_to_body()
444
    {
445
        return self::backend()->getWriteJavascriptToBody();
446
    }
447
448
    /**
449
     * Set whether you want to write the JS to the body of the page rather than at the end of the
450
     * head tag.
451
     *
452
     * @param bool
453
     */
454
    public static function set_write_js_to_body($var)
455
    {
456
        self::backend()->setWriteJavascriptToBody($var);
457
    }
458
459
    /**
460
     * Get whether to force the JavaScript to end of the body. Useful if you use inline script tags
461
     * that don't rely on scripts included via {@link Requirements::javascript()).
462
     *
463
     * @return bool
464
     */
465
    public static function get_force_js_to_bottom()
466
    {
467
        return self::backend()->getForceJSToBottom();
468
    }
469
470
    /**
471
     * Set whether to force the JavaScript to end of the body. Useful if you use inline script tags
472
     * that don't rely on scripts included via {@link Requirements::javascript()).
473
     *
474
     * @param bool $var If true, force the JavaScript to be included at the bottom of the page
475
     */
476
    public static function set_force_js_to_bottom($var)
477
    {
478
        self::backend()->setForceJSToBottom($var);
479
    }
480
481
    /**
482
     * Check if JS minification is enabled
483
     *
484
     * @return bool
485
     */
486
    public static function get_minify_combined_js_files()
487
    {
488
        return self::backend()->getMinifyCombinedJSFiles();
0 ignored issues
show
The method getMinifyCombinedJSFiles() does not exist on SilverStripe\View\Requirements_Backend. Did you maybe mean getMinifyCombinedFiles()? ( Ignorable by Annotation )

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

488
        return self::backend()->/** @scrutinizer ignore-call */ getMinifyCombinedJSFiles();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
489
    }
490
491
    /**
492
     * Enable or disable js minification
493
     *
494
     * @param bool $minify
495
     */
496
    public static function set_minify_combined_js_files($minify)
497
    {
498
        self::backend()->setMinifyCombinedJSFiles($minify);
0 ignored issues
show
The method setMinifyCombinedJSFiles() does not exist on SilverStripe\View\Requirements_Backend. Did you maybe mean setMinifyCombinedFiles()? ( Ignorable by Annotation )

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

498
        self::backend()->/** @scrutinizer ignore-call */ setMinifyCombinedJSFiles($minify);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
499
    }
500
501
    /**
502
     * Check if header comments are written
503
     *
504
     * @return bool
505
     */
506
    public static function get_write_header_comments()
507
    {
508
        return self::backend()->getWriteHeaderComment();
509
    }
510
511
    /**
512
     * Flag whether header comments should be written for each combined file
513
     *
514
     * @param bool $write
515
     */
516
    public function set_write_header_comments($write)
517
    {
518
        self::backend()->setWriteHeaderComment($write);
519
    }
520
521
522
    /**
523
     * Output debugging information
524
     */
525
    public static function debug()
526
    {
527
        self::backend()->debug();
528
    }
529
}
530