Completed
Push — master ( f6cae8...f7ba78 )
by Daniel
23s
created

Requirements_Backend::javascript()   D

Complexity

Conditions 10
Paths 98

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 18
c 1
b 0
f 1
nc 98
nop 2
dl 0
loc 29
rs 4.8196

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use SilverStripe\Filesystem\Storage\GeneratedAssetHandler;
4
5
/**
6
 * Requirements tracker for JavaScript and CSS.
7
 *
8
 * @package framework
9
 * @subpackage view
10
 */
11
class Requirements implements Flushable {
12
13
	/**
14
	 * Flag whether combined files should be deleted on flush.
15
	 *
16
	 * By default all combined files are deleted on flush. If combined files are stored in source control,
17
	 * and thus updated manually, you might want to turn this on to disable this behaviour.
18
	 *
19
	 * @config
20
	 * @var bool
21
	 */
22
	private static $disable_flush_combined = false;
23
24
	/**
25
	 * Triggered early in the request when a flush is requested
26
	 */
27
	public static function flush() {
28
		$disabled = Config::inst()->get(static::class, 'disable_flush_combined');
29
		if(!$disabled) {
30
			self::delete_all_combined_files();
31
		}
32
	}
33
34
	/**
35
	 * Enable combining of css/javascript files.
36
	 *
37
	 * @param bool $enable
38
	 */
39
	public static function set_combined_files_enabled($enable) {
40
		self::backend()->setCombinedFilesEnabled($enable);
41
	}
42
43
	/**
44
	 * Checks whether combining of css/javascript files is enabled.
45
	 *
46
	 * @return bool
47
	 */
48
	public static function get_combined_files_enabled() {
49
		return self::backend()->getCombinedFilesEnabled();
50
	}
51
52
	/**
53
	 * Set the relative folder e.g. 'assets' for where to store combined files
54
	 *
55
	 * @param string $folder Path to folder
56
	 */
57
	public static function set_combined_files_folder($folder) {
58
		self::backend()->setCombinedFilesFolder($folder);
59
	}
60
61
	/**
62
	 * Set whether to add caching query params to the requests for file-based requirements.
63
	 * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
64
	 * filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
65
	 * while automatically busting this cache every time the file is changed.
66
	 *
67
	 * @param bool
68
	 */
69
	public static function set_suffix_requirements($var) {
70
		self::backend()->setSuffixRequirements($var);
71
	}
72
73
	/**
74
	 * Check whether we want to suffix requirements
75
	 *
76
	 * @return bool
77
	 */
78
	public static function get_suffix_requirements() {
79
		return self::backend()->getSuffixRequirements();
80
	}
81
82
	/**
83
	 * Instance of the requirements for storage. You can create your own backend to change the
84
	 * default JS and CSS inclusion behaviour.
85
	 *
86
	 * @var Requirements_Backend
87
	 */
88
	private static $backend = null;
89
90
	/**
91
	 * @return Requirements_Backend
92
	 */
93
	public static function backend() {
94
		if(!self::$backend) {
95
			self::$backend = Injector::inst()->create('Requirements_Backend');
96
		}
97
		return self::$backend;
98
	}
99
100
	/**
101
	 * Setter method for changing the Requirements backend
102
	 *
103
	 * @param Requirements_Backend $backend
104
	 */
105
	public static function set_backend(Requirements_Backend $backend) {
106
		self::$backend = $backend;
107
	}
108
109
	/**
110
	 * Register the given JavaScript file as required.
111
	 *
112
	 * @param string $file Relative to docroot
113
	 * @param array $options List of options. Available options include:
114
	 * - 'provides' : List of scripts files included in this file
115
	 * - 'async' : Boolean value to set async attribute to script tag
116
	 * - 'defer' : Boolean value to set defer attribute to script tag
117
	 */
118
	public static function javascript($file, $options = array()) {
119
		self::backend()->javascript($file, $options);
120
	}
121
122
	/**
123
	 * Register the given JavaScript code into the list of requirements
124
	 *
125
	 * @param string     $script       The script content as a string (without enclosing <script> tag)
126
	 * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
127
	 */
128
	public static function customScript($script, $uniquenessID = null) {
129
		self::backend()->customScript($script, $uniquenessID);
130
	}
131
132
	/**
133
	 * Return all registered custom scripts
134
	 *
135
	 * @return array
136
	 */
137
	public static function get_custom_scripts() {
138
		return self::backend()->getCustomScripts();
139
	}
140
141
	/**
142
	 * Register the given CSS styles into the list of requirements
143
	 *
144
	 * @param string     $script       CSS selectors as a string (without enclosing <style> tag)
145
	 * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
146
	 */
147
	public static function customCSS($script, $uniquenessID = null) {
148
		self::backend()->customCSS($script, $uniquenessID);
149
	}
150
151
	/**
152
	 * Add the following custom HTML code to the <head> section of the page
153
	 *
154
	 * @param string     $html         Custom HTML code
155
	 * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
156
	 */
157
	public static function insertHeadTags($html, $uniquenessID = null) {
158
		self::backend()->insertHeadTags($html, $uniquenessID);
159
	}
160
161
	/**
162
	 * Include the content of the given JavaScript file in the list of requirements. Dollar-sign
163
	 * variables will be interpolated with values from $vars similar to a .ss template.
164
	 *
165
	 * @param string         $file         The template file to load, relative to docroot
166
	 * @param string[]|int[] $vars         The array of variables to interpolate.
167
	 * @param string|int     $uniquenessID A unique ID that ensures a piece of code is only added once
168
	 */
169
	public static function javascriptTemplate($file, $vars, $uniquenessID = null) {
170
		self::backend()->javascriptTemplate($file, $vars, $uniquenessID);
171
	}
172
173
	/**
174
	 * Register the given stylesheet into the list of requirements.
175
	 *
176
	 * @param string $file  The CSS file to load, relative to site root
177
	 * @param string $media Comma-separated list of media types to use in the link tag
178
	 *                      (e.g. 'screen,projector')
179
	 */
180
	public static function css($file, $media = null) {
181
		self::backend()->css($file, $media);
182
	}
183
184
	/**
185
	 * Registers the given themeable stylesheet as required.
186
	 *
187
	 * A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
188
	 * and it that doesn't exist and the module parameter is set then a CSS file with that name in
189
	 * the module is used.
190
	 *
191
	 * @param string $name   The name of the file - eg '/css/File.css' would have the name 'File'
192
	 * @param string $module The module to fall back to if the css file does not exist in the
193
	 *                       current theme.
194
	 * @param string $media  Comma-separated list of media types to use in the link tag
195
	 *                       (e.g. 'screen,projector')
196
	 */
197
	public static function themedCSS($name, $module = null, $media = null) {
198
		self::backend()->themedCSS($name, $module, $media);
199
	}
200
201
	/**
202
	 * Clear either a single or all requirements
203
	 *
204
	 * Caution: Clearing single rules added via customCSS and customScript only works if you
205
	 * originally specified a $uniquenessID.
206
	 *
207
	 * @param string|int $fileOrID
208
	 */
209
	public static function clear($fileOrID = null) {
210
		self::backend()->clear($fileOrID);
211
	}
212
213
	/**
214
	 * Restore requirements cleared by call to Requirements::clear
215
	 */
216
	public static function restore() {
217
		self::backend()->restore();
218
	}
219
220
	/**
221
	 * Block inclusion of a specific file
222
	 *
223
	 * The difference between this and {@link clear} is that the calling order does not matter;
224
	 * {@link clear} must be called after the initial registration, whereas {@link block} can be
225
	 * used in advance. This is useful, for example, to block scripts included by a superclass
226
	 * without having to override entire functions and duplicate a lot of code.
227
	 *
228
	 * Note that blocking should be used sparingly because it's hard to trace where an file is
229
	 * being blocked from.
230
	 *
231
	 * @param string|int $fileOrID
232
	 */
233
	public static function block($fileOrID) {
234
		self::backend()->block($fileOrID);
235
	}
236
237
	/**
238
	 * Remove an item from the block list
239
	 *
240
	 * @param string|int $fileOrID
241
	 */
242
	public static function unblock($fileOrID) {
243
		self::backend()->unblock($fileOrID);
244
	}
245
246
	/**
247
	 * Removes all items from the block list
248
	 */
249
	public static function unblock_all() {
250
		self::backend()->unblockAll();
251
	}
252
253
	/**
254
	 * Update the given HTML content with the appropriate include tags for the registered
255
	 * requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
256
	 * including a head and body tag.
257
	 *
258
	 * @param string $content      HTML content that has already been parsed from the $templateFile
259
	 *                             through {@link SSViewer}
260
	 * @return string HTML content augmented with the requirements tags
261
	 */
262
	public static function includeInHTML($content) {
263
		if(func_num_args() > 1) {
264
			Deprecation::notice(
265
				'5.0',
266
				'$templateFile argument is deprecated. includeInHTML takes a sole $content parameter now.'
267
			);
268
			$content = func_get_arg(1);
269
		}
270
271
		return self::backend()->includeInHTML($content);
272
	}
273
274
	/**
275
	 * Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
276
	 * HTTP Response
277
	 *
278
	 * @param SS_HTTPResponse $response
279
	 */
280
	public static function include_in_response(SS_HTTPResponse $response) {
281
		self::backend()->includeInResponse($response);
282
	}
283
284
	/**
285
	 * Add i18n files from the given javascript directory. SilverStripe expects that the given
286
	 * directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
287
	 * etc.
288
	 *
289
	 * @param string $langDir  The JavaScript lang directory, relative to the site root, e.g.,
290
	 *                         'framework/javascript/lang'
291
	 * @param bool   $return   Return all relative file paths rather than including them in
292
	 *                         requirements
293
	 * @param bool   $langOnly Only include language files, not the base libraries
294
	 *
295
	 * @return array
296
	 */
297
	public static function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
298
		return self::backend()->add_i18n_javascript($langDir, $return, $langOnly);
299
	}
