mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-08-28 13:36:12 +02:00
1439 lines
45 KiB
PHP
1439 lines
45 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Requirements tracker for JavaScript and CSS.
|
|
*
|
|
* @package framework
|
|
* @subpackage view
|
|
*/
|
|
class Requirements implements Flushable {
|
|
|
|
/**
|
|
* Flag whether combined files should be deleted on flush.
|
|
*
|
|
* By default all combined files are deleted on flush. If combined files are stored in source control,
|
|
* and thus updated manually, you might want to turn this on to disable this behaviour.
|
|
*
|
|
* @config
|
|
* @var bool
|
|
*/
|
|
private static $disable_flush_combined = false;
|
|
|
|
/**
|
|
* Triggered early in the request when a flush is requested
|
|
*/
|
|
public static function flush() {
|
|
$disabled = Config::inst()->get(__CLASS__, 'disable_flush_combined');
|
|
if(!$disabled) {
|
|
self::delete_all_combined_files();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable combining of css/javascript files.
|
|
*
|
|
* @param bool $enable
|
|
*/
|
|
public static function set_combined_files_enabled($enable) {
|
|
self::backend()->set_combined_files_enabled($enable);
|
|
}
|
|
|
|
/**
|
|
* Checks whether combining of css/javascript files is enabled.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function get_combined_files_enabled() {
|
|
return self::backend()->get_combined_files_enabled();
|
|
}
|
|
|
|
/**
|
|
* Set the relative folder e.g. 'assets' for where to store combined files
|
|
*
|
|
* @param string $folder Path to folder
|
|
*/
|
|
public static function set_combined_files_folder($folder) {
|
|
self::backend()->setCombinedFilesFolder($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
|
|
*/
|
|
public static function set_suffix_requirements($var) {
|
|
self::backend()->set_suffix_requirements($var);
|
|
}
|
|
|
|
/**
|
|
* Check whether we want to suffix requirements
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function get_suffix_requirements() {
|
|
return self::backend()->get_suffix_requirements();
|
|
}
|
|
|
|
/**
|
|
* Instance of the requirements for storage. You can create your own backend to change the
|
|
* default JS and CSS inclusion behaviour.
|
|
*
|
|
* @var Requirements_Backend
|
|
*/
|
|
private static $backend = null;
|
|
|
|
public static function backend() {
|
|
if(!self::$backend) {
|
|
self::$backend = new Requirements_Backend();
|
|
}
|
|
return self::$backend;
|
|
}
|
|
|
|
/**
|
|
* Setter method for changing the Requirements backend
|
|
*
|
|
* @param Requirements_Backend $backend
|
|
*/
|
|
public static function set_backend(Requirements_Backend $backend) {
|
|
self::$backend = $backend;
|
|
}
|
|
|
|
/**
|
|
* Register the given JavaScript file as required.
|
|
*
|
|
* @param string $file Relative to docroot
|
|
*/
|
|
public static function javascript($file) {
|
|
self::backend()->javascript($file);
|
|
}
|
|
|
|
/**
|
|
* Register the given JavaScript code into the list of requirements
|
|
*
|
|
* @param string $script The script content as a string (without enclosing <script> tag)
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public static function customScript($script, $uniquenessID = null) {
|
|
self::backend()->customScript($script, $uniquenessID);
|
|
}
|
|
|
|
/**
|
|
* Return all registered custom scripts
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_custom_scripts() {
|
|
return self::backend()->get_custom_scripts();
|
|
}
|
|
|
|
/**
|
|
* Register the given CSS styles into the list of requirements
|
|
*
|
|
* @param string $script CSS selectors as a string (without enclosing <style> tag)
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public static function customCSS($script, $uniquenessID = null) {
|
|
self::backend()->customCSS($script, $uniquenessID);
|
|
}
|
|
|
|
/**
|
|
* Add the following custom HTML code to the <head> section of the page
|
|
*
|
|
* @param string $html Custom HTML code
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public static function insertHeadTags($html, $uniquenessID = null) {
|
|
self::backend()->insertHeadTags($html, $uniquenessID);
|
|
}
|
|
|
|
/**
|
|
* Include the content of the given JavaScript file in the list of requirements. Dollar-sign
|
|
* variables will be interpolated with values from $vars similar to a .ss template.
|
|
*
|
|
* @param string $file The template file to load, relative to docroot
|
|
* @param string[]|int[] $vars The array of variables to interpolate.
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public static function javascriptTemplate($file, $vars, $uniquenessID = null) {
|
|
self::backend()->javascriptTemplate($file, $vars, $uniquenessID);
|
|
}
|
|
|
|
/**
|
|
* Register the given stylesheet into the list of requirements.
|
|
*
|
|
* @param string $file The CSS file to load, relative to site root
|
|
* @param string $media Comma-separated list of media types to use in the link tag
|
|
* (e.g. 'screen,projector')
|
|
*/
|
|
public static function css($file, $media = null) {
|
|
self::backend()->css($file, $media);
|
|
}
|
|
|
|
/**
|
|
* Registers the given themeable stylesheet as required.
|
|
*
|
|
* A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
|
|
* and it that doesn't exist and the module parameter is set then a CSS file with that name in
|
|
* the module is used.
|
|
*
|
|
* @param string $name The name of the file - eg '/css/File.css' would have the name 'File'
|
|
* @param string $module The module to fall back to if the css file does not exist in the
|
|
* current theme.
|
|
* @param string $media Comma-separated list of media types to use in the link tag
|
|
* (e.g. 'screen,projector')
|
|
*/
|
|
public static function themedCSS($name, $module = null, $media = null) {
|
|
return self::backend()->themedCSS($name, $module, $media);
|
|
}
|
|
|
|
/**
|
|
* Registers the given themeable javascript as required.
|
|
*
|
|
* A javascript file in the current theme path name 'themename/javascript/$name.js' is first searched for,
|
|
* and it that doesn't exist and the module parameter is set then a javascript file with that name in
|
|
* the module is used.
|
|
*
|
|
* @param string $name The name of the file - eg '/javascript/File.js' would have the name 'File'
|
|
* @param string $module The module to fall back to if the javascript file does not exist in the
|
|
* current theme.
|
|
* @param string $type Comma-separated list of types to use in the script tag
|
|
* (e.g. 'text/javascript,text/ecmascript')
|
|
*/
|
|
public static function themedJavascript($name, $module = null, $type = null) {
|
|
return self::backend()->themedJavascript($name, $module, $type);
|
|
}
|
|
|
|
/**
|
|
* Clear either a single or all requirements
|
|
*
|
|
* Caution: Clearing single rules added via customCSS and customScript only works if you
|
|
* originally specified a $uniquenessID.
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public static function clear($fileOrID = null) {
|
|
self::backend()->clear($fileOrID);
|
|
}
|
|
|
|
/**
|
|
* Restore requirements cleared by call to Requirements::clear
|
|
*/
|
|
public static function restore() {
|
|
self::backend()->restore();
|
|
}
|
|
|
|
/**
|
|
* Block inclusion of a specific file
|
|
*
|
|
* The difference between this and {@link clear} is that the calling order does not matter;
|
|
* {@link clear} must be called after the initial registration, whereas {@link block} can be
|
|
* used in advance. This is useful, for example, to block scripts included by a superclass
|
|
* without having to override entire functions and duplicate a lot of code.
|
|
*
|
|
* Note that blocking should be used sparingly because it's hard to trace where an file is
|
|
* being blocked from.
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public static function block($fileOrID) {
|
|
self::backend()->block($fileOrID);
|
|
}
|
|
|
|
/**
|
|
* Remove an item from the block list
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public static function unblock($fileOrID) {
|
|
self::backend()->unblock($fileOrID);
|
|
}
|
|
|
|
/**
|
|
* Removes all items from the block list
|
|
*/
|
|
public static function unblock_all() {
|
|
self::backend()->unblock_all();
|
|
}
|
|
|
|
/**
|
|
* Update the given HTML content with the appropriate include tags for the registered
|
|
* requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
|
|
* including a head and body tag.
|
|
*
|
|
* @param string $templateFile No longer used, only retained for compatibility
|
|
* @param string $content HTML content that has already been parsed from the $templateFile
|
|
* through {@link SSViewer}
|
|
* @return string HTML content augmented with the requirements tags
|
|
*/
|
|
public static function includeInHTML($templateFile, $content) {
|
|
return self::backend()->includeInHTML($templateFile, $content);
|
|
}
|
|
|
|
/**
|
|
* Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
|
|
* HTTP Response
|
|
*
|
|
* @param SS_HTTPResponse $response
|
|
*/
|
|
public static function include_in_response(SS_HTTPResponse $response) {
|
|
return self::backend()->include_in_response($response);
|
|
}
|
|
|
|
/**
|
|
* Add i18n files from the given javascript directory. SilverStripe expects that the given
|
|
* directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
|
|
* etc.
|
|
*
|
|
* @param string $langDir The JavaScript lang directory, relative to the site root, e.g.,
|
|
* 'framework/javascript/lang'
|
|
* @param bool $return Return all relative file paths rather than including them in
|
|
* requirements
|
|
* @param bool $langOnly Only include language files, not the base libraries
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
|
|
return self::backend()->add_i18n_javascript($langDir, $return, $langOnly);
|
|
}
|
|
|
|
/**
|
|
* Concatenate several css or javascript files into a single dynamically generated file. This
|
|
* increases performance by fewer HTTP requests.
|
|
*
|
|
* The combined file is regenerated based on every file modification time. Optionally a
|
|
* rebuild can be triggered by appending ?flush=1 to the URL. If all files to be combined are
|
|
* JavaScript, we use the external JSMin library to minify the JavaScript.
|
|
*
|
|
* All combined files will have a comment on the start of each concatenated file denoting their
|
|
* original position. For easier debugging, we only minify JavaScript if not in development
|
|
* mode ({@link Director::isDev()}).
|
|
*
|
|
* CAUTION: You're responsible for ensuring that the load order for combined files is
|
|
* retained - otherwise combining JavaScript files can lead to functional errors in the
|
|
* JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
|
|
* only include each file once across all includes and comibinations in a single page load.
|
|
*
|
|
* CAUTION: Combining CSS Files discards any "media" information.
|
|
*
|
|
* Example for combined JavaScript:
|
|
* <code>
|
|
* Requirements::combine_files(
|
|
* 'foobar.js',
|
|
* array(
|
|
* 'mysite/javascript/foo.js',
|
|
* 'mysite/javascript/bar.js',
|
|
* )
|
|
* );
|
|
* </code>
|
|
*
|
|
* Example for combined CSS:
|
|
* <code>
|
|
* Requirements::combine_files(
|
|
* 'foobar.css',
|
|
* array(
|
|
* 'mysite/javascript/foo.css',
|
|
* 'mysite/javascript/bar.css',
|
|
* )
|
|
* );
|
|
* </code>
|
|
*
|
|
* @param string $combinedFileName Filename of the combined file relative to docroot
|
|
* @param array $files Array of filenames relative to docroot
|
|
* @param string $media
|
|
*
|
|
* @return bool|void
|
|
*/
|
|
public static function combine_files($combinedFileName, $files, $media = null) {
|
|
self::backend()->combine_files($combinedFileName, $files, $media);
|
|
}
|
|
|
|
/**
|
|
* Return all combined files; keys are the combined file names, values are lists of
|
|
* files being combined.
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_combine_files() {
|
|
return self::backend()->get_combine_files();
|
|
}
|
|
|
|
/**
|
|
* Delete all dynamically generated combined files from the filesystem
|
|
*
|
|
* @param string $combinedFileName If left blank, all combined files are deleted.
|
|
*/
|
|
public static function delete_combined_files($combinedFileName = null) {
|
|
return self::backend()->delete_combined_files($combinedFileName);
|
|
}
|
|
|
|
/**
|
|
* Deletes all generated combined files in the configured combined files directory,
|
|
* but doesn't delete the directory itself
|
|
*/
|
|
public static function delete_all_combined_files() {
|
|
return self::backend()->delete_all_combined_files();
|
|
}
|
|
|
|
/**
|
|
* Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
|
|
*/
|
|
public static function clear_combined_files() {
|
|
self::backend()->clear_combined_files();
|
|
}
|
|
|
|
/**
|
|
* Do the heavy lifting involved in combining (and, in the case of JavaScript minifying) the
|
|
* combined files.
|
|
*/
|
|
public static function process_combined_files() {
|
|
return self::backend()->process_combined_files();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public static function set_write_js_to_body($var) {
|
|
self::backend()->set_write_js_to_body($var);
|
|
}
|
|
|
|
/**
|
|
* Set whether to force the JavaScript to end of the body. Useful if you use inline script tags
|
|
* that don't rely on scripts included via {@link Requirements::javascript()).
|
|
*
|
|
* @param boolean $var If true, force the JavaScript to be included at the bottom of the page
|
|
*/
|
|
public static function set_force_js_to_bottom($var) {
|
|
self::backend()->set_force_js_to_bottom($var);
|
|
}
|
|
|
|
/**
|
|
* Output debugging information
|
|
*/
|
|
public static function debug() {
|
|
return self::backend()->debug();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @package framework
|
|
* @subpackage view
|
|
*/
|
|
class Requirements_Backend {
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $suffix_requirements = true;
|
|
|
|
/**
|
|
* Whether to combine CSS and JavaScript files
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $combined_files_enabled = true;
|
|
|
|
/**
|
|
* Paths to all required JavaScript files relative to docroot
|
|
*
|
|
* @var array $javascript
|
|
*/
|
|
protected $javascript = array();
|
|
|
|
/**
|
|
* Paths to all required CSS files relative to the docroot.
|
|
*
|
|
* @var array $css
|
|
*/
|
|
protected $css = array();
|
|
|
|
/**
|
|
* All custom javascript code that is inserted into the page's HTML
|
|
*
|
|
* @var array $customScript
|
|
*/
|
|
protected $customScript = array();
|
|
|
|
/**
|
|
* All custom CSS rules which are inserted directly at the bottom of the HTML <head> tag
|
|
*
|
|
* @var array $customCSS
|
|
*/
|
|
protected $customCSS = array();
|
|
|
|
/**
|
|
* All custom HTML markup which is added before the closing <head> tag, e.g. additional
|
|
* metatags.
|
|
*/
|
|
protected $customHeadTags = array();
|
|
|
|
/**
|
|
* Remembers the file paths or uniquenessIDs of all Requirements cleared through
|
|
* {@link clear()}, so that they can be restored later.
|
|
*
|
|
* @var array $disabled
|
|
*/
|
|
protected $disabled = array();
|
|
|
|
/**
|
|
* 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 $blocked
|
|
*/
|
|
protected $blocked = array();
|
|
|
|
/**
|
|
* A list of combined files registered via {@link combine_files()}. Keys are the output file
|
|
* names, values are lists of input files.
|
|
*
|
|
* @var array $combine_files
|
|
*/
|
|
public $combine_files = array();
|
|
|
|
/**
|
|
* Use the JSMin library to minify any javascript file passed to {@link combine_files()}.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $combine_js_with_jsmin = true;
|
|
|
|
/**
|
|
* Whether or not file headers should be written when combining files
|
|
*
|
|
* @var boolean
|
|
*/
|
|
public $write_header_comment = 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 <body> tag,
|
|
* rather than the default behaviour of placing them at the end of the <head> tag. This means
|
|
* script downloads won't block other HTTP requests, which can be a performance improvement.
|
|
*
|
|
* @var bool
|
|
*/
|
|
public $write_js_to_body = true;
|
|
|
|
/**
|
|
* Force the JavaScript to the bottom of the page, even if there's a script tag in the body already
|
|
*
|
|
* @var boolean
|
|
*/
|
|
protected $force_js_to_bottom = false;
|
|
|
|
/**
|
|
* Enable or disable the combination of CSS and JavaScript files
|
|
*
|
|
* @param $enable
|
|
*/
|
|
public function set_combined_files_enabled($enable) {
|
|
$this->combined_files_enabled = (bool) $enable;
|
|
}
|
|
|
|
/**
|
|
* Check whether file combination is enabled.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function get_combined_files_enabled() {
|
|
return $this->combined_files_enabled;
|
|
}
|
|
|
|
/**
|
|
* Set the folder to save combined files in. 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.
|
|
*
|
|
* @param string $folder
|
|
*/
|
|
public function setCombinedFilesFolder($folder) {
|
|
$this->combinedFilesFolder = $folder;
|
|
}
|
|
|
|
/**
|
|
* @return string Folder relative to the webroot
|
|
*/
|
|
public function getCombinedFilesFolder() {
|
|
return ($this->combinedFilesFolder) ? $this->combinedFilesFolder : ASSETS_DIR . '/_combinedfiles';
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public function set_suffix_requirements($var) {
|
|
$this->suffix_requirements = $var;
|
|
}
|
|
|
|
/**
|
|
* Check whether we want to suffix requirements
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function get_suffix_requirements() {
|
|
return $this->suffix_requirements;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public function set_write_js_to_body($var) {
|
|
$this->write_js_to_body = $var;
|
|
}
|
|
|
|
/**
|
|
* Forces the JavaScript requirements to the end of the body, right before the closing tag
|
|
*
|
|
* @param bool
|
|
*/
|
|
public function set_force_js_to_bottom($var) {
|
|
$this->force_js_to_bottom = $var;
|
|
}
|
|
|
|
/**
|
|
* Register the given JavaScript file as required.
|
|
*
|
|
* @param string $file Relative to docroot
|
|
*/
|
|
public function javascript($file) {
|
|
$this->javascript[$file] = true;
|
|
}
|
|
|
|
/**
|
|
* Returns an array of all required JavaScript
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_javascript() {
|
|
return array_keys(array_diff_key($this->javascript, $this->blocked));
|
|
}
|
|
|
|
/**
|
|
* Register the given JavaScript code into the list of requirements
|
|
*
|
|
* @param string $script The script content as a string (without enclosing <script> tag)
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public function customScript($script, $uniquenessID = null) {
|
|
if($uniquenessID) $this->customScript[$uniquenessID] = $script;
|
|
else $this->customScript[] = $script;
|
|
|
|
$script .= "\n";
|
|
}
|
|
|
|
/**
|
|
* Return all registered custom scripts
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_custom_scripts() {
|
|
$requirements = "";
|
|
|
|
if($this->customScript) {
|
|
foreach($this->customScript as $script) {
|
|
$requirements .= "$script\n";
|
|
}
|
|
}
|
|
|
|
return $requirements;
|
|
}
|
|
|
|
/**
|
|
* Register the given CSS styles into the list of requirements
|
|
*
|
|
* @param string $script CSS selectors as a string (without enclosing <style> tag)
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public function customCSS($script, $uniquenessID = null) {
|
|
if($uniquenessID) $this->customCSS[$uniquenessID] = $script;
|
|
else $this->customCSS[] = $script;
|
|
}
|
|
|
|
/**
|
|
* Add the following custom HTML code to the <head> section of the page
|
|
*
|
|
* @param string $html Custom HTML code
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public function insertHeadTags($html, $uniquenessID = null) {
|
|
if($uniquenessID) $this->customHeadTags[$uniquenessID] = $html;
|
|
else $this->customHeadTags[] = $html;
|
|
}
|
|
|
|
/**
|
|
* Include the content of the given JavaScript file in the list of requirements. Dollar-sign
|
|
* variables will be interpolated with values from $vars similar to a .ss template.
|
|
*
|
|
* @param string $file The template file to load, relative to docroot
|
|
* @param string[]|int[] $vars The array of variables to interpolate.
|
|
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
|
|
*/
|
|
public function javascriptTemplate($file, $vars, $uniquenessID = null) {
|
|
$script = file_get_contents(Director::getAbsFile($file));
|
|
$search = array();
|
|
$replace = array();
|
|
|
|
if($vars) foreach($vars as $k => $v) {
|
|
$search[] = '$' . $k;
|
|
$replace[] = str_replace("\\'","'", Convert::raw2js($v));
|
|
}
|
|
|
|
$script = str_replace($search, $replace, $script);
|
|
$this->customScript($script, $uniquenessID);
|
|
}
|
|
|
|
/**
|
|
* Register the given stylesheet into the list of requirements.
|
|
*
|
|
* @param string $file The CSS file to load, relative to site root
|
|
* @param string $media Comma-separated list of media types to use in the link tag
|
|
* (e.g. 'screen,projector')
|
|
*/
|
|
public function css($file, $media = null) {
|
|
$this->css[$file] = array(
|
|
"media" => $media
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the list of registered CSS file requirements, excluding blocked files
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_css() {
|
|
return array_diff_key($this->css, $this->blocked);
|
|
}
|
|
|
|
/**
|
|
* Clear either a single or all requirements
|
|
*
|
|
* Caution: Clearing single rules added via customCSS and customScript only works if you
|
|
* originally specified a $uniquenessID.
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public function clear($fileOrID = null) {
|
|
$types = array(
|
|
'javascript',
|
|
'css',
|
|
'customScript',
|
|
'customCSS',
|
|
'customHeadTags',
|
|
'combine_files',
|
|
);
|
|
foreach ($types as $type) {
|
|
if ($fileOrID) {
|
|
if (isset($this->{$type}[$fileOrID])) {
|
|
$this->disabled[$type][$fileOrID] = $this->{$type}[$fileOrID];
|
|
unset($this->{$type}[$fileOrID]);
|
|
}
|
|
} else {
|
|
$this->disabled[$type] = $this->{$type};
|
|
$this->{$type} = array();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore requirements cleared by call to Requirements::clear
|
|
*/
|
|
public function restore() {
|
|
$types = array(
|
|
'javascript',
|
|
'css',
|
|
'customScript',
|
|
'customCSS',
|
|
'customHeadTags',
|
|
'combine_files',
|
|
);
|
|
foreach ($types as $type) {
|
|
$this->{$type} = $this->disabled[$type];
|
|
}
|
|
}
|
|
/**
|
|
* Block inclusion of a specific file
|
|
*
|
|
* The difference between this and {@link clear} is that the calling order does not matter;
|
|
* {@link clear} must be called after the initial registration, whereas {@link block} can be
|
|
* used in advance. This is useful, for example, to block scripts included by a superclass
|
|
* without having to override entire functions and duplicate a lot of code.
|
|
*
|
|
* Note that blocking should be used sparingly because it's hard to trace where an file is
|
|
* being blocked from.
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public function block($fileOrID) {
|
|
$this->blocked[$fileOrID] = $fileOrID;
|
|
}
|
|
|
|
/**
|
|
* Remove an item from the block list
|
|
*
|
|
* @param string|int $fileOrID
|
|
*/
|
|
public function unblock($fileOrID) {
|
|
if(isset($this->blocked[$fileOrID])) unset($this->blocked[$fileOrID]);
|
|
}
|
|
|
|
/**
|
|
* Removes all items from the block list
|
|
*/
|
|
public function unblock_all() {
|
|
$this->blocked = array();
|
|
}
|
|
|
|
/**
|
|
* Update the given HTML content with the appropriate include tags for the registered
|
|
* requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
|
|
* including a head and body tag.
|
|
*
|
|
* @param string $templateFile No longer used, only retained for compatibility
|
|
* @param string $content HTML content that has already been parsed from the $templateFile
|
|
* through {@link SSViewer}
|
|
* @return string HTML content augmented with the requirements tags
|
|
*/
|
|
public function includeInHTML($templateFile, $content) {
|
|
if(
|
|
(strpos($content, '</head>') !== false || strpos($content, '</head ') !== false)
|
|
&& ($this->css || $this->javascript || $this->customCSS || $this->customScript || $this->customHeadTags)
|
|
) {
|
|
$requirements = '';
|
|
$jsRequirements = '';
|
|
|
|
// Combine files - updates $this->javascript and $this->css
|
|
$this->process_combined_files();
|
|
|
|
foreach(array_diff_key($this->javascript,$this->blocked) as $file => $dummy) {
|
|
$path = Convert::raw2xml($this->path_for_file($file));
|
|
if($path) {
|
|
$jsRequirements .= "<script type=\"text/javascript\" src=\"$path\"></script>\n";
|
|
}
|
|
}
|
|
|
|
// Add all inline JavaScript *after* including external files they might rely on
|
|
if($this->customScript) {
|
|
foreach(array_diff_key($this->customScript,$this->blocked) as $script) {
|
|
$jsRequirements .= "<script type=\"text/javascript\">\n//<![CDATA[\n";
|
|
$jsRequirements .= "$script\n";
|
|
$jsRequirements .= "\n//]]>\n</script>\n";
|
|
}
|
|
}
|
|
|
|
foreach(array_diff_key($this->css,$this->blocked) as $file => $params) {
|
|
$path = Convert::raw2xml($this->path_for_file($file));
|
|
if($path) {
|
|
$media = (isset($params['media']) && !empty($params['media']))
|
|
? " media=\"{$params['media']}\"" : "";
|
|
$requirements .= "<link rel=\"stylesheet\" type=\"text/css\"{$media} href=\"$path\" />\n";
|
|
}
|
|
}
|
|
|
|
foreach(array_diff_key($this->customCSS, $this->blocked) as $css) {
|
|
$requirements .= "<style type=\"text/css\">\n$css\n</style>\n";
|
|
}
|
|
|
|
foreach(array_diff_key($this->customHeadTags,$this->blocked) as $customHeadTag) {
|
|
$requirements .= "$customHeadTag\n";
|
|
}
|
|
|
|
$replacements = array();
|
|
if ($this->force_js_to_bottom) {
|
|
$jsRequirements = $this->removeNewlinesFromCode($jsRequirements);
|
|
|
|
// Forcefully put the scripts at the bottom of the body instead of before the first
|
|
// script tag.
|
|
$replacements["/(<\/body[^>]*>)/i"] = $this->escapeReplacement($jsRequirements) . "\\1";
|
|
|
|
// Put CSS at the bottom of the head
|
|
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements) . "\\1";
|
|
} elseif ($this->write_js_to_body) {
|
|
$jsRequirements = $this->removeNewlinesFromCode($jsRequirements);
|
|
|
|
// If your template already has script tags in the body, then we try to put our script
|
|
// tags just before those. Otherwise, we put it at the bottom.
|
|
$p2 = stripos($content, '<body');
|
|
$p1 = stripos($content, '<script', $p2);
|
|
|
|
$commentTags = array();
|
|
$canWriteToBody = ($p1 !== false)
|
|
&&
|
|
// Check that the script tag is not inside a html comment tag
|
|
!(
|
|
preg_match('/.*(?|(<!--)|(-->))/U', $content, $commentTags, 0, $p1)
|
|
&&
|
|
$commentTags[1] == '-->'
|
|
);
|
|
|
|
if ($canWriteToBody) {
|
|
$content = substr($content, 0, $p1) . $jsRequirements . substr($content, $p1);
|
|
} else {
|
|
$replacements["/(<\/body[^>]*>)/i"] = $this->escapeReplacement($jsRequirements) . "\\1";
|
|
}
|
|
|
|
// Put CSS at the bottom of the head
|
|
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements) . "\\1";
|
|
} else {
|
|
// Put CSS and Javascript together before the closing head tag
|
|
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements . $jsRequirements) . "\\1";
|
|
}
|
|
|
|
if (!empty($replacements)) {
|
|
// Replace everything at once (only once)
|
|
$content = preg_replace(array_keys($replacements), array_values($replacements), $content, 1);
|
|
}
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Remove all newlines from code to preserve layout
|
|
*
|
|
* @param string $code
|
|
* @return string
|
|
*/
|
|
protected function removeNewlinesFromCode($code) {
|
|
return preg_replace('/>\n*/', '>', $code);
|
|
}
|
|
|
|
/**
|
|
* Safely escape a literal string for use in preg_replace replacement
|
|
*
|
|
* @param string $replacement
|
|
* @return string
|
|
*/
|
|
protected function escapeReplacement($replacement) {
|
|
return addcslashes($replacement, '\\$');
|
|
}
|
|
|
|
/**
|
|
* Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
|
|
* HTTP Response
|
|
*
|
|
* @param SS_HTTPResponse $response
|
|
*/
|
|
public function include_in_response(SS_HTTPResponse $response) {
|
|
$this->process_combined_files();
|
|
$jsRequirements = array();
|
|
$cssRequirements = array();
|
|
|
|
foreach(array_diff_key($this->javascript, $this->blocked) as $file => $dummy) {
|
|
$path = $this->path_for_file($file);
|
|
if($path) {
|
|
$jsRequirements[] = str_replace(',', '%2C', $path);
|
|
}
|
|
}
|
|
|
|
$response->addHeader('X-Include-JS', implode(',', $jsRequirements));
|
|
|
|
foreach(array_diff_key($this->css,$this->blocked) as $file => $params) {
|
|
$path = $this->path_for_file($file);
|
|
if($path) {
|
|
$path = str_replace(',', '%2C', $path);
|
|
$cssRequirements[] = isset($params['media']) ? "$path:##:$params[media]" : $path;
|
|
}
|
|
}
|
|
|
|
$response->addHeader('X-Include-CSS', implode(',', $cssRequirements));
|
|
}
|
|
|
|
/**
|
|
* Add i18n files from the given javascript directory. SilverStripe expects that the given
|
|
* directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
|
|
* etc.
|
|
*
|
|
* @param string $langDir The JavaScript lang directory, relative to the site root, e.g.,
|
|
* 'framework/javascript/lang'
|
|
* @param bool $return Return all relative file paths rather than including them in
|
|
* requirements
|
|
* @param bool $langOnly Only include language files, not the base libraries
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
|
|
$files = array();
|
|
$base = Director::baseFolder() . '/';
|
|
if(i18n::config()->js_i18n) {
|
|
// Include i18n.js even if no languages are found. The fact that
|
|
// add_i18n_javascript() was called indicates that the methods in
|
|
// here are needed.
|
|
if(!$langOnly) $files[] = FRAMEWORK_DIR . '/javascript/i18n.js';
|
|
|
|
if(substr($langDir,-1) != '/') $langDir .= '/';
|
|
|
|
$candidates = array(
|
|
'en.js',
|
|
'en_US.js',
|
|
i18n::get_lang_from_locale(i18n::default_locale()) . '.js',
|
|
i18n::default_locale() . '.js',
|
|
i18n::get_lang_from_locale(i18n::get_locale()) . '.js',
|
|
i18n::get_locale() . '.js',
|
|
);
|
|
foreach($candidates as $candidate) {
|
|
if(file_exists($base . DIRECTORY_SEPARATOR . $langDir . $candidate)) {
|
|
$files[] = $langDir . $candidate;
|
|
}
|
|
}
|
|
} else {
|
|
// Stub i18n implementation for when i18n is disabled.
|
|
if(!$langOnly) $files[] = FRAMEWORK_DIR . '/javascript/i18nx.js';
|
|
}
|
|
|
|
if($return) {
|
|
return $files;
|
|
} else {
|
|
foreach($files as $file) $this->javascript($file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the path for specified file
|
|
*
|
|
* @param string $fileOrUrl
|
|
* @return string|bool
|
|
*/
|
|
protected function path_for_file($fileOrUrl) {
|
|
if(preg_match('{^//|http[s]?}', $fileOrUrl)) {
|
|
return $fileOrUrl;
|
|
} elseif(Director::fileExists($fileOrUrl)) {
|
|
$filePath = preg_replace('/\?.*/', '', Director::baseFolder() . '/' . $fileOrUrl);
|
|
$prefix = Director::baseURL();
|
|
$mtimesuffix = "";
|
|
$suffix = '';
|
|
if($this->suffix_requirements) {
|
|
$mtimesuffix = "?m=" . filemtime($filePath);
|
|
$suffix = '&';
|
|
}
|
|
if(strpos($fileOrUrl, '?') !== false) {
|
|
if (strlen($suffix) == 0) {
|
|
$suffix = '?';
|
|
}
|
|
$suffix .= substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
|
|
$fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
|
|
} else {
|
|
$suffix = '';
|
|
}
|
|
return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Concatenate several css or javascript files into a single dynamically generated file. This
|
|
* increases performance by fewer HTTP requests.
|
|
*
|
|
* The combined file is regenerated based on every file modification time. Optionally a
|
|
* rebuild can be triggered by appending ?flush=1 to the URL. If all files to be combined are
|
|
* JavaScript, we use the external JSMin library to minify the JavaScript. This can be
|
|
* controlled using {@link $combine_js_with_jsmin}.
|
|
*
|
|
* All combined files will have a comment on the start of each concatenated file denoting their
|
|
* original position. For easier debugging, we only minify JavaScript if not in development
|
|
* mode ({@link Director::isDev()}).
|
|
*
|
|
* CAUTION: You're responsible for ensuring that the load order for combined files is
|
|
* retained - otherwise combining JavaScript files can lead to functional errors in the
|
|
* JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
|
|
* only include each file once across all includes and combinations in a single page load.
|
|
*
|
|
* CAUTION: Combining CSS Files discards any "media" information.
|
|
*
|
|
* Example for combined JavaScript:
|
|
* <code>
|
|
* Requirements::combine_files(
|
|
* 'foobar.js',
|
|
* array(
|
|
* 'mysite/javascript/foo.js',
|
|
* 'mysite/javascript/bar.js',
|
|
* )
|
|
* );
|
|
* </code>
|
|
*
|
|
* Example for combined CSS:
|
|
* <code>
|
|
* Requirements::combine_files(
|
|
* 'foobar.css',
|
|
* array(
|
|
* 'mysite/javascript/foo.css',
|
|
* 'mysite/javascript/bar.css',
|
|
* )
|
|
* );
|
|
* </code>
|
|
*
|
|
* @param string $combinedFileName Filename of the combined file relative to docroot
|
|
* @param array $files Array of filenames relative to docroot
|
|
* @param string $media
|
|
*
|
|
* @return bool|void
|
|
*/
|
|
public function combine_files($combinedFileName, $files, $media = null) {
|
|
// duplicate check
|
|
foreach($this->combine_files as $_combinedFileName => $_files) {
|
|
$duplicates = array_intersect($_files, $files);
|
|
if($duplicates && $combinedFileName != $_combinedFileName) {
|
|
user_error("Requirements_Backend::combine_files(): Already included files " . implode(',', $duplicates)
|
|
. " in combined file '{$_combinedFileName}'", E_USER_NOTICE);
|
|
return false;
|
|
}
|
|
}
|
|
foreach($files as $index=>$file) {
|
|
if(is_array($file)) {
|
|
// Either associative array path=>path type=>type or numeric 0=>path 1=>type
|
|
// Otherwise, assume path is the first item
|
|
if (isset($file['type']) && in_array($file['type'], array('css', 'javascript', 'js'))) {
|
|
switch ($file['type']) {
|
|
case 'css':
|
|
$this->css($file['path'], $media);
|
|
break;
|
|
default:
|
|
$this->javascript($file['path']);
|
|
break;
|
|
}
|
|
$files[$index] = $file['path'];
|
|
} elseif (isset($file[1]) && in_array($file[1], array('css', 'javascript', 'js'))) {
|
|
switch ($file[1]) {
|
|
case 'css':
|
|
$this->css($file[0], $media);
|
|
break;
|
|
default:
|
|
$this->javascript($file[0]);
|
|
break;
|
|
}
|
|
$files[$index] = $file[0];
|
|
} else {
|
|
$file = array_shift($file);
|
|
}
|
|
}
|
|
if (!is_array($file)) {
|
|
if(substr($file, -2) == 'js') {
|
|
$this->javascript($file);
|
|
} elseif(substr($file, -3) == 'css') {
|
|
$this->css($file, $media);
|
|
} else {
|
|
user_error("Requirements_Backend::combine_files(): Couldn't guess file type for file '$file', "
|
|
. "please specify by passing using an array instead.", E_USER_NOTICE);
|
|
}
|
|
}
|
|
}
|
|
$this->combine_files[$combinedFileName] = $files;
|
|
}
|
|
|
|
/**
|
|
* Return all combined files; keys are the combined file names, values are lists of
|
|
* files being combined.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_combine_files() {
|
|
return $this->combine_files;
|
|
}
|
|
|
|
/**
|
|
* Delete all dynamically generated combined files from the filesystem
|
|
*
|
|
* @param string $combinedFileName If left blank, all combined files are deleted.
|
|
*/
|
|
public function delete_combined_files($combinedFileName = null) {
|
|
$combinedFiles = ($combinedFileName) ? array($combinedFileName => null) : $this->combine_files;
|
|
$combinedFolder = ($this->getCombinedFilesFolder()) ?
|
|
(Director::baseFolder() . '/' . $this->combinedFilesFolder) : Director::baseFolder();
|
|
foreach($combinedFiles as $combinedFile => $sourceItems) {
|
|
$filePath = $combinedFolder . '/' . $combinedFile;
|
|
if(file_exists($filePath)) {
|
|
unlink($filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes all generated combined files in the configured combined files directory,
|
|
* but doesn't delete the directory itself.
|
|
*/
|
|
public function delete_all_combined_files() {
|
|
$combinedFolder = $this->getCombinedFilesFolder();
|
|
if(!$combinedFolder) return false;
|
|
|
|
$path = Director::baseFolder() . '/' . $combinedFolder;
|
|
if(file_exists($path)) {
|
|
Filesystem::removeFolder($path, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all registered CSS and JavaScript file combinations
|
|
*/
|
|
public function clear_combined_files() {
|
|
$this->combine_files = array();
|
|
}
|
|
|
|
/**
|
|
* Do the heavy lifting involved in combining (and, in the case of JavaScript minifying) the
|
|
* combined files.
|
|
*/
|
|
public function process_combined_files() {
|
|
// The class_exists call prevents us loading SapphireTest.php (slow) just to know that
|
|
// SapphireTest isn't running :-)
|
|
if(class_exists('SapphireTest', false)) $runningTest = SapphireTest::is_running_test();
|
|
else $runningTest = false;
|
|
|
|
if((Director::isDev() && !$runningTest && !isset($_REQUEST['combine'])) || !$this->combined_files_enabled) {
|
|
return;
|
|
}
|
|
|
|
// Make a map of files that could be potentially combined
|
|
$combinerCheck = array();
|
|
foreach($this->combine_files as $combinedFile => $sourceItems) {
|
|
foreach($sourceItems as $sourceItem) {
|
|
if(isset($combinerCheck[$sourceItem]) && $combinerCheck[$sourceItem] != $combinedFile){
|
|
user_error("Requirements_Backend::process_combined_files - file '$sourceItem' appears in two " .
|
|
"combined files:" . " '{$combinerCheck[$sourceItem]}' and '$combinedFile'", E_USER_WARNING);
|
|
}
|
|
$combinerCheck[$sourceItem] = $combinedFile;
|
|
|
|
}
|
|
}
|
|
|
|
// Work out the relative URL for the combined files from the base folder
|
|
$combinedFilesFolder = ($this->getCombinedFilesFolder()) ? ($this->getCombinedFilesFolder() . '/') : '';
|
|
|
|
// Figure out which ones apply to this request
|
|
$combinedFiles = array();
|
|
$newJSRequirements = array();
|
|
$newCSSRequirements = array();
|
|
foreach($this->javascript as $file => $dummy) {
|
|
if(isset($combinerCheck[$file])) {
|
|
$newJSRequirements[$combinedFilesFolder . $combinerCheck[$file]] = true;
|
|
$combinedFiles[$combinerCheck[$file]] = true;
|
|
} else {
|
|
$newJSRequirements[$file] = true;
|
|
}
|
|
}
|
|
|
|
foreach($this->css as $file => $params) {
|
|
if(isset($combinerCheck[$file])) {
|
|
// Inherit the parameters from the last file in the combine set.
|
|
$newCSSRequirements[$combinedFilesFolder . $combinerCheck[$file]] = $params;
|
|
$combinedFiles[$combinerCheck[$file]] = true;
|
|
} else {
|
|
$newCSSRequirements[$file] = $params;
|
|
}
|
|
}
|
|
|
|
// Process the combined files
|
|
$base = Director::baseFolder() . '/';
|
|
foreach(array_diff_key($combinedFiles, $this->blocked) as $combinedFile => $dummy) {
|
|
$fileList = $this->combine_files[$combinedFile];
|
|
$combinedFilePath = $base . $combinedFilesFolder . '/' . $combinedFile;
|
|
|
|
|
|
// Make the folder if necessary
|
|
if(!file_exists(dirname($combinedFilePath))) {
|
|
Filesystem::makeFolder(dirname($combinedFilePath));
|
|
}
|
|
|
|
// If the file isn't writeable, don't even bother trying to make the combined file and return. The
|
|
// files will be included individually instead. This is a complex test because is_writable fails
|
|
// if the file doesn't exist yet.
|
|
if((file_exists($combinedFilePath) && !is_writable($combinedFilePath))
|
|
|| (!file_exists($combinedFilePath) && !is_writable(dirname($combinedFilePath)))
|
|
) {
|
|
user_error("Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'",
|
|
E_USER_WARNING);
|
|
return false;
|
|
}
|
|
|
|
// Determine if we need to build the combined include
|
|
if(file_exists($combinedFilePath)) {
|
|
// file exists, check modification date of every contained file
|
|
$srcLastMod = 0;
|
|
foreach($fileList as $file) {
|
|
if(file_exists($base . $file)) {
|
|
$srcLastMod = max(filemtime($base . $file), $srcLastMod);
|
|
}
|
|
}
|
|
$refresh = $srcLastMod > filemtime($combinedFilePath);
|
|
} else {
|
|
// File doesn't exist, or refresh was explicitly required
|
|
$refresh = true;
|
|
}
|
|
|
|
if(!$refresh) continue;
|
|
|
|
$failedToMinify = false;
|
|
$combinedData = "";
|
|
foreach(array_diff($fileList, $this->blocked) as $file) {
|
|
$fileContent = file_get_contents($base . $file);
|
|
|
|
try{
|
|
$fileContent = $this->minifyFile($file, $fileContent);
|
|
}catch(Exception $e){
|
|
$failedToMinify = true;
|
|
}
|
|
|
|
if ($this->write_header_comment) {
|
|
// Write a header comment for each file for easier identification and debugging. The semicolon between each file is required for jQuery to be combined properly and protects against unterminated statements.
|
|
$combinedData .= "/****** FILE: $file *****/\n";
|
|
}
|
|
|
|
$combinedData .= $fileContent . "\n";
|
|
}
|
|
|
|
$successfulWrite = false;
|
|
$fh = fopen($combinedFilePath, 'wb');
|
|
if($fh) {
|
|
if(fwrite($fh, $combinedData) == strlen($combinedData)) $successfulWrite = true;
|
|
fclose($fh);
|
|
unset($fh);
|
|
}
|
|
|
|
if($failedToMinify){
|
|
// Failed to minify, use unminified files instead. This warning is raised at the end to allow code execution
|
|
// to complete in case this warning is caught inside a try-catch block.
|
|
user_error('Failed to minify '.$file.', exception: '.$e->getMessage(), E_USER_WARNING);
|
|
}
|
|
|
|
// Unsuccessful write - just include the regular JS files, rather than the combined one
|
|
if(!$successfulWrite) {
|
|
user_error("Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'",
|
|
E_USER_WARNING);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Note: Alters the original information, which means you can't call this method repeatedly - it will behave
|
|
// differently on the subsequent calls
|
|
$this->javascript = $newJSRequirements;
|
|
$this->css = $newCSSRequirements;
|
|
}
|
|
|
|
/**
|
|
* Minify the given $content according to the file type indicated in $filename
|
|
*
|
|
* @param string $filename
|
|
* @param string $content
|
|
* @return string
|
|
*/
|
|
protected function minifyFile($filename, $content) {
|
|
// if we have a javascript file and jsmin is enabled, minify the content
|
|
$isJS = stripos($filename, '.js');
|
|
if($isJS && $this->combine_js_with_jsmin) {
|
|
require_once('thirdparty/jsmin/jsmin.php');
|
|
|
|
increase_time_limit_to();
|
|
$content = JSMin::minify($content);
|
|
}
|
|
$content .= ($isJS ? ';' : '') . "\n";
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Registers the given themeable stylesheet as required.
|
|
*
|
|
* A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
|
|
* and it that doesn't exist and the module parameter is set then a CSS file with that name in
|
|
* the module is used.
|
|
*
|
|
* @param string $name The name of the file - eg '/css/File.css' would have the name 'File'
|
|
* @param string $module The module to fall back to if the css file does not exist in the
|
|
* current theme.
|
|
* @param string $media Comma-separated list of media types to use in the link tag
|
|
* (e.g. 'screen,projector')
|
|
*/
|
|
public function themedCSS($name, $module = null, $media = null) {
|
|
$theme = SSViewer::get_theme_folder();
|
|
$project = project();
|
|
$absbase = BASE_PATH . DIRECTORY_SEPARATOR;
|
|
$abstheme = $absbase . $theme;
|
|
$absproject = $absbase . $project;
|
|
$css = "/css/$name.css";
|
|
|
|
if(file_exists($absproject . $css)) {
|
|
$this->css($project . $css, $media);
|
|
} elseif($module && file_exists($abstheme . '_' . $module.$css)) {
|
|
$this->css($theme . '_' . $module . $css, $media);
|
|
} elseif(file_exists($abstheme . $css)) {
|
|
$this->css($theme . $css, $media);
|
|
} elseif($module) {
|
|
$this->css($module . $css, $media);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers the given themeable javascript as required.
|
|
*
|
|
* A javascript file in the current theme path name 'themename/javascript/$name.js' is first searched for,
|
|
* and it that doesn't exist and the module parameter is set then a javascript file with that name in
|
|
* the module is used.
|
|
*
|
|
* @param string $name The name of the file - eg '/js/File.js' would have the name 'File'
|
|
* @param string $module The module to fall back to if the javascript file does not exist in the
|
|
* current theme.
|
|
* @param string $type Comma-separated list of types to use in the script tag
|
|
* (e.g. 'text/javascript,text/ecmascript')
|
|
*/
|
|
public function themedJavascript($name, $module = null, $type = null) {
|
|
$theme = SSViewer::get_theme_folder();
|
|
$project = project();
|
|
$absbase = BASE_PATH . DIRECTORY_SEPARATOR;
|
|
$abstheme = $absbase . $theme;
|
|
$absproject = $absbase . $project;
|
|
$js = "/javascript/$name.js";
|
|
|
|
if(file_exists($absproject . $js)) {
|
|
$this->javascript($project . $js);
|
|
} elseif($module && file_exists($abstheme . '_' . $module.$js)) {
|
|
$this->javascript($theme . '_' . $module . $js);
|
|
} elseif(file_exists($abstheme . $js)) {
|
|
$this->javascript($theme . $js);
|
|
} elseif($module) {
|
|
$this->javascript($module . $js);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Output debugging information.
|
|
*/
|
|
public function debug() {
|
|
Debug::show($this->javascript);
|
|
Debug::show($this->css);
|
|
Debug::show($this->customCSS);
|
|
Debug::show($this->customScript);
|
|
Debug::show($this->customHeadTags);
|
|
Debug::show($this->combine_files);
|
|
}
|
|
|
|
}
|