Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • ArrayData
  • CalendarWidget
  • DataObjectManagerAction
  • LiveCalendarWidget
  • MonthNavigator
  • Requirements
  • Requirements_Backend
  • SSViewer
  • SSViewer_Cached_PartialParser
  • SSViewer_FromString
  • SSViewer_PartialParser
  • ViewableData
  • ViewableData_Customised
  • ViewableData_Debugger
   1 <?php
   2 
   3 /**
   4  * Requirements tracker, for javascript and css.
   5  * @todo Document the requirements tracker, and discuss it with the others.
   6  * 
   7  * @package sapphire
   8  * @subpackage view
   9  */
  10 class Requirements {
  11     
  12     /**
  13      * Enable combining of css/javascript files.
  14      * @param boolean $enable
  15      */
  16     public static function set_combined_files_enabled($enable) {
  17         self::backend()->set_combined_files_enabled($enable);
  18     }
  19 
  20     /**
  21      * Checks whether combining of css/javascript files is enabled.
  22      * @return boolean
  23      */
  24     public static function get_combined_files_enabled() {
  25       return self::backend()->get_combined_files_enabled();
  26     }
  27 
  28     /**
  29      * Set the relative folder e.g. "assets" for where to store combined files
  30      * @param string $folder Path to folder
  31      */
  32     public static function set_combined_files_folder($folder) {
  33         self::backend()->setCombinedFilesFolder($folder);
  34     }
  35 
  36     /**
  37      * Set whether we want to suffix requirements with the time / 
  38      * location on to the requirements
  39      * 
  40      * @param bool
  41      */
  42     public static function set_suffix_requirements($var) {
  43         self::backend()->set_suffix_requirements($var);
  44     }
  45     
  46     /**
  47      * Return whether we want to suffix requirements
  48      * 
  49      * @return bool
  50      */
  51     public static function get_suffix_requirements() {
  52         return self::backend()->get_suffix_requirements();
  53     }
  54     
  55     /**
  56      * Instance of requirements for storage
  57      *
  58      * @var Requirements
  59      */
  60     private static $backend = null;
  61     
  62     public static function backend() {
  63         if(!self::$backend) {
  64             self::$backend = new Requirements_Backend();
  65         }
  66         return self::$backend;
  67     }
  68     
  69     /**
  70      * Setter method for changing the Requirements backend
  71      *
  72      * @param Requirements $backend
  73      */
  74     public static function set_backend(Requirements_Backend $backend) {
  75         self::$backend = $backend;
  76     }
  77     
  78     /**
  79      * Register the given javascript file as required.
  80      * 
  81      * See {@link Requirements_Backend::javascript()} for more info
  82      * 
  83      */
  84     static function javascript($file) {     
  85         self::backend()->javascript($file);
  86     }
  87     
  88     /**
  89      * Add the javascript code to the header of the page
  90      * 
  91      * See {@link Requirements_Backend::customScript()} for more info
  92      * @param script The script content
  93      * @param uniquenessID Use this to ensure that pieces of code only get added once.
  94      */
  95     static function customScript($script, $uniquenessID = null) {
  96         self::backend()->customScript($script, $uniquenessID);
  97     }
  98 
  99     /**
 100      * Include custom CSS styling to the header of the page.
 101      * 
 102      * See {@link Requirements_Backend::customCSS()}
 103      * 
 104      * @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
 105      * @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
 106      */
 107     static function customCSS($script, $uniquenessID = null) {
 108         self::backend()->customCSS($script, $uniquenessID);
 109     }
 110     
 111     /**
 112      * Add the following custom code to the <head> section of the page.
 113      * See {@link Requirements_Backend::insertHeadTags()}
 114      * 
 115      * @param string $html
 116      * @param string $uniquenessID
 117      */
 118     static function insertHeadTags($html, $uniquenessID = null) {
 119         self::backend()->insertHeadTags($html, $uniquenessID);
 120     }
 121     
 122     /**
 123      * Load the given javascript template with the page.
 124      * See {@link Requirements_Backend::javascriptTemplate()}
 125      * 
 126      * @param file The template file to load.
 127      * @param vars The array of variables to load.  These variables are loaded via string search & replace.
 128      */
 129     static function javascriptTemplate($file, $vars, $uniquenessID = null) {
 130         self::backend()->javascriptTemplate($file, $vars, $uniquenessID);
 131     }
 132     
 133     /**
 134      * Register the given stylesheet file as required.
 135      * See {@link Requirements_Backend::css()}
 136      * 
 137      * @param $file String Filenames should be relative to the base, eg, 'sapphire/javascript/tree/tree.css'
 138      * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
 139      * @see http://www.w3.org/TR/REC-CSS2/media.html
 140      */
 141     static function css($file, $media = null) {
 142         self::backend()->css($file, $media);
 143     }
 144     
 145     /**
 146      * Register the given "themeable stylesheet" as required. See {@link Requirements_Backend::themedCSS()}
 147      * 
 148      * @param $name String The identifier of the file.  For example, css/MyFile.css would have the identifier "MyFile"
 149      * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
 150      */
 151     static function themedCSS($name, $media = null) {
 152         return self::backend()->themedCSS($name, $media);
 153     }
 154         
 155     /**
 156      * Clear either a single or all requirements.
 157      * Caution: Clearing single rules works only with customCSS and customScript if you specified a {@uniquenessID}. 
 158      * 
 159      * See {@link Requirements_Backend::clear()}
 160      * 
 161      * @param $file String
 162      */
 163     static function clear($fileOrID = null) {
 164         self::backend()->clear($fileOrID);
 165     }
 166 
 167     /**
 168      * Blocks inclusion of a specific file
 169      * See {@link Requirements_Backend::block()}
 170      *
 171      * @param unknown_type $fileOrID
 172      */
 173     static function block($fileOrID) {
 174         self::backend()->block($fileOrID);
 175     }
 176 
 177     /**
 178      * Removes an item from the blocking-list.
 179      * See {@link Requirements_Backend::unblock()}
 180      * 
 181      * @param string $fileOrID
 182      */
 183     static function unblock($fileOrID) {
 184         self::backend()->unblock($fileOrID);
 185     }
 186 
 187     /**
 188      * Removes all items from the blocking-list.
 189      * See {@link Requirements_Backend::unblock_all()}
 190      */
 191     static function unblock_all() {
 192         self::backend()->unblock_all();
 193     }
 194     
 195     /**
 196      * Restore requirements cleared by call to Requirements::clear
 197      * See {@link Requirements_Backend::restore()}
 198      */
 199     static function restore() {
 200         self::backend()->restore();
 201     }
 202     
 203     /**
 204      * Update the given HTML content with the appropriate include tags for the registered
 205      * requirements. 
 206      * See {@link Requirements_Backend::includeInHTML()} for more information.
 207      * 
 208      * @param string $templateFilePath Absolute path for the *.ss template file
 209      * @param string $content HTML content that has already been parsed from the $templateFilePath through {@link SSViewer}.
 210      * @return string HTML content thats augumented with the requirements before the closing <head> tag.
 211      */
 212     static function includeInHTML($templateFile, $content) {
 213         return self::backend()->includeInHTML($templateFile, $content);
 214     }
 215     
 216     static function include_in_response(SS_HTTPResponse $response) {
 217         return self::backend()->include_in_response($response);
 218     }
 219     
 220     /**
 221      * Add i18n files from the given javascript directory.
 222      * @param $langDir The javascript lang directory, relative to the site root, e.g., 'sapphire/javascript/lang'
 223      * 
 224      * See {@link Requirements_Backend::add_i18n_javascript()} for more information.
 225      */
 226     public static function add_i18n_javascript($langDir) {
 227         return self::backend()->add_i18n_javascript($langDir);
 228     }
 229     
 230     /**
 231      * Concatenate several css or javascript files into a single dynamically generated file.
 232      * See {@link Requirements_Backend::combine_files()} for more info.
 233      *
 234      * @param string $combinedFileName
 235      * @param array $files
 236      */
 237     static function combine_files($combinedFileName, $files) {
 238         self::backend()->combine_files($combinedFileName, $files);
 239     }
 240     
 241     /**
 242      * Returns all combined files.
 243      * See {@link Requirements_Backend::get_combine_files()}
 244      * 
 245      * @return array
 246      */
 247     static function get_combine_files() {
 248         return self::backend()->get_combine_files();
 249     }
 250     
 251     /**
 252      * Deletes all dynamically generated combined files from the filesystem. 
 253      * See {@link Requirements_Backend::delete_combine_files()}
 254      * 
 255      * @param string $combinedFileName If left blank, all combined files are deleted.
 256      */
 257     static function delete_combined_files($combinedFileName = null) {
 258         return self::backend()->delete_combined_files($combinedFileName);
 259     }
 260     
 261 
 262     /**
 263      * Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
 264      */
 265     static function clear_combined_files() {
 266         self::backend()->clear_combined_files();
 267     }
 268         
 269     /**
 270      * See {@link combine_files()}.
 271      */
 272     static function process_combined_files() {
 273         return self::backend()->process_combined_files();
 274     }
 275 
 276     /**
 277      * Returns all custom scripts
 278      * See {@link Requirements_Backend::get_custom_scripts()}
 279      *
 280      * @return array
 281      */
 282     static function get_custom_scripts() {
 283         return self::backend()->get_custom_scripts();
 284     }
 285     
 286     /**
 287      * Set whether you want to write the JS to the body of the page or 
 288      * in the head section 
 289      * 
 290      * @see Requirements_Backend::set_write_js_to_body()
 291      * @param boolean
 292      */
 293     static function set_write_js_to_body($var) {
 294         self::backend()->set_write_js_to_body($var);
 295     }
 296     
 297     static function debug() {
 298         return self::backend()->debug();
 299     }
 300 
 301 }
 302 
 303 /**
 304  * @package sapphire
 305  * @subpackage view
 306  */
 307 class Requirements_Backend {
 308 
 309     /**
 310      * Do we want requirements to suffix onto the requirement link
 311      * tags for caching or is it disabled. Getter / Setter available
 312      * through {@link Requirements::set_suffix_requirements()}
 313      *
 314      * @var bool
 315      */
 316     protected $suffix_requirements = true;
 317 
 318     /**
 319      * Enable combining of css/javascript files.
 320      *
 321      * @var boolean
 322      */
 323     protected $combined_files_enabled = true;
 324 
 325     /**
 326      * Paths to all required .js files relative to the webroot.
 327      *
 328      * @var array $javascript
 329      */
 330     protected $javascript = array();
 331 
 332     /**
 333      * Paths to all required .css files relative to the webroot.
 334      *
 335      * @var array $css
 336      */
 337     protected $css = array();
 338 
 339     /**
 340      * All custom javascript code that is inserted
 341      * directly at the bottom of the HTML <head> tag.
 342      *
 343      * @var array $customScript
 344      */
 345     protected $customScript = array();
 346 
 347     /**
 348      * All custom CSS rules which are inserted
 349      * directly at the bottom of the HTML <head> tag.
 350      *
 351      * @var array $customCSS
 352      */
 353     protected $customCSS = array();
 354 
 355     /**
 356      * All custom HTML markup which is added before
 357      * the closing <head> tag, e.g. additional metatags.
 358      * This is preferred to entering tags directly into
 359      */
 360     protected $customHeadTags = array();
 361 
 362     /**
 363      * Remembers the filepaths of all cleared Requirements
 364      * through {@link clear()}.
 365      *
 366      * @var array $disabled
 367      */
 368     protected $disabled = array();
 369 
 370     /**
 371      * The filepaths (relative to webroot) or
 372      * uniquenessIDs of any included requirements
 373      * which should be blocked when executing {@link inlcudeInHTML()}.
 374      * This is useful to e.g. prevent core classes to modifying
 375      * Requirements without subclassing the entire functionality.
 376      * Use {@link unblock()} or {@link unblock_all()} to revert changes.
 377      *
 378      * @var array $blocked
 379      */
 380     protected $blocked = array();
 381 
 382     /**
 383      * See {@link combine_files()}.
 384      *
 385      * @var array $combine_files
 386      */
 387     public $combine_files = array();
 388 
 389     /**
 390      * Using the JSMin library to minify any
 391      * javascript file passed to {@link combine_files()}.
 392      *
 393      * @var boolean
 394      */
 395     public $combine_js_with_jsmin = true;
 396 
 397     /**
 398      * By default, combined files are stored in assets/_combinedfiles.
 399      * Set this by calling Requirements::set_combined_files_folder()
 400      * @var string
 401      */
 402     protected $combinedFilesFolder = 'assets/_combinedfiles';
 403 
 404     /**
 405      * Put all javascript includes at the bottom of the template
 406      * before the closing <body> tag instead of the <head> tag.
 407      * This means script downloads won't block other HTTP-requests,
 408      * which can be a performance improvement.
 409      * Caution: Doesn't work when modifying the DOM from those external
 410      * scripts without listening to window.onload/document.ready
 411      * (e.g. toplevel document.write() calls).
 412      *
 413      * @see http://developer.yahoo.com/performance/rules.html#js_bottom
 414      *
 415      * @var boolean
 416      */
 417     public $write_js_to_body = true;
 418 
 419     function set_combined_files_enabled($enable) {
 420         $this->combined_files_enabled = (bool) $enable;
 421     }
 422     
 423     function get_combined_files_enabled() {
 424         return $this->combined_files_enabled;
 425     }
 426 
 427     function setCombinedFilesFolder($folder) {
 428         $this->combinedFilesFolder = $folder;
 429     }
 430     
 431     /**
 432      * Set whether we want to suffix requirements with the time / 
 433      * location on to the requirements
 434      * 
 435      * @param bool
 436      */
 437     function set_suffix_requirements($var) {
 438         $this->suffix_requirements = $var;
 439     }
 440     
 441     /**
 442      * Return whether we want to suffix requirements
 443      * 
 444      * @return bool
 445      */
 446     function get_suffix_requirements() {
 447         return $this->suffix_requirements;
 448     }
 449     
 450     /**
 451      * Set whether you want the files written to the head or the body. It
 452      * writes to the body by default which can break some scripts
 453      *
 454      * @param boolean
 455      */
 456     public function set_write_js_to_body($var) {
 457         $this->write_js_to_body = $var;
 458     }
 459     /**
 460      * Register the given javascript file as required.
 461      * Filenames should be relative to the base, eg, 'sapphire/javascript/loader.js'
 462      */
 463     
 464     public function javascript($file) {
 465         $this->javascript[$file] = true;
 466     }
 467     
 468     /**
 469      * Returns an array of all included javascript
 470      *
 471      * @return array
 472      */
 473     public function get_javascript() {
 474         return array_keys(array_diff_key($this->javascript,$this->blocked));
 475     }
 476     
 477     /**
 478      * Add the javascript code to the header of the page
 479      * @todo Make Requirements automatically put this into a separate file :-)
 480      * @param script The script content
 481      * @param uniquenessID Use this to ensure that pieces of code only get added once.
 482      */
 483     public function customScript($script, $uniquenessID = null) {
 484         if($uniquenessID) $this->customScript[$uniquenessID] = $script;
 485         else $this->customScript[] = $script;
 486         
 487         $script .= "\n";
 488     }
 489     
 490     /**
 491      * Include custom CSS styling to the header of the page.
 492      *
 493      * @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
 494      * @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
 495      */
 496     function customCSS($script, $uniquenessID = null) {
 497         if($uniquenessID) $this->customCSS[$uniquenessID] = $script;
 498         else $this->customCSS[] = $script;
 499     }
 500     
 501     /**
 502      * Add the following custom code to the <head> section of the page.
 503      *
 504      * @param string $html
 505      * @param string $uniquenessID
 506      */
 507     function insertHeadTags($html, $uniquenessID = null) {
 508         if($uniquenessID) $this->customHeadTags[$uniquenessID] = $html;
 509         else $this->customHeadTags[] = $html;
 510     }
 511     
 512     /**
 513      * Load the given javascript template with the page.
 514      * @param file The template file to load.
 515      * @param vars The array of variables to load.  These variables are loaded via string search & replace.
 516      */
 517     function javascriptTemplate($file, $vars, $uniquenessID = null) {
 518         $script = file_get_contents(Director::getAbsFile($file));
 519         $search = array();
 520         $replace = array();
 521 
 522         if($vars) foreach($vars as $k => $v) {
 523             $search[] = '$' . $k;
 524             $replace[] = str_replace("\\'","'", Convert::raw2js($v));
 525         }
 526         
 527         $script = str_replace($search, $replace, $script);
 528         $this->customScript($script, $uniquenessID);
 529     }
 530     
 531     /**
 532      * Register the given stylesheet file as required.
 533      * 
 534      * @param $file String Filenames should be relative to the base, eg, 'sapphire/javascript/tree/tree.css'
 535      * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
 536      * @see http://www.w3.org/TR/REC-CSS2/media.html
 537      */
 538     function css($file, $media = null) {
 539         $this->css[$file] = array(
 540             "media" => $media
 541         );
 542     }
 543     
 544     function get_css() {
 545         return array_diff_key($this->css, $this->blocked);
 546     }
 547     
 548     /**
 549      * Needed to actively prevent the inclusion of a file,
 550      * e.g. when using your own prototype.js.
 551      * Blocking should only be used as an exception, because
 552      * it is hard to trace back. You can just block items with an
 553      * ID, so make sure you add an unique identifier to customCSS() and customScript().
 554      * 
 555      * @param string $fileOrID
 556      */
 557     function block($fileOrID) {
 558         $this->blocked[$fileOrID] = $fileOrID;
 559     }
 560     
 561     /**
 562      * Clear either a single or all requirements.
 563      * Caution: Clearing single rules works only with customCSS and customScript if you specified a {@uniquenessID}. 
 564      * 
 565      * @param $file String
 566      */
 567     function clear($fileOrID = null) {
 568         if($fileOrID) {
 569             foreach(array('javascript','css', 'customScript', 'customCSS', 'customHeadTags') as $type) {
 570                 if(isset($this->{$type}[$fileOrID])) {
 571                     $this->disabled[$type][$fileOrID] = $this->{$type}[$fileOrID];
 572                     unset($this->{$type}[$fileOrID]);
 573                 }
 574             }
 575         } else {
 576             $this->disabled['javascript'] = $this->javascript;
 577             $this->disabled['css'] = $this->css;
 578             $this->disabled['customScript'] = $this->customScript;
 579             $this->disabled['customCSS'] = $this->customCSS;
 580             $this->disabled['customHeadTags'] = $this->customHeadTags;
 581         
 582             $this->javascript = array();
 583             $this->css = array();
 584             $this->customScript = array();
 585             $this->customCSS = array();
 586             $this->customHeadTags = array();
 587         }
 588     }
 589     
 590     /**
 591      * Removes an item from the blocking-list.
 592      * CAUTION: Does not "re-add" any previously blocked elements.
 593      * @param string $fileOrID
 594      */
 595     function unblock($fileOrID) {
 596         if(isset($this->blocked[$fileOrID])) unset($this->blocked[$fileOrID]);
 597     }
 598     /**
 599      * Removes all items from the blocking-list.
 600      */
 601     function unblock_all() {
 602         $this->blocked = array();
 603     }
 604     
 605     /**
 606      * Restore requirements cleared by call to Requirements::clear
 607      */
 608     function restore() {
 609         $this->javascript = $this->disabled['javascript'];
 610         $this->css = $this->disabled['css'];
 611         $this->customScript = $this->disabled['customScript'];
 612         $this->customCSS = $this->disabled['customCSS'];
 613         $this->customHeadTags = $this->disabled['customHeadTags'];
 614     }
 615     
 616     /**
 617      * Update the given HTML content with the appropriate include tags for the registered
 618      * requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
 619      * including a <head> tag. The requirements will insert before the closing <head> tag automatically.
 620      *
 621      * @todo Calculate $prefix properly
 622      * 
 623      * @param string $templateFilePath Absolute path for the *.ss template file
 624      * @param string $content HTML content that has already been parsed from the $templateFilePath through {@link SSViewer}.
 625      * @return string HTML content thats augumented with the requirements before the closing <head> tag.
 626      */
 627     function includeInHTML($templateFile, $content) {
 628         if(isset($_GET['debug_profile'])) Profiler::mark("Requirements::includeInHTML");
 629         
 630         if(strpos($content, '</head') !== false && ($this->css || $this->javascript || $this->customCSS || $this->customScript || $this->customHeadTags)) {
 631             $requirements = '';
 632             $jsRequirements = '';
 633             
 634             // Combine files - updates $this->javascript and $this->css 
 635             $this->process_combined_files(); 
 636     
 637             foreach(array_diff_key($this->javascript,$this->blocked) as $file => $dummy) {
 638                 $path = $this->path_for_file($file);
 639                 if($path) {
 640                     $jsRequirements .= "<script type=\"text/javascript\" src=\"$path\"></script>\n";
 641                 }
 642             }
 643             
 644             // add all inline javascript *after* including external files which
 645             // they might rely on
 646             if($this->customScript) {
 647                 foreach(array_diff_key($this->customScript,$this->blocked) as $script) { 
 648                     $jsRequirements .= "<script type=\"text/javascript\">\n//<![CDATA[\n";
 649                     $jsRequirements .= "$script\n";
 650                     $jsRequirements .= "\n//]]>\n</script>\n";
 651                 }
 652             }
 653             
 654             foreach(array_diff_key($this->css,$this->blocked) as $file => $params) {                    
 655                 $path = $this->path_for_file($file);
 656                 if($path) {
 657                     $media = (isset($params['media']) && !empty($params['media'])) ? " media=\"{$params['media']}\"" : "";
 658                     $requirements .= "<link rel=\"stylesheet\" type=\"text/css\"{$media} href=\"$path\" />\n";
 659                 }
 660             }
 661             
 662             foreach(array_diff_key($this->customCSS, $this->blocked) as $css) { 
 663                 $requirements .= "<style type=\"text/css\">\n$css\n</style>\n";
 664             }
 665             
 666             foreach(array_diff_key($this->customHeadTags,$this->blocked) as $customHeadTag) { 
 667                 $requirements .= "$customHeadTag\n"; 
 668             }
 669     
 670             if($this->write_js_to_body) {
 671                 // Remove all newlines from code to preserve layout
 672                 $jsRequirements = preg_replace('/>\n*/', '>', $jsRequirements);
 673                 
 674                 // We put script tags into the body, for performance.
 675                 // If your template already has script tags in the body, then we put our script 
 676                 // tags just before those. Otherwise, we put it at the bottom.
 677                 $p2 = stripos($content, '<body');
 678                 $p1 = stripos($content, '<script', (int) $p2);
 679                 if($p1 !== false && $p1 > $p2) {
 680                     $content = substr($content,0,$p1) . $jsRequirements . substr($content,$p1);
 681                 } else {
 682                     $content = preg_replace("!(</body>)!i", $jsRequirements . "\\1", $content);
 683                 }
 684                 
 685                 // Put CSS at the bottom of the head            
 686                 $content = preg_replace("/(<\/head>)/i", $requirements . "\\1", $content, 1);
 687             } else {
 688                 $content = preg_replace("/(<\/head>)/i", $requirements . "\\1", $content, 1);
 689                 $content = preg_replace("/(<\/head>)/i", $jsRequirements . "\\1", $content, 1);
 690             }
 691         } 
 692         
 693         if(isset($_GET['debug_profile'])) Profiler::unmark("Requirements::includeInHTML");
 694         
 695         return $content;
 696     }
 697 
 698     /**
 699      * Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the HTTP response
 700      */
 701     function include_in_response(SS_HTTPResponse $response) {
 702         $this->process_combined_files(); 
 703         $jsRequirements = array();
 704         $cssRequirements = array();
 705 
 706         foreach(array_diff_key($this->javascript, $this->blocked) as $file => $dummy) { 
 707             $path = $this->path_for_file($file);
 708             if($path) $jsRequirements[] = $path;
 709         }
 710         
 711         $response->addHeader('X-Include-JS', implode(',', $jsRequirements));
 712 
 713         foreach(array_diff_key($this->css,$this->blocked) as $file => $params) {                    
 714             $path = $this->path_for_file($file);
 715             if($path) $cssRequirements[] = isset($params['media']) ? "$path:##:$params[media]" : $path;
 716         }
 717 
 718         $response->addHeader('X-Include-CSS', implode(',', $cssRequirements));
 719     }
 720     
 721     /**
 722      * Add i18n files from the given javascript directory.  Sapphire expects that the given directory
 723      * will contain a number of java script files named by language: en_US.js, de_DE.js, etc.
 724      * @param $langDir The javascript lang directory, relative to the site root, e.g., 'sapphire/javascript/lang'
 725      */
 726     public function add_i18n_javascript($langDir) {
 727         if(i18n::get_js_i18n()) {
 728             // Include i18n.js even if no languages are found.  The fact that
 729             // add_i18n_javascript() was called indicates that the methods in
 730             // here are needed.
 731             $this->javascript(SAPPHIRE_DIR . '/javascript/i18n.js');
 732 
 733             if(substr($langDir,-1) != '/') $langDir .= '/';
 734             
 735             $this->javascript($langDir . i18n::default_locale() . '.js');
 736             $this->javascript($langDir . i18n::get_locale() . '.js');
 737         
 738         // Stub i18n implementation for when i18n is disabled.
 739         } else {
 740             $this->javascript[SAPPHIRE_DIR . '/javascript/i18nx.js'] = true;
 741         }
 742     } 
 743     
 744     /**
 745      * Finds the path for specified file.
 746      *
 747      * @param string $fileOrUrl
 748      * @return string|boolean 
 749      */
 750     protected function path_for_file($fileOrUrl) {
 751         if(preg_match('!^(https?|//)!', $fileOrUrl)) {
 752             return $fileOrUrl;
 753         } elseif(Director::fileExists($fileOrUrl)) {
 754             $prefix = Director::absoluteBaseURL();
 755             $mtimesuffix = "";
 756             $suffix = '';
 757             if(strpos($fileOrUrl, '?') !== false) {
 758                 $suffix = '&' . substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
 759                 $fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
 760             }
 761             if($this->suffix_requirements) {
 762                 $mtimesuffix = "?m=" . filemtime(Director::baseFolder() . '/' . $fileOrUrl);
 763             }
 764             return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
 765         } else {
 766             return false;
 767         }
 768     }
 769     
 770     /**
 771      * Concatenate several css or javascript files into a single dynamically generated
 772      * file (stored in {@link Director::baseFolder()}). This increases performance
 773      * by fewer HTTP requests.
 774      * 
 775      * The combined file is regenerated
 776      * based on every file modification time. Optionally a rebuild can be triggered
 777      * by appending ?flush=1 to the URL.
 778      * If all files to be combined are javascript, we use the external JSMin library
 779      * to minify the javascript. This can be controlled by {@link $combine_js_with_jsmin}.
 780      * 
 781      * All combined files will have a comment on the start of each concatenated file
 782      * denoting their original position. For easier debugging, we recommend to only
 783      * minify javascript if not in development mode ({@link Director::isDev()}).
 784      * 
 785      * CAUTION: You're responsible for ensuring that the load order for combined files
 786      * is retained - otherwise combining javascript files can lead to functional errors
 787      * in the javascript logic, and combining css can lead to wrong styling inheritance.
 788      * Depending on the javascript logic, you also have to ensure that files are not included
 789      * in more than one combine_files() call.
 790      * Best practice is to include every javascript file in exactly *one* combine_files()
 791      * directive to avoid the issues mentioned above - this is enforced by this function.
 792      * 
 793      * CAUTION: Combining CSS Files discards any "media" information.
 794      *
 795      * Example for combined JavaScript:
 796      * <code>
 797      * Requirements::combine_files(
 798      *  'foobar.js',
 799      *  array(
 800      *      'mysite/javascript/foo.js',
 801      *      'mysite/javascript/bar.js',
 802      *  )
 803      * );
 804      * </code>
 805      *
 806      * Example for combined CSS:
 807      * <code>
 808      * Requirements::combine_files(
 809      *  'foobar.css',
 810      *  array(
 811      *      'mysite/javascript/foo.css',
 812      *      'mysite/javascript/bar.css',
 813      *  )
 814      * );
 815      * </code>
 816      *
 817      * @see http://code.google.com/p/jsmin-php/
 818      * 
 819      * @todo Should we enforce unique inclusion of files, or leave it to the developer? Can auto-detection cause breaks?
 820      * 
 821      * @param string $combinedFileName Filename of the combined file (will be stored in {@link Director::baseFolder()} by default)
 822      * @param array $files Array of filenames relative to the webroot
 823      */
 824     function combine_files($combinedFileName, $files) {     
 825         // duplicate check
 826         foreach($this->combine_files as $_combinedFileName => $_files) {
 827             $duplicates = array_intersect($_files, $files);
 828             if($duplicates) {
 829                 user_error("Requirements_Backend::combine_files(): Already included files " . implode(',', $duplicates) . " in combined file '{$_combinedFileName}'", E_USER_NOTICE);
 830                 return false;
 831             }
 832         }
 833         
 834         $this->combine_files[$combinedFileName] = $files;
 835     }
 836     
 837         /**
 838      * Returns all combined files.
 839      * @return array
 840      */
 841     function get_combine_files() {
 842         return $this->combine_files;
 843     }
 844     
 845     /**
 846      * Deletes all dynamically generated combined files from the filesystem. 
 847      * 
 848      * @param string $combinedFileName If left blank, all combined files are deleted.
 849      */
 850     function delete_combined_files($combinedFileName = null) {
 851         $combinedFiles = ($combinedFileName) ? array($combinedFileName => null) : $this->combine_files;
 852         $combinedFolder = ($this->combinedFilesFolder) ? (Director::baseFolder() . '/' . $this->combinedFilesFolder) : Director::baseFolder();
 853         foreach($combinedFiles as $combinedFile => $sourceItems) {
 854             $filePath = $combinedFolder . '/' . $combinedFile;
 855             if(file_exists($filePath)) {
 856                 unlink($filePath);
 857             }
 858         }
 859     }
 860     
 861     function clear_combined_files() {
 862         $this->combine_files = array();
 863     }
 864 
 865     /**
 866      * See {@link combine_files()}
 867      *
 868      */
 869     function process_combined_files() {
 870         // The class_exists call prevents us from loading SapphireTest.php (slow) just to know that
 871         // SapphireTest isn't running :-)
 872         if(class_exists('SapphireTest', false)) $runningTest = SapphireTest::is_running_test();
 873         else $runningTest = false;
 874         
 875         if((Director::isDev() && !$runningTest) || !$this->combined_files_enabled) {
 876             return;
 877         }
 878                 
 879         // FIX by Inxo. JSMin crash javascript for IE6
 880         $useragent = $_SERVER['HTTP_USER_AGENT'];
 881         if(preg_match('|MSIE ([6].[0-9]{1,2})|',$useragent,$matched)){
 882             return;
 883         }
 884         
 885         // Make a map of files that could be potentially combined
 886         $combinerCheck = array();
 887         foreach($this->combine_files as $combinedFile => $sourceItems) {
 888             foreach($sourceItems as $sourceItem) {
 889                 if(isset($combinerCheck[$sourceItem]) && $combinerCheck[$sourceItem] != $combinedFile){ 
 890                     user_error("Requirements_Backend::process_combined_files - file '$sourceItem' appears in two combined files:" . " '{$combinerCheck[$sourceItem]}' and '$combinedFile'", E_USER_WARNING);
 891                 }
 892                 $combinerCheck[$sourceItem] = $combinedFile;
 893                 
 894             }
 895         }
 896 
 897         // Work out the relative URL for the combined files from the base folder
 898         $combinedFilesFolder = ($this->combinedFilesFolder) ? ($this->combinedFilesFolder . '/') : '';
 899 
 900         // Figure out which ones apply to this pageview
 901         $combinedFiles = array();
 902         $newJSRequirements = array();
 903         $newCSSRequirements = array();
 904         foreach($this->javascript as $file => $dummy) {
 905             if(isset($combinerCheck[$file])) {
 906                 $newJSRequirements[$combinedFilesFolder . $combinerCheck[$file]] = true;
 907                 $combinedFiles[$combinerCheck[$file]] = true;
 908             } else {
 909                 $newJSRequirements[$file] = true;
 910             }
 911         }
 912         
 913         foreach($this->css as $file => $params) {
 914             if(isset($combinerCheck[$file])) {
 915                 $newCSSRequirements[$combinedFilesFolder . $combinerCheck[$file]] = true;
 916                 $combinedFiles[$combinerCheck[$file]] = true;
 917             } else {
 918                 $newCSSRequirements[$file] = $params;
 919             }
 920         }
 921 
 922         // Process the combined files
 923         $base = Director::baseFolder() . '/';
 924         foreach(array_diff_key($combinedFiles, $this->blocked) as $combinedFile => $dummy) {
 925             $fileList = $this->combine_files[$combinedFile];
 926             $combinedFilePath = $base . $this->combinedFilesFolder . '/' . $combinedFile;
 927 
 928 
 929             // Make the folder if necessary
 930             if(!file_exists(dirname($combinedFilePath))) {
 931                 Filesystem::makeFolder(dirname($combinedFilePath));
 932             }
 933             
 934             // If the file isn't writebale, don't even bother trying to make the combined file
 935             // Complex test because is_writable fails if the file doesn't exist yet.
 936             if((file_exists($combinedFilePath) && !is_writable($combinedFilePath)) ||
 937                 (!file_exists($combinedFilePath) && !is_writable(dirname($combinedFilePath)))) {
 938                 user_error("Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'", E_USER_WARNING);
 939                 continue;
 940             }
 941 
 942              // Determine if we need to build the combined include
 943             if(file_exists($combinedFilePath) && !isset($_GET['flush'])) {
 944                 // file exists, check modification date of every contained file
 945                 $srcLastMod = 0;
 946                 foreach($fileList as $file) {
 947                     $srcLastMod = max(filemtime($base . $file), $srcLastMod);
 948                 }
 949                 $refresh = $srcLastMod > filemtime($combinedFilePath);
 950             } else {
 951                 // file doesn't exist, or refresh was explicitly required
 952                 $refresh = true;
 953             }
 954 
 955             if(!$refresh) continue;
 956 
 957             $combinedData = "";
 958             foreach(array_diff($fileList, $this->blocked) as $file) {
 959                 $fileContent = file_get_contents($base . $file);
 960                 // if we have a javascript file and jsmin is enabled, minify the content
 961                 $isJS = stripos($file, '.js');
 962                 if($isJS && $this->combine_js_with_jsmin) {
 963                     require_once('thirdparty/jsmin/jsmin.php');
 964                     
 965                     increase_time_limit_to();
 966                     $fileContent = JSMin::minify($fileContent);
 967                 }
 968                 // write a header comment for each file for easier identification and debugging
 969                 // also the semicolon between each file is required for jQuery to be combinable properly
 970                 $combinedData .= "/****** FILE: $file *****/\n" . $fileContent . "\n".($isJS ? ';' : '')."\n";
 971             }
 972 
 973             $successfulWrite = false;
 974             $fh = fopen($combinedFilePath, 'wb');
 975             if($fh) {
 976                 if(fwrite($fh, $combinedData) == strlen($combinedData)) $successfulWrite = true;
 977                 fclose($fh);
 978                 unset($fh);
 979             }
 980 
 981             // Unsuccessful write - just include the regular JS files, rather than the combined one
 982             if(!$successfulWrite) {
 983                 user_error("Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'", E_USER_WARNING);
 984                 continue;
 985             }
 986         }
 987 
 988         // @todo Alters the original information, which means you can't call this
 989         // method repeatedly - it will behave different on the second call!
 990         $this->javascript = $newJSRequirements;
 991         $this->css = $newCSSRequirements;
 992   }
 993   
 994   function get_custom_scripts() {
 995         $requirements = "";
 996         
 997         if($this->customScript) {
 998             foreach($this->customScript as $script) {
 999                 $requirements .= "$script\n";
1000             }
1001         }
1002         
1003         return $requirements;
1004     }
1005     
1006     /**
1007      * Register the given "themeable stylesheet" as required.
1008      * Themeable stylesheets have globally unique names, just like templates and PHP files.
1009      * Because of this, they can be replaced by similarly named CSS files in the theme directory.
1010      * 
1011      * @param $name String The identifier of the file.  For example, css/MyFile.css would have the identifier "MyFile"
1012      * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
1013      */
1014     function themedCSS($name, $media = null) {
1015         global $_CSS_MANIFEST;
1016         
1017         $theme = SSViewer::current_theme();
1018         
1019         if($theme && isset($_CSS_MANIFEST[$name]) && isset($_CSS_MANIFEST[$name]['themes']) 
1020             && isset($_CSS_MANIFEST[$name]['themes'][$theme])) 
1021             $this->css($_CSS_MANIFEST[$name]['themes'][$theme], $media);
1022 
1023         else if(isset($_CSS_MANIFEST[$name]) && isset($_CSS_MANIFEST[$name]['unthemed'])) $this->css($_CSS_MANIFEST[$name]['unthemed'], $media);
1024         // Normal requirements fails quietly when there is no css - we should do the same
1025         // else user_error("themedCSS - No CSS file '$name.css' found.", E_USER_WARNING);
1026     }
1027     
1028     function debug() {
1029         Debug::show($this->javascript);
1030         Debug::show($this->css);
1031         Debug::show($this->customCSS);
1032         Debug::show($this->customScript);
1033         Debug::show($this->customHeadTags);
1034         Debug::show($this->combine_files);
1035     }
1036     
1037 }
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0