300
301
	/**
302
	 * Concatenate several css or javascript files into a single dynamically generated file. This
303
	 * increases performance by fewer HTTP requests.
304
	 *
305
	 * The combined file is regenerated based on every file modification time. Optionally a
306
	 * rebuild can be triggered by appending ?flush=1 to the URL.
307
	 *
308
	 * All combined files will have a comment on the start of each concatenated file denoting their
309
	 * original position.
310
	 *
311
	 * CAUTION: You're responsible for ensuring that the load order for combined files is
312
	 * retained - otherwise combining JavaScript files can lead to functional errors in the
313
	 * JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
314
	 * only include each file once across all includes and comibinations in a single page load.
315
	 *
316
	 * CAUTION: Combining CSS Files discards any "media" information.
317
	 *
318
	 * Example for combined JavaScript:
319
	 * <code>
320
	 * Requirements::combine_files(
321
	 *  'foobar.js',
322
	 *  array(
323
	 *        'mysite/javascript/foo.js',
324
	 *        'mysite/javascript/bar.js',
325
	 *    )
326
	 * );
327
	 * </code>
328
	 *
329
	 * Example for combined CSS:
330
	 * <code>
331
	 * Requirements::combine_files(
332
	 *  'foobar.css',
333
	 *    array(
334
	 *        'mysite/javascript/foo.css',
335
	 *        'mysite/javascript/bar.css',
336
	 *    )
337
	 * );
338
	 * </code>
339
	 *
340
	 * @param string $combinedFileName Filename of the combined file relative to docroot
341
	 * @param array  $files            Array of filenames relative to docroot
342
	 * @param array  $options          Array of options for combining files. Available options are:
343
	 * - 'media' : If including CSS Files, you can specify a media type
344
	 * - 'async' : If including JavaScript Files, boolean value to set async attribute to script tag
345
	 * - 'defer' : If including JavaScript Files, boolean value to set defer attribute to script tag
346
	 *
347
	 * @return bool|void
348
	 */
349
	public static function combine_files($combinedFileName, $files, $options = array()) {
350
        if(is_string($options))  {
351
            Deprecation::notice('4.0', 'Parameter media is deprecated. Use options array instead.');
352
            $options = array('media' => $options);
353
        }
354
		self::backend()->combineFiles($combinedFileName, $files, $options);
355
	}
356
357
	/**
358
	 * Return all combined files; keys are the combined file names, values are lists of
359
	 * associative arrays with 'files', 'type', and 'media' keys for details about this
360
	 * combined file.
361
	 *
362
	 * @return array
363
	 */
364
	public static function get_combine_files() {
365
		return self::backend()->getCombinedFiles();
366
	}
367
368
	/**
369
	 * Deletes all generated combined files in the configured combined files directory,
370
	 * but doesn't delete the directory itself
371
	 */
372
	public static function delete_all_combined_files() {
373
		self::backend()->deleteAllCombinedFiles();
374
	}
375
376
	/**
377
	 * Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
378
	 */
379
	public static function clear_combined_files() {
380
		self::backend()->clearCombinedFiles();
381
	}
382
383
	/**
384
	 * Do the heavy lifting involved in combining the combined files.
385
 	 */
386
	public static function process_combined_files() {
387
		self::backend()->processCombinedFiles();
388
	}
389
390
	/**
391
	 * Set whether you want to write the JS to the body of the page rather than at the end of the
392
	 * head tag.
393
	 *
394
	 * @return bool
395
	 */
396
	public static function get_write_js_to_body() {
397
		return self::backend()->getWriteJavascriptToBody();
398
	}
399
400
	/**
401
	 * Set whether you want to write the JS to the body of the page rather than at the end of the
402
	 * head tag.
403
	 *
404
	 * @param bool
405
	 */
406
	public static function set_write_js_to_body($var) {
407
		self::backend()->setWriteJavascriptToBody($var);
408
	}
409
410
	/**
411
	 * Get whether to force the JavaScript to end of the body. Useful if you use inline script tags
412
	 * that don't rely on scripts included via {@link Requirements::javascript()).
413
	 *
414
	 * @return bool
415
	 */
416
	public static function get_force_js_to_bottom() {
417
		return self::backend()->getForceJSToBottom();
418
	}
419
420
	/**
421
	 * Set whether to force the JavaScript to end of the body. Useful if you use inline script tags
422
	 * that don't rely on scripts included via {@link Requirements::javascript()).
423
	 *
424
	 * @param bool $var If true, force the JavaScript to be included at the bottom of the page
425
	 */
426
	public static function set_force_js_to_bottom($var) {
427
		self::backend()->setForceJSToBottom($var);
428
	}
429
430
	/**
431
	 * Check if JS minification is enabled
432
	 *
433
	 * @return bool
434
	 */
435
	public static function get_minify_combined_js_files() {
436
		return self::backend()->getMinifyCombinedJSFiles();
437
	}
438
439
	/**
440
	 * Enable or disable js minification
441
	 *
442
	 * @param bool $minify
443
	 */
444
	public static function set_minify_combined_js_files($minify) {
445
		self::backend()->setMinifyCombinedJSFiles($minify);
446
	}
447
448
	/**
449
	 * Check if header comments are written
450
	 *
451
	 * @return bool
452
	 */
453
	public static function get_write_header_comments() {
454
		return self::backend()->getWriteHeaderComment();
455
	}
456
457
	/**
458
	 * Flag whether header comments should be written for each combined file
459
	 *
460
	 * @param bool $write
461
	 */
462
	public function set_write_header_comments($write) {
463
		self::backend()->setWriteHeaderComment($write);
464
	}
465
466
467
	/**
468
	 * Output debugging information
469
	 */
470
	public static function debug() {
471
		self::backend()->debug();
472
	}
473
474
}
475
476
/**
477
 * @package framework
478
 * @subpackage view
479
 */
