array(provided filepaths) */ protected $providedJavascript = []; /** * Paths to all required CSS files relative to the docroot. * * @var array */ protected $css = []; /** * All custom javascript code that is inserted into the page's HTML * * @var array */ protected $customScript = []; /** * All custom CSS rules which are inserted directly at the bottom of the HTML `` tag * * @var array */ protected $customCSS = []; /** * All custom HTML markup which is added before the closing `` tag, e.g. additional * metatags. * * @var array */ protected $customHeadTags = []; /** * Remembers the file paths or uniquenessIDs of all Requirements cleared through * {@link clear()}, so that they can be restored later. * * @var array */ protected $disabled = []; /** * The file paths (relative to docroot) or uniquenessIDs of any included requirements which * should be blocked when executing {@link inlcudeInHTML()}. This is useful, for example, * to block scripts included by a superclass without having to override entire functions and * duplicate a lot of code. * * Use {@link unblock()} or {@link unblock_all()} to revert changes. * * @var array */ protected $blocked = []; /** * A list of combined files registered via {@link combine_files()}. Keys are the output file * names, values are lists of input files. * * @var array */ protected $combinedFiles = []; /** * Use the injected minification service to minify any javascript file passed to {@link combine_files()}. * * @var bool */ protected $minifyCombinedFiles = false; /** * Whether or not file headers should be written when combining files * * @var boolean */ protected $writeHeaderComment = true; /** * Where to save combined files. By default they're placed in assets/_combinedfiles, however * this may be an issue depending on your setup, especially for CSS files which often contain * relative paths. * * @var string */ protected $combinedFilesFolder = null; /** * Put all JavaScript includes at the bottom of the template before the closing `` tag, * rather than the default behaviour of placing them at the end of the `` tag. This means * script downloads won't block other HTTP requests, which can be a performance improvement. * * @var bool */ public $writeJavascriptToBody = true; /** * Force the JavaScript to the bottom of the page, even if there's a script tag in the body already * * @var boolean */ protected $forceJSToBottom = false; /** * Configures the default prefix for combined files. * * This defaults to `_combinedfiles`, and is the folder within the configured asset backend that * combined files will be stored in. If using a backend shared with other systems, it is usually * necessary to distinguish combined files from other assets. * * @config * @var string */ private static $default_combined_files_folder = '_combinedfiles'; /** * Flag to include the hash in the querystring instead of the filename for combined files. * * By default the `` of the source files is appended to the end of the combined file * (prior to the file extension). If combined files are versioned in source control or running * in a distributed environment (such as one where the newest version of a file may not always be * immediately available) then it may sometimes be necessary to disable this. When this is set to true, * the hash will instead be appended via a querystring parameter to enable cache busting, but not in * the filename itself. I.e. `assets/_combinedfiles/name.js?m=` * * @config * @var bool */ private static $combine_hash_querystring = false; /** * @var GeneratedAssetHandler */ protected $assetHandler = null; /** * @var Requirements_Minifier */ protected $minifier = null; /** * Gets the backend storage for generated files * * @return GeneratedAssetHandler */ public function getAssetHandler() { return $this->assetHandler; } /** * Set a new asset handler for this backend * * @param GeneratedAssetHandler $handler */ public function setAssetHandler(GeneratedAssetHandler $handler) { $this->assetHandler = $handler; } /** * Gets the minification service for this backend * * @deprecated 4.0.0:5.0.0 * @return Requirements_Minifier */ public function getMinifier() { return $this->minifier; } /** * Set a new minification service for this backend * * @param Requirements_Minifier $minifier */ public function setMinifier(Requirements_Minifier $minifier = null) { $this->minifier = $minifier; } /** * Enable or disable the combination of CSS and JavaScript files * * @param bool $enable */ public function setCombinedFilesEnabled($enable) { $this->combinedFilesEnabled = (bool)$enable; } /** * Check if header comments are written * * @return bool */ public function getWriteHeaderComment() { return $this->writeHeaderComment; } /** * Flag whether header comments should be written for each combined file * * @param bool $write * @return $this */ public function setWriteHeaderComment($write) { $this->writeHeaderComment = $write; return $this; } /** * Set the folder to save combined files in. By default they're placed in _combinedfiles, * however this may be an issue depending on your setup, especially for CSS files which often * contain relative paths. * * This must not include any 'assets' prefix * * @param string $folder */ public function setCombinedFilesFolder($folder) { $this->combinedFilesFolder = $folder; } /** * Retrieve the combined files folder prefix * * @return string */ public function getCombinedFilesFolder() { if ($this->combinedFilesFolder) { return $this->combinedFilesFolder; } return Config::inst()->get(__CLASS__, 'default_combined_files_folder'); } /** * Set whether to add caching query params to the requests for file-based requirements. * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by * filemtime. This has the benefit of allowing the browser to cache the URL infinitely, * while automatically busting this cache every time the file is changed. * * @param bool $var */ public function setSuffixRequirements($var) { $this->suffixRequirements = $var; } /** * Check whether we want to suffix requirements * * @return bool */ public function getSuffixRequirements() { return $this->suffixRequirements; } /** * Set whether you want to write the JS to the body of the page rather than at the end of the * head tag. * * @param bool $var * @return $this */ public function setWriteJavascriptToBody($var) { $this->writeJavascriptToBody = $var; return $this; } /** * Check whether you want to write the JS to the body of the page rather than at the end of the * head tag. * * @return bool */ public function getWriteJavascriptToBody() { return $this->writeJavascriptToBody; } /** * Forces the JavaScript requirements to the end of the body, right before the closing tag * * @param bool $var * @return $this */ public function setForceJSToBottom($var) { $this->forceJSToBottom = $var; return $this; } /** * Check if the JavaScript requirements are written to the end of the body, right before the closing tag * * @return bool */ public function getForceJSToBottom() { return $this->forceJSToBottom; } /** * Check if minify files should be combined * * @return bool */ public function getMinifyCombinedFiles() { return $this->minifyCombinedFiles; } /** * Set if combined files should be minified * * @param bool $minify * @return $this */ public function setMinifyCombinedFiles($minify) { $this->minifyCombinedFiles = $minify; return $this; } /** * Register the given JavaScript file as required. * * @param string $file Either relative to docroot or in the form "vendor/package:resource" * @param array $options List of options. Available options include: * - 'provides' : List of scripts files included in this file * - 'async' : Boolean value to set async attribute to script tag * - 'defer' : Boolean value to set defer attribute to script tag * - 'type' : Override script type= value. * - 'integrity' : SubResource Integrity hash * - 'crossorigin' : Cross-origin policy for the resource */ public function javascript($file, $options = []) { $file = ModuleResourceLoader::singleton()->resolvePath($file); // Get type $type = null; if (isset($this->javascript[$file]['type'])) { $type = $this->javascript[$file]['type']; } if (isset($options['type'])) { $type = $options['type']; } // make sure that async/defer is set if it is set once even if file is included multiple times $async = ( isset($options['async']) && $options['async'] || ( isset($this->javascript[$file]) && isset($this->javascript[$file]['async']) && $this->javascript[$file]['async'] ) ); $defer = ( isset($options['defer']) && $options['defer'] || ( isset($this->javascript[$file]) && isset($this->javascript[$file]['defer']) && $this->javascript[$file]['defer'] ) ); $integrity = $options['integrity'] ?? null; $crossorigin = $options['crossorigin'] ?? null; $this->javascript[$file] = [ 'async' => $async, 'defer' => $defer, 'type' => $type, 'integrity' => $integrity, 'crossorigin' => $crossorigin, ]; // Record scripts included in this file if (isset($options['provides'])) { $this->providedJavascript[$file] = array_values($options['provides'] ?? []); } } /** * Remove a javascript requirement * * @param string $file */ protected function unsetJavascript($file) { unset($this->javascript[$file]); } /** * Gets all scripts that are already provided by prior scripts. * This follows these rules: * - Files will not be considered provided if they are separately * included prior to the providing file. * - Providing files can be blocked, and don't provide anything * - Provided files can't be blocked (you need to block the provider) * - If a combined file includes files that are provided by prior * scripts, then these should be excluded from the combined file. * - If a combined file includes files that are provided by later * scripts, then these files should be included in the combined * file, but we can't block the later script either (possible double * up of file). * * @return array Array of provided files (map of $path => $path) */ public function getProvidedScripts() { $providedScripts = []; $includedScripts = []; foreach ($this->javascript as $script => $options) { // Ignore scripts that are explicitly blocked if (isset($this->blocked[$script])) { continue; } // At this point, the file is included. // This might also be combined at this point, potentially. $includedScripts[$script] = true; // Record any files this provides, EXCEPT those already included by now if (isset($this->providedJavascript[$script])) { foreach ($this->providedJavascript[$script] as $provided) { if (!isset($includedScripts[$provided])) { $providedScripts[$provided] = $provided; } } } } return $providedScripts; } /** * Returns an array of required JavaScript, excluding blocked * and duplicates of provided files. * * @return array */ public function getJavascript() { return array_diff_key( $this->javascript ?? [], $this->getBlocked(), $this->getProvidedScripts() ); } /** * Gets all javascript, including blocked files. Unwraps the array into a non-associative list * * @return array Indexed array of javascript files */ protected function getAllJavascript() { return $this->javascript; } /** * Register the given JavaScript code into the list of requirements * * @param string $script The script content as a string (without enclosing `