Вход Регистрация
Файл: framework/view/Requirements.php
Строк: 1715
<?php

/**
 * Requirements tracker for JavaScript and CSS.
 *
 * @package framework
 * @subpackage view
 */
class Requirements implements Flushable {

    
/**
     * Triggered early in the request when a flush is requested
     */
    
public static function flush() {
        
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);
    }

    
/**
     * 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 .= "$scriptn";
            }
        }

        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) {
        if(
$fileOrID) {
            foreach(array(
'javascript','css''customScript''customCSS''customHeadTags') as $type) {
                if(isset(
$this->{$type}[$fileOrID])) {
                    
$this->disabled[$type][$fileOrID] = $this->{$type}[$fileOrID];
                    unset(
$this->{$type}[$fileOrID]);
                }
            }
        } else {
            
$this->disabled['javascript'] = $this->javascript;
            
$this->disabled['css'] = $this->css;
            
$this->disabled['customScript'] = $this->customScript;
            
$this->disabled['customCSS'] = $this->customCSS;
            
$this->disabled['customHeadTags'] = $this->customHeadTags;

            
$this->javascript = array();
            
$this->css = array();
            
$this->customScript = array();
            
$this->customCSS = array();
            
$this->customHeadTags = array();
        }
    }

    
/**
     * Restore requirements cleared by call to Requirements::clear
     */
    
public function restore() {
        
$this->javascript $this->disabled['javascript'];
        
$this->css $this->disabled['css'];
        
$this->customScript $this->disabled['customScript'];
        
$this->customCSS $this->disabled['customCSS'];
        
$this->customHeadTags $this->disabled['customHeadTags'];
    }
    
/**
     * 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 .= "$scriptn";
                    
$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$cssn</style>n";
            }

            foreach(
array_diff_key($this->customHeadTags,$this->blocked) as $customHeadTag) {
                
$requirements .= "$customHeadTagn";
            }

            if (
$this->force_js_to_bottom) {
                
// Remove all newlines from code to preserve layout
                
$jsRequirements preg_replace('/>n*/''>'$jsRequirements);

                
// Forcefully put the scripts at the bottom of the body instead of before the first
                // script tag.
                
$content preg_replace("/(</body[^>]*>)/i"$jsRequirements "\1"$content);
                
                
// Put CSS at the bottom of the head
                
$content preg_replace("/(</head>)/i"$requirements "\1"$content);                
            } elseif(
$this->write_js_to_body) {
                
// Remove all newlines from code to preserve layout
                
$jsRequirements preg_replace('/>n*/''>'$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$commentTags0$p1)
                        && 
                        
$commentTags[1] == '-->'
                    
);

                if(
$canWriteToBody) {
                    
$content substr($content,0,$p1) . $jsRequirements substr($content,$p1);
                } else {
                    
$content preg_replace("/(</body[^>]*>)/i"$jsRequirements "\1"$content);
                }

                
// Put CSS at the bottom of the head
                
$content preg_replace("/(</head>)/i"$requirements "\1"$content);
            } else {
                
$content preg_replace("/(</head>)/i"$requirements "\1"$content);
                
$content preg_replace("/(</head>)/i"$jsRequirements "\1"$content);
            }
        }

        return 
$content;
    }

    
/**
     * 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($fileOrUrlstrpos($fileOrUrl'?')+1);
                
$fileOrUrl substr($fileOrUrl0strpos($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($pathtrue);
        }
    }

    
/**
     * 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);
        }
    }

    
/**
     * 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);
    }

}
Онлайн: 0
Реклама