480
class Requirements_Backend
481
{
482
483
	/**
484
	 * Whether to add caching query params to the requests for file-based requirements.
485
	 * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
486
	 * filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
487
	 * while automatically busting this cache every time the file is changed.
488
	 *
489
	 * @var bool
490
	 */
491
	protected $suffixRequirements = true;
492
493
	/**
494
	 * Whether to combine CSS and JavaScript files
495
	 *
496
	 * @var bool
497
	 */
498
	protected $combinedFilesEnabled = true;
499
500
	/**
501
	 * Determine if files should be combined automatically on dev mode.
502
	 *
503
	 * By default combined files will not be combined except in test or
504
	 * live environments. Turning this on will allow for pre-combining of files in development mode.
505
	 *
506
	 * @config
507
	 * @var bool
508
	 */
509
	private static $combine_in_dev = false;
510
511
	/**
512
	 * Paths to all required JavaScript files relative to docroot
513
	 *
514
	 * @var array
515
	 */
516
	protected $javascript = array();
517
518
	/**
519
	 * Map of included scripts to array of contained files.
520
	 * To be used alongside front-end combination mechanisms.
521
	 *
522
	 * @var array Map of providing filepath => array(provided filepaths)
523
	 */
524
	protected $providedJavascript = array();
525
526
	/**
527
	 * Paths to all required CSS files relative to the docroot.
528
	 *
529
	 * @var array
530
	 */
531
	protected $css = array();
532
533
	/**
534
	 * All custom javascript code that is inserted into the page's HTML
535
	 *
536
	 * @var array
537
	 */
538
	protected $customScript = array();
539
540
	/**
541
	 * All custom CSS rules which are inserted directly at the bottom of the HTML <head> tag
542
	 *
543
	 * @var array
544
	 */
545
	protected $customCSS = array();
546
547
	/**
548
	 * All custom HTML markup which is added before the closing <head> tag, e.g. additional
549
	 * metatags.
550
	 *
551
	 * @var array
552
	 */
553
	protected $customHeadTags = array();
554
555
	/**
556
	 * Remembers the file paths or uniquenessIDs of all Requirements cleared through
557
	 * {@link clear()}, so that they can be restored later.
558
	 *
559
	 * @var array
560
	 */
561
	protected $disabled = array();
562
563
	/**
564
	 * The file paths (relative to docroot) or uniquenessIDs of any included requirements which
565
	 * should be blocked when executing {@link inlcudeInHTML()}. This is useful, for example,
566
	 * to block scripts included by a superclass without having to override entire functions and
567
	 * duplicate a lot of code.
568
	 *
569
	 * Use {@link unblock()} or {@link unblock_all()} to revert changes.
570
	 *
571
	 * @var array
572
	 */
573
	protected $blocked = array();
574
575
	/**
576
	 * A list of combined files registered via {@link combine_files()}. Keys are the output file
577
	 * names, values are lists of input files.
578
	 *
579
	 * @var array
580
	 */
581
	protected $combinedFiles = array();
582
583
	/**
584
	 * Use the JSMin library to minify any javascript file passed to {@link combine_files()}.
585
	 *
586
	 * @var bool
587
	 */
588
	protected $minifyCombinedJSFiles = true;
589
590
	/**
591
	 * Whether or not file headers should be written when combining files
592
	 *
593
	 * @var boolean
594
	 */
595
	protected $writeHeaderComment = true;
596
597
	/**
598
	 * Where to save combined files. By default they're placed in assets/_combinedfiles, however
599
	 * this may be an issue depending on your setup, especially for CSS files which often contain
600
	 * relative paths.
601
	 *
602
	 * @var string
603
	 */
604
	protected $combinedFilesFolder = null;
605
606
	/**
607
	 * Put all JavaScript includes at the bottom of the template before the closing <body> tag,
608
	 * rather than the default behaviour of placing them at the end of the <head> tag. This means
609
	 * script downloads won't block other HTTP requests, which can be a performance improvement.
610
	 *
611
	 * @var bool
612
	 */
613
	public $writeJavascriptToBody = true;
614
615
	/**
616
	 * Force the JavaScript to the bottom of the page, even if there's a script tag in the body already
617
	 *
618
	 * @var boolean
619
	 */
620
	protected $forceJSToBottom = false;
621
622
	/**
623
	 * Configures the default prefix for combined files.
624
	 *
625
	 * This defaults to `_combinedfiles`, and is the folder within the configured asset backend that
626
	 * combined files will be stored in. If using a backend shared with other systems, it is usually
627
	 * necessary to distinguish combined files from other assets.
628
	 *
629
	 * @config
630
	 * @var string
631
	 */
632
	private static $default_combined_files_folder = '_combinedfiles';
633
634
	/**
635
	 * Flag to include the hash in the querystring instead of the filename for combined files.
636
	 *
637
	 * By default the `<hash>` of the source files is appended to the end of the combined file
638
	 * (prior to the file extension). If combined files are versioned in source control or running
639
	 * in a distributed environment (such as one where the newest version of a file may not always be
640
	 * immediately available) then it may sometimes be necessary to disable this. When this is set to true,
641
	 * the hash will instead be appended via a querystring parameter to enable cache busting, but not in
642
	 * the filename itself. I.e. `assets/_combinedfiles/name.js?m=<hash>`
643
	 *
644
	 * @config
645
	 * @var bool
646
	 */
647
	private static $combine_hash_querystring = false;
648
649
	/**
650
	 * @var GeneratedAssetHandler
651
	 */
652
	protected $assetHandler = null;
653
654
	/**
655
	 * Gets the backend storage for generated files
656
	 *
657
	 * @return GeneratedAssetHandler
658
	 */
659
	public function getAssetHandler() {
660
		return $this->assetHandler;
661
	}
662
663
	/**
664
	 * Set a new asset handler for this backend
665
	 *
666
	 * @param GeneratedAssetHandler $handler
667
	 */
668
	public function setAssetHandler(GeneratedAssetHandler $handler) {
669
		$this->assetHandler = $handler;
670
	}
671
672
	/**
673
	 * Enable or disable the combination of CSS and JavaScript files
674
	 *
675
	 * @param bool $enable
676
	 */
677
	public function setCombinedFilesEnabled($enable) {
678
		$this->combinedFilesEnabled = (bool) $enable;
679
	}
680
681
	/**
682
	 * Check if header comments are written
683
	 *
684
	 * @return bool
685
	 */
686
	public function getWriteHeaderComment() {
687
		return $this->writeHeaderComment;
688
	}
689
690
	/**
691
	 * Flag whether header comments should be written for each combined file
692
	 *
693
	 * @param bool $write
694
	 * @return $this
695
	 */
696
	public function setWriteHeaderComment($write) {
697
		$this->writeHeaderComment = $write;
698
		return $this;
699
	}
700
701
	/**
702
	 * Set the folder to save combined files in. By default they're placed in _combinedfiles,
703
	 * however this may be an issue depending on your setup, especially for CSS files which often
704
	 * contain relative paths.
705
	 *
706
	 * This must not include any 'assets' prefix
707
	 *
708
	 * @param string $folder
709
	 */
710
	public function setCombinedFilesFolder($folder) {
711
		$this->combinedFilesFolder = $folder;
712
	}
713
714
	/**
715
	 * Retrieve the combined files folder prefix
716
	 *
717
	 * @return string
718
	 */
719
	public function getCombinedFilesFolder() {
720
		if($this->combinedFilesFolder) {
721
			return $this->combinedFilesFolder;
722
		}
723
		return Config::inst()->get(__CLASS__, 'default_combined_files_folder');
724
	}
725
726
	/**
727
	 * Set whether to add caching query params to the requests for file-based requirements.
728
	 * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
729
	 * filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
730
	 * while automatically busting this cache every time the file is changed.
731
	 *
732
	 * @param bool
733
	 */
734
	public function setSuffixRequirements($var) {
735
		$this->suffixRequirements = $var;
736
	}
737
738
	/**
739
	 * Check whether we want to suffix requirements
740
	 *
741
	 * @return bool
742
	 */
743
	public function getSuffixRequirements() {
744
		return $this->suffixRequirements;
745
	}
746
747
	/**
748
	 * Set whether you want to write the JS to the body of the page rather than at the end of the
749
	 * head tag.
750
	 *
751
	 * @param bool
752
	 * @return $this
753
	 */
754
	public function setWriteJavascriptToBody($var) {
755
		$this->writeJavascriptToBody = $var;
756
		return $this;
757
	}
758
759
	/**
760
	 * Check whether you want to write the JS to the body of the page rather than at the end of the
761
	 * head tag.
762
	 *
763
	 * @return bool
764
	 */
765
	public function getWriteJavascriptToBody() {
766
		return $this->writeJavascriptToBody;
767
	}
768
769
	/**
770
	 * Forces the JavaScript requirements to the end of the body, right before the closing tag
771
	 *
772
	 * @param bool
773
	 * @return $this
774
	 */
775
	public function setForceJSToBottom($var) {
776
		$this->forceJSToBottom = $var;
777
		return $this;
778
	}
779
780
	/**
781
	 * Check if the JavaScript requirements are written to the end of the body, right before the closing tag
782
	 *
783
	 * @return bool
784
	 */
785
	public function getForceJSToBottom() {
786
		return $this->forceJSToBottom;
787
	}
788
789
	/**
790
	 * Check if minify js files should be combined
791
	 *
792
	 * @return bool
793
	 */
794
	public function getMinifyCombinedJSFiles() {
795
		return $this->minifyCombinedJSFiles;
796
	}
797
798
	/**
799
	 * Set if combined js files should be minified
800
	 *
801
	 * @param bool $minify
802
	 * @return $this
803
	 */
804
	public function setMinifyCombinedJSFiles($minify) {
805
		$this->minifyCombinedJSFiles = $minify;
806
		return $this;
807
	}
808
809
	/**
810
	 * Register the given JavaScript file as required.
811
	 *
812
	 * @param string $file Relative to docroot
813
	 * @param array $options List of options. Available options include:
814
	 * - 'provides' : List of scripts files included in this file
815
	 * - 'async' : Boolean value to set async attribute to script tag
816
	 * - 'defer' : Boolean value to set defer attribute to script tag
817
	 */
818
	public function javascript($file, $options = array()) {
819
	    // make sure that async/defer is set if it is set once even if file is included multiple times
820
        $async = (
821
            isset($options['async']) && isset($options['async']) == true  
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
822
            || (
823
                isset($this->javascript[$file])
824
                && isset($this->javascript[$file]['async'])
825
                && $this->javascript[$file]['async'] == true
826
            )
827
        );
828
        $defer = (
829
            isset($options['defer']) && isset($options['defer']) == true
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
830
            || (
831
                isset($this->javascript[$file])
832
                && isset($this->javascript[$file]['defer'])
833
                && $this->javascript[$file]['defer'] == true
834
            )
835
        );
836
        $this->javascript[$file] = array(
837
            'async' => $async,
838
            'defer' => $defer,
839
        );
840
841
		// Record scripts included in this file
842
		if(isset($options['provides'])) {
843
			$this->providedJavascript[$file] = array_values($options['provides']);
844
	    }
845
	
846
	}
847
848
	/**
849
	 * Remove a javascript requirement
850
	 *
851
	 * @param string $file
852
	 */
853
	protected function unsetJavascript($file) {
854
		unset($this->javascript[$file]);
855
	}
856
857
	/**
858
	 * Gets all scripts that are already provided by prior scripts.
859
	 * This follows these rules:
860
	 *  - Files will not be considered provided if they are separately
861
	 *    included prior to the providing file.
862
	 *  - Providing files can be blocked, and don't provide anything
863
	 *  - Provided files can't be blocked (you need to block the provider)
864
	 *  - If a combined file includes files that are provided by prior
865
	 *    scripts, then these should be excluded from the combined file.
866
	 *  - If a combined file includes files that are provided by later
867
	 *    scripts, then these files should be included in the combined
868
	 *    file, but we can't block the later script either (possible double
869
	 *    up of file).
870
	 *
871
	 * @return array Array of provided files (map of $path => $path)
872
	 */
873
	public function getProvidedScripts() {
874
		$providedScripts = array();
875
		$includedScripts = array();
876
		foreach($this->javascript as $script => $options) {
877
			// Ignore scripts that are explicitly blocked
878
			if(isset($this->blocked[$script])) {
879
				continue;
880
			}
881
			// At this point, the file is included.
882
			// This might also be combined at this point, potentially.
883
			$includedScripts[$script] = true;
884
885
			// Record any files this provides, EXCEPT those already included by now
886
			if(isset($this->providedJavascript[$script])) {
887
				foreach($this->providedJavascript[$script] as $provided) {
888
					if(!isset($includedScripts[$provided])) {
889
						$providedScripts[$provided] = $provided;
890
					}
891
				}
892
			}
893
		}
894
		return $providedScripts;
895
	}
896
897
	/**
898
	 * Returns an array of required JavaScript, excluding blocked
899
	 * and duplicates of provided files.
900
	 *
901
	 * @return array
902
	 */
903
	public function getJavascript() {
904
		return array_diff_key(
905
			$this->javascript,
906
			$this->getBlocked(),
907
			$this->getProvidedScripts()
908
		);
909
	}
910
911
	/**
912
	 * Gets all javascript, including blocked files. Unwraps the array into a non-associative list
913
	 *
914
	 * @return array Indexed array of javascript files
915
	 */
916
	protected function getAllJavascript() {
917
		return $this->javascript;
918
	}
919
920
	/**
921
	 * Register the given JavaScript code into the list of requirements
922
	 *
923
	 * @param string     $script       The script content as a string (without enclosing <script> tag)
924
	 * @param string $uniquenessID A unique ID that ensures a piece of code is only added once
925
	 */
926
	public function customScript($script, $uniquenessID = null) {
927
		if($uniquenessID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniquenessID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
928
			$this->customScript[$uniquenessID] = $script;
929
		} else {
930
			$this->customScript[] = $script;
931
		}
932
	}
933
934
	/**
935
	 * Return all registered custom scripts
936
	 *
937
	 * @return array
938
	 */
939
	public function getCustomScripts() {
940
		return array_diff_key($this->customScript, $this->blocked);
941
	}
942
943
	/**
944
	 * Register the given CSS styles into the list of requirements
945
	 *
946
	 * @param string     $script       CSS selectors as a string (without enclosing <style> tag)
947
	 * @param string $uniquenessID A unique ID that ensures a piece of code is only added once
948
	 */
949
	public function customCSS($script, $uniquenessID = null) {
950
		if($uniquenessID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniquenessID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
951
			$this->customCSS[$uniquenessID] = $script;
952
		} else {
953
			$this->customCSS[] = $script;
954
		}
955
	}
956
957
	/**
958
	 * Return all registered custom CSS
959
	 *
960
	 * @return array
961
	 */
962
	public function getCustomCSS() {
963
		return array_diff_key($this->customCSS, $this->blocked);
964
	}
965
966
	/**
967
	 * Add the following custom HTML code to the <head> section of the page
968
	 *
969
	 * @param string     $html         Custom HTML code
970
	 * @param string $uniquenessID A unique ID that ensures a piece of code is only added once
971
	 */
972
	public function insertHeadTags($html, $uniquenessID = null) {
973
		if($uniquenessID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniquenessID of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
974
			$this->customHeadTags[$uniquenessID] = $html;
975
		} else {
976
			$this->customHeadTags[] = $html;
977
		}
978
	}
979
980
	/**
981
	 * Return all custom head tags
982
	 *
983
	 * @return array
984
	 */
985
	public function getCustomHeadTags() {
986
		return array_diff_key($this->customHeadTags, $this->blocked);
987
	}
988
989
	/**
990
	 * Include the content of the given JavaScript file in the list of requirements. Dollar-sign
991
	 * variables will be interpolated with values from $vars similar to a .ss template.
992
	 *
993
	 * @param string         $file         The template file to load, relative to docroot
994
	 * @param string[] $vars The array of variables to interpolate.
995
	 * @param string $uniquenessID A unique ID that ensures a piece of code is only added once
996
	 */
997
	public function javascriptTemplate($file, $vars, $uniquenessID = null) {
998
		$script = file_get_contents(Director::getAbsFile($file));
999
		$search = array();
1000
		$replace = array();
1001
1002
		if($vars) foreach($vars as $k => $v) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $vars of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1003
			$search[] = '$' . $k;
1004
			$replace[] = str_replace("\\'","'", Convert::raw2js($v));
1005
		}
1006
1007
		$script = str_replace($search, $replace, $script);
1008
		$this->customScript($script, $uniquenessID);
1009
	}
1010
1011
	/**
1012
	 * Register the given stylesheet into the list of requirements.
1013
	 *
1014
	 * @param string $file  The CSS file to load, relative to site root
1015
	 * @param string $media Comma-separated list of media types to use in the link tag
1016
	 *                      (e.g. 'screen,projector')
1017
	 */
1018
	public function css($file, $media = null) {
1019
		$this->css[$file] = array(
1020
			"media" => $media
1021
		);
1022
	}
1023
1024
	/**
1025
	 * Remove a css requirement
1026
	 *
1027
	 * @param string $file
1028
	 */
1029
	protected function unsetCSS($file) {
1030
		unset($this->css[$file]);
1031
	}
1032
1033
	/**
1034
	 * Get the list of registered CSS file requirements, excluding blocked files
1035
	 *
1036
	 * @return array Associative array of file to spec
1037
	 */
1038
	public function getCSS() {
1039
		return array_diff_key($this->css, $this->blocked);
1040
	}
1041
1042
	/**
1043
	 * Gets all CSS files requirements, including blocked
1044
	 *
1045
	 * @return array Associative array of file to spec
1046
	 */
1047
	protected function getAllCSS() {
1048
		return $this->css;
1049
	}
1050
1051
	/**
1052
	 * Gets the list of all blocked files
1053
	 *
1054
	 * @return array
1055
	 */
1056
	public function getBlocked() {
1057
		return $this->blocked;
1058
	}
1059
1060
	/**
1061
	 * Clear either a single or all requirements
1062
	 *
1063
	 * Caution: Clearing single rules added via customCSS and customScript only works if you
1064
	 * originally specified a $uniquenessID.
1065
	 *
1066
	 * @param string|int $fileOrID
1067
	 */
1068
	public function clear($fileOrID = null) {
1069
		if($fileOrID) {
1070
			foreach(array('javascript','css', 'customScript', 'customCSS', 'customHeadTags') as $type) {
1071
				if(isset($this->{$type}[$fileOrID])) {
1072
					$this->disabled[$type][$fileOrID] = $this->{$type}[$fileOrID];
1073
					unset($this->{$type}[$fileOrID]);
1074
				}
1075
			}
1076
		} else {
1077
			$this->disabled['javascript'] = $this->javascript;
1078
			$this->disabled['css'] = $this->css;
1079
			$this->disabled['customScript'] = $this->customScript;
1080
			$this->disabled['customCSS'] = $this->customCSS;
1081
			$this->disabled['customHeadTags'] = $this->customHeadTags;
1082
1083
			$this->javascript = array();
1084
			$this->css = array();
1085
			$this->customScript = array();
1086
			$this->customCSS = array();
1087
			$this->customHeadTags = array();
1088
		}
1089
	}
1090
1091
	/**
1092
	 * Restore requirements cleared by call to Requirements::clear
1093
	 */
1094
	public function restore() {
1095
		$this->javascript = $this->disabled['javascript'];
1096
		$this->css = $this->disabled['css'];
1097
		$this->customScript = $this->disabled['customScript'];
1098
		$this->customCSS = $this->disabled['customCSS'];
1099
		$this->customHeadTags = $this->disabled['customHeadTags'];
1100
	}
1101
1102
	/**
1103
	 * Block inclusion of a specific file
1104
	 *
1105
	 * The difference between this and {@link clear} is that the calling order does not matter;
1106
	 * {@link clear} must be called after the initial registration, whereas {@link block} can be
1107
	 * used in advance. This is useful, for example, to block scripts included by a superclass
1108
	 * without having to override entire functions and duplicate a lot of code.
1109
	 *
1110
	 * Note that blocking should be used sparingly because it's hard to trace where an file is
1111
	 * being blocked from.
1112
	 *
1113
	 * @param string|int $fileOrID
1114
	 */
1115
	public function block($fileOrID) {
1116
		$this->blocked[$fileOrID] = $fileOrID;
1117
	}
1118
1119
	/**
1120
	 * Remove an item from the block list
1121
	 *
1122
	 * @param string|int $fileOrID
1123
	 */
1124
	public function unblock($fileOrID) {
1125
		unset($this->blocked[$fileOrID]);
1126
	}
1127
1128
	/**
1129
	 * Removes all items from the block list
1130
	 */
1131
	public function unblockAll() {
1132
		$this->blocked = array();
1133
	}
1134
1135
	/**
1136
	 * Update the given HTML content with the appropriate include tags for the registered
1137
	 * requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
1138
	 * including a head and body tag.
1139
	 *
1140
	 * @param string $content      HTML content that has already been parsed from the $templateFile
1141
	 *                             through {@link SSViewer}
1142
	 * @return string HTML content augmented with the requirements tags
1143
	 */
1144
	public function includeInHTML($content) {
1145
		if(func_num_args() > 1) {
1146
			Deprecation::notice(
1147
				'5.0',
1148
				'$templateFile argument is deprecated. includeInHTML takes a sole $content parameter now.'
1149
			);
1150
			$content = func_get_arg(1);
1151
		}
1152
1153
		// Skip if content isn't injectable, or there is nothing to inject
1154
		$tagsAvailable = preg_match('#</head\b#', $content);
1155
		$hasFiles = $this->css || $this->javascript || $this->customCSS || $this->customScript || $this->customHeadTags;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->css of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->javascript of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->customCSS of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->customScript of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->customHeadTags of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1156
		if(!$tagsAvailable || !$hasFiles) {
1157
			return $content;
1158
		}
1159
		$requirements = '';
1160
		$jsRequirements = '';
1161
1162
		// Combine files - updates $this->javascript and $this->css
1163
		$this->processCombinedFiles();
1164
1165
		foreach($this->getJavascript() as $file => $attributes) {
1166
		    $async = (isset($attributes['async']) && $attributes['async'] == true) ? " async" : "";
1167
		    $defer = (isset($attributes['defer']) && $attributes['defer'] == true) ? " defer" : "";
1168
			$path = Convert::raw2att($this->pathForFile($file));
0 ignored issues
show
Bug introduced by
It seems like $this->pathForFile($file) targeting Requirements_Backend::pathForFile() can also be of type boolean; however, Convert::raw2att() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1169
			if($path) {
1170
				$jsRequirements .= "<script type=\"application/javascript\" src=\"$path\"{$async}{$defer}></script>";
1171
			}
1172
		}
1173
1174
		// Add all inline JavaScript *after* including external files they might rely on
1175
		foreach($this->getCustomScripts() as $script) {
1176
			$jsRequirements .= "<script type=\"application/javascript\">//<![CDATA[\n";
1177
			$jsRequirements .= "$script\n";
1178
			$jsRequirements .= "//]]></script>";
1179
		}
1180
1181
		foreach($this->getCSS() as $file => $params) {
1182
			$path = Convert::raw2att($this->pathForFile($file));
0 ignored issues
show
Bug introduced by
It seems like $this->pathForFile($file) targeting Requirements_Backend::pathForFile() can also be of type boolean; however, Convert::raw2att() does only seem to accept array|string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1183
			if($path) {
1184
				$media = (isset($params['media']) && !empty($params['media']))
1185
					? " media=\"{$params['media']}\"" : "";
1186
				$requirements .= "<link rel=\"stylesheet\" type=\"text/css\" {$media} href=\"$path\" />\n";
1187
			}
1188
		}
1189
1190
		foreach($this->getCustomCSS() as $css) {
1191
			$requirements .= "<style type=\"text/css\">\n$css\n</style>\n";
1192
		}
1193
1194
		foreach($this->getCustomHeadTags() as $customHeadTag) {
1195
			$requirements .= "$customHeadTag\n";
1196
		}
1197
1198
		// Inject CSS  into body
1199
		$content = $this->insertTagsIntoHead($requirements, $content);
1200
1201
		// Inject scripts
1202
		if ($this->getForceJSToBottom()) {
1203
			$content = $this->insertScriptsAtBottom($jsRequirements, $content);
1204
		} elseif($this->getWriteJavascriptToBody()) {
1205
			$content = $this->insertScriptsIntoBody($jsRequirements, $content);
1206
		} else {
1207
			$content = $this->insertTagsIntoHead($jsRequirements, $content);
1208
		}
1209
		return $content;
1210
	}
1211
1212
	/**
1213
	 * Given a block of HTML, insert the given scripts at the bottom before
1214
	 * the closing </body> tag
1215
	 *
1216
	 * @param string $jsRequirements String containing one or more javascript <script /> tags
1217
	 * @param string $content HTML body
1218
	 * @return string Merged HTML
1219
	 */
1220
	protected function insertScriptsAtBottom($jsRequirements, $content) {
1221
		// Forcefully put the scripts at the bottom of the body instead of before the first
1222
		// script tag.
1223
		$content = preg_replace(
1224
			'/(<\/body[^>]*>)/i',
1225
			$this->escapeReplacement($jsRequirements) . '\\1',
1226
			$content
1227
		);
1228
		return $content;
1229
	}
1230
1231
	/**
1232
	 * Given a block of HTML, insert the given scripts inside the <body></body>
1233
	 *
1234
	 * @param string $jsRequirements String containing one or more javascript <script /> tags
1235
	 * @param string $content HTML body
1236
	 * @return string Merged HTML
1237
	 */
1238
	protected function insertScriptsIntoBody($jsRequirements, $content) {
1239
		// If your template already has script tags in the body, then we try to put our script
1240
		// tags just before those. Otherwise, we put it at the bottom.
1241
		$bodyTagPosition = stripos($content, '<body');
1242
		$scriptTagPosition = stripos($content, '<script', $bodyTagPosition);
1243
1244
		$commentTags = array();
1245
		$canWriteToBody = ($scriptTagPosition !== false)
1246
			&&
1247
			// Check that the script tag is not inside a html comment tag
1248
			!(
1249
				preg_match('/.*(?|(<!--)|(-->))/U', $content, $commentTags, 0, $scriptTagPosition)
1250
				&&
1251
				$commentTags[1] == '-->'
1252
			);
1253
1254
		if($canWriteToBody) {
1255
			// Insert content before existing script tags
1256
			$content = substr($content, 0, $scriptTagPosition)
1257
				. $jsRequirements
1258
				. substr($content, $scriptTagPosition);
1259
		} else {
1260
			// Insert content at bottom of page otherwise
1261
			$content = $this->insertScriptsAtBottom($jsRequirements, $content);
1262
		}
1263
1264
		return $content;
1265
	}
1266
1267
	/**
1268
	 * Given a block of HTML, insert the given code inside the <head></head> block
1269
	 *
1270
	 * @param string $jsRequirements String containing one or more html tags
1271
	 * @param string $content HTML body
1272
	 * @return string Merged HTML
1273
	 */
1274
	protected function insertTagsIntoHead($jsRequirements, $content) {
1275
		$content = preg_replace(
1276
			'/(<\/head>)/i',
1277
			$this->escapeReplacement($jsRequirements) . '\\1',
1278
			$content
1279
		);
1280
		return $content;
1281
	}
1282
1283
	/**
1284
	 * Safely escape a literal string for use in preg_replace replacement
1285
	 *
1286
	 * @param string $replacement
1287
	 * @return string
1288
	 */
1289
	protected function escapeReplacement($replacement) {
1290
		return addcslashes($replacement, '\\$');
1291
	}
1292
1293
	/**
1294
	 * Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
1295
	 * HTTP Response
1296
	 *
1297
	 * @param SS_HTTPResponse $response
1298
	 */
1299
	public function includeInResponse(SS_HTTPResponse $response) {
1300
		$this->processCombinedFiles();
1301
		$jsRequirements = array();
1302
		$cssRequirements = array();
1303
1304
		foreach($this->getJavascript() as $file => $attributes) {
1305
			$path = $this->pathForFile($file);
1306
			if($path) {
1307
				$jsRequirements[] = str_replace(',', '%2C', $path);
1308
			}
1309
		}
1310
1311
		if(count($jsRequirements)) {
1312
			$response->addHeader('X-Include-JS', implode(',', $jsRequirements));
1313
		}
1314
1315
		foreach($this->getCSS() as $file => $params) {
1316
			$path = $this->pathForFile($file);
1317
			if($path) {
1318
				$path = str_replace(',', '%2C', $path);
1319
				$cssRequirements[] = isset($params['media']) ? "$path:##:$params[media]" : $path;
1320
			}
1321
		}
1322
1323
		if(count($cssRequirements)) {
1324
			$response->addHeader('X-Include-CSS', implode(',', $cssRequirements));
1325
		}
1326
	}
1327
1328
	/**
1329
	 * Add i18n files from the given javascript directory. SilverStripe expects that the given
1330
	 * directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
1331
	 * etc.
1332
	 *
1333
	 * @param string $langDir  The JavaScript lang directory, relative to the site root, e.g.,
1334
	 *                         'framework/javascript/lang'
1335
	 * @param bool   $return   Return all relative file paths rather than including them in
1336
	 *                         requirements
1337
	 * @param bool   $langOnly Only include language files, not the base libraries
1338
	 *
1339
	 * @return array|null All relative files if $return is true, or null otherwise
1340
	 */
1341
	public function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
1342
		$files = array();
1343
		$base = Director::baseFolder() . '/';
1344
		if(i18n::config()->js_i18n) {
1345
			// Include i18n.js even if no languages are found.  The fact that
1346
			// add_i18n_javascript() was called indicates that the methods in
1347
			// here are needed.
1348
			if(!$langOnly) $files[] = FRAMEWORK_DIR . '/client/dist/js/i18n.js';
1349
1350
			if(substr($langDir,-1) != '/') $langDir .= '/';
1351
1352
			$candidates = array(
1353
				'en.js',
1354
				'en_US.js',
1355
				i18n::get_lang_from_locale(i18n::config()->default_locale) . '.js',
1356
				i18n::config()->default_locale . '.js',
1357
				i18n::get_lang_from_locale(i18n::get_locale()) . '.js',
1358
				i18n::get_locale() . '.js',
1359
			);
1360
			foreach($candidates as $candidate) {
1361
				if(file_exists($base . DIRECTORY_SEPARATOR . $langDir . $candidate)) {
1362
					$files[] = $langDir . $candidate;
1363
				}
1364
			}
1365
		} else {
1366
			// Stub i18n implementation for when i18n is disabled.
1367
			if(!$langOnly) {
1368
				$files[] = FRAMEWORK_DIR . '/client/dist/js/i18nx.js';
1369
			}
1370
		}
1371
1372
		if($return) {
1373
			return $files;
1374
		} else {
1375
			foreach($files as $file) {
1376
				$this->javascript($file);
1377
			}
1378
			return null;
1379
		}
1380
	}
1381
1382
	/**
1383
	 * Finds the path for specified file
1384
	 *
1385
	 * @param string $fileOrUrl
1386
	 * @return string|bool
1387
	 */
1388
	protected function pathForFile($fileOrUrl) {
1389
		// Since combined urls could be root relative, treat them as urls here.
1390
		if(preg_match('{^(//)|(http[s]?:)}', $fileOrUrl) || Director::is_root_relative_url($fileOrUrl)) {
1391
			return $fileOrUrl;
1392
		} elseif(Director::fileExists($fileOrUrl)) {
1393
			$filePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $fileOrUrl);
1394
			$prefix = Director::baseURL();
1395
			$mtimesuffix = "";
1396
			$suffix = '';
1397
			if($this->getSuffixRequirements()) {
1398
				$mtimesuffix = "?m=" . filemtime($filePath);
1399
				$suffix = '&';
1400
			}
1401
			if(strpos($fileOrUrl, '?') !== false) {
1402
				if (strlen($suffix) == 0) {
1403
					$suffix = '?';
1404
				}
1405
				$suffix .= substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
1406
				$fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
1407
			} else {
1408
				$suffix = '';
1409
			}
1410
			return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
1411
		} else {
1412
			throw new InvalidArgumentException("File {$fileOrUrl} does not exist");
1413
		}
1414
	}
1415
1416
	/**
1417
	 * Concatenate several css or javascript files into a single dynamically generated file. This
1418
	 * increases performance by fewer HTTP requests.
1419
	 *
1420
	 * The combined file is regenerated based on every file modification time. Optionally a
1421
	 * rebuild can be triggered by appending ?flush=1 to the URL.
1422
	 *
1423
	 * All combined files will have a comment on the start of each concatenated file denoting their
1424
	 * original position.
1425
	 *
1426
	 * CAUTION: You're responsible for ensuring that the load order for combined files is
1427
	 * retained - otherwise combining JavaScript files can lead to functional errors in the
1428
	 * JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
1429
	 * only include each file once across all includes and combinations in a single page load.
1430
	 *
1431
	 * CAUTION: Combining CSS Files discards any "media" information.
1432
	 *
1433
	 * Example for combined JavaScript:
1434
	 * <code>
1435
	 * Requirements::combine_files(
1436
	 *    'foobar.js',
1437
	 *    array(
1438
	 *        'mysite/javascript/foo.js',
1439
	 *        'mysite/javascript/bar.js',
1440
	 *    ),
1441
	 *    array(
1442
	 *        'async' => true,
1443
	 *        'defer' => true,
1444
	 *    )
1445
	 * );
1446
	 * </code>
1447
	 *
1448
	 * Example for combined CSS:
1449
	 * <code>
1450
	 * Requirements::combine_files(
1451
	 *    'foobar.css',
1452
	 *    array(
1453
	 *        'mysite/javascript/foo.css',
1454
	 *        'mysite/javascript/bar.css',
1455
	 *    ),
1456
	 *    array(
1457
	 *        'media' => 'print',
1458
	 *    )
1459
	 * );
1460
	 * </code>
1461
	 *
1462
	 * @param string $combinedFileName Filename of the combined file relative to docroot
1463
	 * @param array  $files            Array of filenames relative to docroot
1464
	 * @param array  $options          Array of options for combining files. Available options are:
1465
	 * - 'media' : If including CSS Files, you can specify a media type
1466
	 * - 'async' : If including JavaScript Files, boolean value to set async attribute to script tag
1467
	 * - 'defer' : If including JavaScript Files, boolean value to set defer attribute to script tag
1468
	 */
1469
	public function combineFiles($combinedFileName, $files, $options = array()) {
1470
	    if(is_string($options))  {
1471
            Deprecation::notice('4.0', 'Parameter media is deprecated. Use options array instead.');
1472
            $options = array('media' => $options);
1473
	    }
1474
		// Skip this combined files if already included
1475
		if(isset($this->combinedFiles[$combinedFileName])) {
1476
			return;
1477
		}
1478
1479
		// Add all files to necessary type list
1480
		$paths = array();
1481
		$combinedType = null;
1482
		foreach($files as $file) {
1483
			// Get file details
1484
			list($path, $type) = $this->parseCombinedFile($file);
1485
			if($type === 'javascript') {
1486
				$type = 'js';
1487
		    }
1488
			if($combinedType && $type && $combinedType !== $type) {
1489
				throw new InvalidArgumentException(
1490
					"Cannot mix js and css files in same combined file {$combinedFileName}"
1491
				);
1492
			}
1493
			switch($type) {
1494
				case 'css':
1495
					$this->css($path, (isset($options['media']) ? $options['media'] : null));
1496
					break;
1497
				case 'js':
1498
					$this->javascript($path, $options);
1499
					break;
1500
				default:
1501
					throw new InvalidArgumentException("Invalid combined file type: {$type}");
1502
			}
1503
			$combinedType = $type;
1504
			$paths[] = $path;
1505
		}
1506
1507
		// Duplicate check
1508
		foreach($this->combinedFiles as $existingCombinedFilename => $combinedItem) {
1509
			$existingFiles = $combinedItem['files'];
1510
			$duplicates = array_intersect($existingFiles, $paths);
1511
			if($duplicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $duplicates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1512
				throw new InvalidArgumentException(sprintf(
1513
					"Requirements_Backend::combine_files(): Already included file(s) %s in combined file '%s'",
1514
					implode(',', $duplicates),
1515
					$existingCombinedFilename
1516
				));
1517
			}
1518
		}
1519
1520
		$this->combinedFiles[$combinedFileName] = array(
1521
			'files' => $paths,
1522
			'type' => $combinedType,
1523
			'options' => $options,
1524
		);
1525
	}
1526
1527
	/**
1528
	 * Return path and type of given combined file
1529
	 *
1530
	 * @param string|array $file Either a file path, or an array spec
1531
	 * @return array array with two elements, path and type of file
1532
	 */
1533
	protected function parseCombinedFile($file) {
1534
		// Array with path and type keys
1535
		if(is_array($file) && isset($file['path']) && isset($file['type'])) {
1536
			return array($file['path'], $file['type']);
1537
				}
1538
1539
		// Extract value from indexed array
1540
		if(is_array($file)) {
1541
			$path = array_shift($file);
1542
1543
			// See if there's a type specifier
1544
			if($file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1545
				$type = array_shift($file);
1546
				return array($path, $type);
1547
			}
1548
1549
			// Otherwise convent to string
1550
			$file = $path;
1551
		}
1552
1553
		$type = File::get_file_extension($file);
1554
		return array($file, $type);
1555
	}
1556
1557
	/**
1558
	 * Return all combined files; keys are the combined file names, values are lists of
1559
	 * associative arrays with 'files', 'type', and 'media' keys for details about this
1560
	 * combined file.
1561
	 *
1562
	 * @return array
1563
	 */
1564
	public function getCombinedFiles() {
1565
		return array_diff_key($this->combinedFiles, $this->blocked);
1566
	}
1567
1568
	/**
1569
	 * Includes all combined files, including blocked ones
1570
	 *
1571
	 * @return array
1572
	 */
1573
	protected function getAllCombinedFiles() {
1574
		return $this->combinedFiles;
1575
	}
1576
1577
	/**
1578
	 * Clears all combined files
1579
	 */
1580
	public function deleteAllCombinedFiles() {
1581
		$combinedFolder = $this->getCombinedFilesFolder();
1582
		if($combinedFolder) {
1583
			$this->getAssetHandler()->removeContent($combinedFolder);
1584
		}
1585
	}
1586
1587
	/**
1588
	 * Clear all registered CSS and JavaScript file combinations
1589
	 */
1590
	public function clearCombinedFiles() {
1591
		$this->combinedFiles = array();
1592
	}
1593
1594
	/**
1595
	 * Do the heavy lifting involved in combining the combined files.
1596
	 */
1597
	public function processCombinedFiles() {
1598
		// Check if combining is enabled
1599
		if(!$this->getCombinedFilesEnabled()) {
1600
			return;
1601
		}
1602
1603
		// Before scripts are modified, detect files that are provided by preceding ones
1604
		$providedScripts = $this->getProvidedScripts();
1605
1606
		// Process each combined files
1607
		foreach($this->getAllCombinedFiles() as $combinedFile => $combinedItem) {
1608
			$fileList = $combinedItem['files'];
1609
			$type = $combinedItem['type'];
1610
			$options = $combinedItem['options'];
1611
1612
			// Generate this file, unless blocked
1613
			$combinedURL = null;
1614
			if(!isset($this->blocked[$combinedFile])) {
1615
				// Filter files for blocked / provided
1616
				$filteredFileList = array_diff(
1617
					$fileList,
1618
					$this->getBlocked(),
1619
					$providedScripts
1620
				);
1621
				$combinedURL = $this->getCombinedFileURL($combinedFile, $filteredFileList, $type);
1622
			}
1623
1624
			// Replace all existing files, injecting the combined file at the position of the first item
1625
			// in order to preserve inclusion order.
1626
			// Note that we iterate across blocked files in order to get the correct order, and validate
1627
			// that the file is included in the correct location (regardless of which files are blocked).
1628
			$included = false;
1629
			switch($type) {
1630
				case 'css': {
0 ignored issues
show
Coding Style introduced by
CASE statements must be defined using a colon

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

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

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

Loading history...
1631
					$newCSS = array(); // Assoc array of css file => spec
1632
					foreach($this->getAllCSS() as $css => $spec) {
1633
						if(!in_array($css, $fileList)) {
1634
							$newCSS[$css] = $spec;
1635
						} elseif(!$included && $combinedURL) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $combinedURL of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1636
							$newCSS[$combinedURL] = array('media' => (isset($options['media']) ? $options['media'] : null));
1637
							$included = true;
1638
						}
1639
						// If already included, or otherwise blocked, then don't add into CSS
1640
					}
1641
					$this->css = $newCSS;
1642
					break;
1643
				}
1644
				case 'js': {
0 ignored issues
show
Coding Style introduced by
CASE statements must be defined using a colon

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

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

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

Loading history...
1645
					// Assoc array of file => attributes
1646
					$newJS = array();
1647
					foreach($this->getAllJavascript() as $script => $attributes) {
1648
						if(!in_array($script, $fileList)) {
1649
							$newJS[$script] = $attributes;
1650
						} elseif(!$included && $combinedURL) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $combinedURL of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1651
							$newJS[$combinedURL] = $options;
1652
							$included = true;
1653
						}
1654
						// If already included, or otherwise blocked, then don't add into scripts
1655
					}
1656
					$this->javascript = $newJS;
1657
					break;
1658
				}
1659
			}
1660
		}
1661
	}
1662
1663
	/**
1664
	 * Given a set of files, combine them (as necessary) and return the url
1665
	 *
1666
	 * @param string $combinedFile Filename for this combined file
1667
	 * @param array $fileList List of files to combine
1668
	 * @param string $type Either 'js' or 'css'
1669
	 * @return string|null URL to this resource, if there are files to combine
1670
	 */
1671
	protected function getCombinedFileURL($combinedFile, $fileList, $type) {
1672
		// Skip empty lists
1673
		if(empty($fileList)) {
1674
			return null;
1675
		}
1676
1677
		// Generate path (Filename)
1678
		$hashQuerystring = Config::inst()->get(static::class, 'combine_hash_querystring');
1679
		if(!$hashQuerystring) {
1680
			$combinedFile = $this->hashedCombinedFilename($combinedFile, $fileList);
1681
			}
1682
		$combinedFileID =  File::join_paths($this->getCombinedFilesFolder(), $combinedFile);
1683
1684
		// Send file combination request to the backend, with an optional callback to perform regeneration
1685
		$minify = $this->getMinifyCombinedJSFiles();
1686
		$combinedURL = $this
1687
			->getAssetHandler()
1688
			->getContentURL(
1689
				$combinedFileID,
1690
				function() use ($fileList, $minify, $type) {
1691
					// Physically combine all file content
1692
					$combinedData = '';
1693
					$base = Director::baseFolder() . '/';
1694
					$minifier = Injector::inst()->get('Requirements_Minifier');
1695
					foreach($fileList as $file) {
1696
						$fileContent = file_get_contents($base . $file);
1697
						// Use configured minifier
1698
						if($minify) {
1699
							$fileContent = $minifier->minify($fileContent, $type, $file);
1700
			}
1701
1702
						if ($this->writeHeaderComment) {
1703
							// Write a header comment for each file for easier identification and debugging.
1704
							$combinedData .= "/****** FILE: $file *****/\n";
1705
					}
1706
						$combinedData .= $fileContent . "\n";
1707
				}
1708
					return $combinedData;
1709
			}
1710
			);
1711
1712
		// If the name isn't hashed, we will need to append the querystring m= parameter instead
1713
		// Since url won't be automatically suffixed, add it in here
1714
		if($hashQuerystring && $this->getSuffixRequirements()) {
1715
			$hash = $this->hashOfFiles($fileList);
1716
			$q = stripos($combinedURL, '?') === false ? '?' : '&';
1717
			$combinedURL .= "{$q}m={$hash}";
1718
		}
1719
1720
		return $combinedURL;
1721
	}
1722
1723
	/**
1724
	 * Given a filename and list of files, generate a new filename unique to these files
1725
	 *
1726
	 * @param string $combinedFile
1727
	 * @param array $fileList
1728
	 * @return string
1729
	 */
1730
	protected function hashedCombinedFilename($combinedFile, $fileList) {
1731
		$name = pathinfo($combinedFile, PATHINFO_FILENAME);
1732
		$hash = $this->hashOfFiles($fileList);
1733
		$extension = File::get_file_extension($combinedFile);
1734
		return $name . '-' . substr($hash, 0, 7) . '.' . $extension;
1735
	}
1736
1737
	/**
1738
	 * Check if combined files are enabled
1739
	 *
1740
	 * @return bool
1741
	 */
1742
	public function getCombinedFilesEnabled() {
0 ignored issues
show
Coding Style introduced by
getCombinedFilesEnabled uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1743
		if(!$this->combinedFilesEnabled) {
1744
			return false;
1745
		}
1746
1747
		// Tests should be combined
1748
		if(class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
1749
			return true;
1750
		}
1751
1752
		// Check if specified via querystring
1753
		if(isset($_REQUEST['combine'])) {
1754
			return true;
1755
		}
1756
1757
		// Non-dev sites are always combined
1758
		if(!Director::isDev()) {
1759
			return true;
1760
		}
1761
1762
		// Fallback to default
1763
		return Config::inst()->get(__CLASS__, 'combine_in_dev');
1764
	}
1765
1766
	/**
1767
	 * For a given filelist, determine some discriminating value to determine if
1768
	 * any of these files have changed.
1769
	 *
1770
	 * @param array $fileList List of files
1771
	 * @return string SHA1 bashed file hash
1772
	 */
1773
	protected function hashOfFiles($fileList) {
1774
		// Get hash based on hash of each file
1775
		$base = Director::baseFolder() . '/';
1776
		$hash = '';
1777
		foreach($fileList as $file) {
1778
			if(file_exists($base . $file)) {
1779
				$hash .= sha1_file($base . $file);
1780
			} else {
1781
				throw new InvalidArgumentException("Combined file {$file} does not exist");
1782
			}
1783
		}
1784
		return sha1($hash);
1785
	}
1786
1787
	/**
1788
	 * Registers the given themeable stylesheet as required.
1789
	 *
1790
	 * A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
1791
	 * and it that doesn't exist and the module parameter is set then a CSS file with that name in
1792
	 * the module is used.
1793
	 *
1794
	 * @param string $name   The name of the file - eg '/css/File.css' would have the name 'File'
1795
	 * @param string $module The module to fall back to if the css file does not exist in the
1796
	 *                       current theme.
1797
	 * @param string $media  Comma-separated list of media types to use in the link tag
1798
	 *                       (e.g. 'screen,projector')
1799
	 */
1800
	public function themedCSS($name, $module = null, $media = null) {
1801
		$theme = SSViewer::get_theme_folder();
1802
		$project = project();
1803
		$absbase = BASE_PATH . DIRECTORY_SEPARATOR;
1804
		$abstheme = $absbase . $theme;
1805
		$absproject = $absbase . $project;
1806
		$css = "/css/$name.css";
1807
1808
		if(file_exists($absproject . $css)) {
1809
			$this->css($project . $css, $media);
1810
		} elseif($module && file_exists($abstheme . '_' . $module.$css)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $module of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1811
			$this->css($theme . '_' . $module . $css, $media);
1812
		} elseif(file_exists($abstheme . $css)) {
1813
			$this->css($theme . $css, $media);
1814
		} elseif($module) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $module of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1815
			$this->css($module . $css, $media);
1816
		}
1817
	}
1818
1819
	/**
1820
	 * Output debugging information.
1821
	 */
1822
	public function debug() {
1823
		Debug::show($this->javascript);
1824
		Debug::show($this->css);
1825
		Debug::show($this->customCSS);
1826
		Debug::show($this->customScript);
1827
		Debug::show($this->customHeadTags);
1828
		Debug::show($this->combinedFiles);
1829
	}
1830
1831
}
1832
1833
/**
1834
 * Provides an abstract interface for minifying content
1835
 */
1836
interface Requirements_Minifier {
1837
1838
	/**
1839
	 * Minify the given content
1840
	 *
1841
	 * @param string $content
1842
	 * @param string $type Either js or css
1843
	 * @param string $filename Name of file to display in case of error
1844
	 * @return string minified content
1845
	 */
1846
	public function minify($content, $type, $filename);
1847
}
1848