2007-07-19 12:40:28 +02:00
< ? php
2008-11-07 03:06:28 +01:00
2007-07-19 12:40:28 +02:00
/**
* Requirements tracker , for javascript and css .
* @ todo Document the requirements tracker , and discuss it with the others .
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ subpackage view
2007-07-19 12:40:28 +02:00
*/
class Requirements {
2009-03-04 04:44:11 +01:00
/**
* Enable combining of css / javascript files .
*
* @ var boolean
*/
private static $combined_files_enabled = true ;
public static function set_combined_files_enabled ( $enable ) {
self :: $combined_files_enabled = ( bool ) $enable ;
}
public static function get_combined_files_enabled () {
return self :: $combined_files_enabled ;
}
2008-11-07 03:06:28 +01:00
/**
* Instance of requirements for storage
*
* @ var Requirements
*/
private static $backend = null ;
2008-11-10 02:13:42 +01:00
public static function backend () {
2008-11-07 03:06:28 +01:00
if ( ! self :: $backend ) {
self :: $backend = new Requirements_Backend ();
}
return self :: $backend ;
}
/**
* Setter method for changing the Requirements backend
*
* @ param Requirements $backend
*/
public static function set_backend ( Requirements_Backend $backend ) {
self :: $backend = $backend ;
}
/**
* Register the given javascript file as required .
*
* See { @ link Requirements_Backend :: javascript ()} for more info
*
*/
static function javascript ( $file ) {
self :: backend () -> javascript ( $file );
}
/**
* Add the javascript code to the header of the page
*
* See { @ link Requirements_Backend :: customScript ()} for more info
* @ param script The script content
* @ param uniquenessID Use this to ensure that pieces of code only get added once .
*/
static function customScript ( $script , $uniquenessID = null ) {
self :: backend () -> customScript ( $script , $uniquenessID );
}
/**
2009-02-02 00:49:53 +01:00
* Include custom CSS styling to the header of the page .
2008-11-07 03:06:28 +01:00
*
* See { @ link Requirements_Backend :: customCSS ()}
2009-02-02 00:49:53 +01:00
*
* @ param string $script CSS selectors as a string ( without < style > tag enclosing selectors ) .
* @ param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
2008-11-07 03:06:28 +01:00
*/
static function customCSS ( $script , $uniquenessID = null ) {
2009-02-02 00:49:53 +01:00
self :: backend () -> customCSS ( $script , $uniquenessID );
2008-11-07 03:06:28 +01:00
}
/**
* Add the following custom code to the < head > section of the page .
* See { @ link Requirements_Backend :: insertHeadTags ()}
*
* @ param string $html
* @ param string $uniquenessID
*/
static function insertHeadTags ( $html , $uniquenessID = null ) {
self :: backend () -> insertHeadTags ( $html , $uniquenessID );
}
/**
* Load the given javascript template with the page .
* See { @ link Requirements_Backend :: javascriptTemplate ()}
*
* @ param file The template file to load .
* @ param vars The array of variables to load . These variables are loaded via string search & replace .
*/
static function javascriptTemplate ( $file , $vars , $uniquenessID = null ) {
self :: backend () -> javascriptTemplate ( $file , $vars , $uniquenessID );
}
/**
* Register the given stylesheet file as required .
* See { @ link Requirements_Backend :: css ()}
*
* @ param $file String Filenames should be relative to the base , eg , 'jsparty/tree/tree.css'
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
* @ see http :// www . w3 . org / TR / REC - CSS2 / media . html
*/
static function css ( $file , $media = null ) {
self :: backend () -> css ( $file , $media );
}
/**
* Register the given " themeable stylesheet " as required . See { @ link Requirements_Backend :: themedCSS ()}
*
* @ param $name String The identifier of the file . For example , css / MyFile . css would have the identifier " MyFile "
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
*/
static function themedCSS ( $name , $media = null ) {
return self :: backend () -> themedCSS ( $name , $media );
}
/**
* Clear either a single or all requirements .
* Caution : Clearing single rules works only with customCSS and customScript if you specified a { @ uniquenessID } .
*
* See { @ link Requirements_Backend :: clear ()}
*
* @ param $file String
*/
static function clear ( $fileOrID = null ) {
self :: backend () -> clear ( $fileOrID );
}
/**
* Blocks inclusion of a specific file
* See { @ link Requirements_Backend :: block ()}
*
* @ param unknown_type $fileOrID
*/
static function block ( $fileOrID ) {
self :: backend () -> block ( $fileOrID );
}
/**
* Removes an item from the blocking - list .
* See { @ link Requirements_Backend :: unblock ()}
*
* @ param string $fileOrID
*/
static function unblock ( $fileOrID ) {
self :: backend () -> unblock ( $fileOrID );
}
/**
* Removes all items from the blocking - list .
* See { @ link Requirements_Backend :: unblock_all ()}
*/
static function unblock_all () {
self :: backend () -> unblock_all ();
}
/**
* Restore requirements cleared by call to Requirements :: clear
* See { @ link Requirements_Backend :: restore ()}
*/
static function restore () {
self :: backend () -> restore ();
}
/**
* Update the given HTML content with the appropriate include tags for the registered
* requirements .
* See { @ link Requirements_Backend :: includeInHTML ()} for more information .
*
* @ param string $templateFilePath Absolute path for the *. ss template file
* @ param string $content HTML content that has already been parsed from the $templateFilePath through { @ link SSViewer } .
* @ return string HTML content thats augumented with the requirements before the closing < head > tag .
*/
static function includeInHTML ( $templateFile , $content ) {
return self :: backend () -> includeInHTML ( $templateFile , $content );
}
2008-11-12 05:31:33 +01:00
static function include_in_response ( HTTPResponse $response ) {
return self :: backend () -> include_in_response ( $response );
}
2008-11-07 03:06:28 +01:00
/**
2009-03-10 23:08:52 +01:00
* Add i18n files from the given javascript directory .
* @ param $langDir The javascript lang directory , relative to the site root , e . g . , 'sapphire/javascript/lang'
2008-11-07 03:06:28 +01:00
*
2009-03-10 23:08:52 +01:00
* See { @ link Requirements_Backend :: add_i18n_javascript ()} for more information .
2008-11-07 03:06:28 +01:00
*/
2009-03-10 23:08:52 +01:00
public static function add_i18n_javascript ( $langDir ) {
return self :: backend () -> add_i18n_javascript ( $langDir );
2008-11-07 03:06:28 +01:00
}
/**
* Concatenate several css or javascript files into a single dynamically generated file .
* See { @ link Requirements_Backend :: combine_files ()} for more info .
*
* @ param string $combinedFileName
* @ param array $files
*/
static function combine_files ( $combinedFileName , $files ) {
self :: backend () -> combine_files ( $combinedFileName , $files );
}
/**
* Returns all combined files .
* See { @ link Requirements_Backend :: get_combine_files ()}
*
* @ return array
*/
static function get_combine_files () {
return self :: backend () -> get_combine_files ();
}
/**
* Deletes all dynamically generated combined files from the filesystem .
* See { @ link Requirements_Backend :: delete_combine_files ()}
*
* @ param string $combinedFileName If left blank , all combined files are deleted .
*/
static function delete_combined_files ( $combinedFileName = null ) {
return self :: backend () -> delete_combined_files ( $combinedFileName );
}
/**
* Re - sets the combined files definition . See { @ link Requirements_Backend :: clear_combined_files ()}
*/
static function clear_combined_files () {
self :: backend () -> clear_combined_files ();
}
/**
* See { @ link combine_files ()} .
*/
static function process_combined_files () {
return self :: backend () -> process_combined_files ();
}
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
2008-11-07 03:06:28 +01:00
* Returns all custom scripts
* See { @ link Requirements_Backend :: get_custom_scripts ()}
*
* @ return array
*/
static function get_custom_scripts () {
return self :: backend () -> get_custom_scripts ();
}
2008-12-04 23:38:32 +01:00
/**
* Set whether you want to write the JS to the body of the page or
* in the head section
*
2009-02-02 00:49:53 +01:00
* @ see Requirements_Backend :: set_write_js_to_body ()
2008-12-04 23:38:32 +01:00
* @ param boolean
*/
static function set_write_js_to_body ( $var ) {
self :: backend () -> set_write_js_to_body ( $var );
}
2008-11-07 03:06:28 +01:00
static function debug () {
return self :: backend () -> debug ();
}
}
class Requirements_Backend {
/**
2008-07-18 05:47:17 +02:00
* Paths to all required . js files relative to the webroot .
*
* @ var array $javascript
*/
2008-11-07 03:06:28 +01:00
protected $javascript = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* Paths to all required . css files relative to the webroot .
*
2008-09-22 18:02:33 +02:00
* @ var array $css
2008-07-18 05:47:17 +02:00
*/
2008-11-07 03:06:28 +01:00
protected $css = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* All custom javascript code that is inserted
* directly at the bottom of the HTML < head > tag .
*
* @ var array $customScript
*/
2008-11-07 03:06:28 +01:00
protected $customScript = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* All custom CSS rules which are inserted
* directly at the bottom of the HTML < head > tag .
*
* @ var array $customCSS
*/
2008-11-07 03:06:28 +01:00
protected $customCSS = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* All custom HTML markup which is added before
* the closing < head > tag , e . g . additional metatags .
* This is preferred to entering tags directly into
*/
2008-11-07 03:06:28 +01:00
protected $customHeadTags = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* Remembers the filepaths of all cleared Requirements
* through { @ link clear ()} .
*
* @ var array $disabled
*/
2008-11-07 03:06:28 +01:00
protected $disabled = array ();
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
/**
* The filepaths ( relative to webroot ) or
* uniquenessIDs of any included requirements
* which should be blocked when executing { @ link inlcudeInHTML ()} .
* This is useful to e . g . prevent core classes to modifying
* Requirements without subclassing the entire functionality .
* Use { @ link unblock ()} or { @ link unblock_all ()} to revert changes .
*
* @ var array $blocked
*/
2008-11-07 03:06:28 +01:00
protected $blocked = array ();
2007-07-19 12:40:28 +02:00
2008-07-18 01:32:31 +02:00
/**
* See { @ link combine_files ()} .
*
2008-07-18 05:47:17 +02:00
* @ var array $combine_files
2008-07-18 01:32:31 +02:00
*/
2008-11-07 03:06:28 +01:00
public $combine_files = array ();
2008-07-18 01:32:31 +02:00
/**
* Using the JSMin library to minify any
* javascript file passed to { @ link combine_files ()} .
*
* @ var boolean
*/
2008-11-07 03:06:28 +01:00
public $combine_js_with_jsmin = true ;
2008-07-18 01:32:31 +02:00
2008-08-11 07:29:41 +02:00
/**
* Put all javascript includes at the bottom of the template
* before the closing < body > tag instead of the < head > tag .
* This means script downloads won ' t block other HTTP - requests ,
* which can be a performance improvement .
* Caution : Doesn ' t work when modifying the DOM from those external
* scripts without listening to window . onload / document . ready
* ( e . g . toplevel document . write () calls ) .
*
* @ see http :// developer . yahoo . com / performance / rules . html #js_bottom
*
* @ var boolean
*/
2008-11-12 05:31:33 +01:00
public $write_js_to_body = true ;
2008-12-04 23:38:32 +01:00
/**
* Set whether you want the files written to the head or the body . It
* writes to the body by default which can break some scripts
*
* @ param boolean
*/
public function set_write_js_to_body ( $var ) {
$this -> write_js_to_body = $var ;
}
2007-07-19 12:40:28 +02:00
/**
* Register the given javascript file as required .
* Filenames should be relative to the base , eg , 'sapphire/javascript/loader.js'
*/
2008-11-07 03:06:28 +01:00
public function javascript ( $file ) {
$this -> javascript [ $file ] = true ;
}
/**
* Returns an array of all included javascript
*
* @ return array
*/
public function get_javascript () {
return array_keys ( array_diff_key ( $this -> javascript , $this -> blocked ));
2007-07-19 12:40:28 +02:00
}
/**
* Add the javascript code to the header of the page
* @ todo Make Requirements automatically put this into a separate file :- )
* @ param script The script content
* @ param uniquenessID Use this to ensure that pieces of code only get added once .
*/
2008-11-07 03:06:28 +01:00
public function customScript ( $script , $uniquenessID = null ) {
2009-02-16 22:10:03 +01:00
if ( $uniquenessID ) $this -> customScript [ $uniquenessID ] = $script ;
else $this -> customScript [] = $script ;
2007-07-19 12:40:28 +02:00
$script .= " \n " ;
}
2008-11-07 03:06:28 +01:00
2009-02-02 00:49:53 +01:00
/**
* Include custom CSS styling to the header of the page .
*
* @ param string $script CSS selectors as a string ( without < style > tag enclosing selectors ) .
* @ param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
*/
2008-11-07 03:06:28 +01:00
function customCSS ( $script , $uniquenessID = null ) {
2009-02-16 22:10:03 +01:00
if ( $uniquenessID ) $this -> customCSS [ $uniquenessID ] = $script ;
else $this -> customCSS [] = $script ;
2007-07-19 12:40:28 +02:00
}
/**
2008-07-18 05:47:17 +02:00
* Add the following custom code to the < head > section of the page .
*
* @ param string $html
* @ param string $uniquenessID
2007-07-19 12:40:28 +02:00
*/
2008-11-07 03:06:28 +01:00
function insertHeadTags ( $html , $uniquenessID = null ) {
2009-02-16 22:10:03 +01:00
if ( $uniquenessID ) $this -> customHeadTags [ $uniquenessID ] = $html ;
else $this -> customHeadTags [] = $html ;
2007-07-19 12:40:28 +02:00
}
/**
* Load the given javascript template with the page .
* @ param file The template file to load .
* @ param vars The array of variables to load . These variables are loaded via string search & replace .
*/
2008-11-07 03:24:31 +01:00
function javascriptTemplate ( $file , $vars , $uniquenessID = null ) {
2007-07-19 12:40:28 +02:00
$script = file_get_contents ( Director :: getAbsFile ( $file ));
2009-02-16 22:08:54 +01:00
$search = array ();
$replace = array ();
if ( $vars ) foreach ( $vars as $k => $v ) {
2007-07-19 12:40:28 +02:00
$search [] = '$' . $k ;
$replace [] = str_replace ( " \\ ' " , " ' " , Convert :: raw2js ( $v ));
}
2009-02-16 22:08:54 +01:00
2007-07-19 12:40:28 +02:00
$script = str_replace ( $search , $replace , $script );
2008-11-07 03:06:28 +01:00
$this -> customScript ( $script , $uniquenessID );
2007-07-19 12:40:28 +02:00
}
2008-11-07 03:06:28 +01:00
2007-07-19 12:40:28 +02:00
/**
* Register the given stylesheet file as required .
*
* @ param $file String Filenames should be relative to the base , eg , 'jsparty/tree/tree.css'
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
* @ see http :// www . w3 . org / TR / REC - CSS2 / media . html
*/
2008-11-07 03:06:28 +01:00
function css ( $file , $media = null ) {
$this -> css [ $file ] = array (
2007-07-19 12:40:28 +02:00
" media " => $media
);
}
2008-11-07 03:06:28 +01:00
function get_css () {
2009-02-02 00:49:53 +01:00
return array_diff_key ( $this -> css , $this -> blocked );
2008-11-07 03:06:28 +01:00
}
2007-07-19 12:40:28 +02:00
/**
2008-11-07 03:06:28 +01:00
* Needed to actively prevent the inclusion of a file ,
* e . g . when using your own prototype . js .
* Blocking should only be used as an exception , because
* it is hard to trace back . You can just block items with an
* ID , so make sure you add an unique identifier to customCSS () and customScript () .
2007-07-19 12:40:28 +02:00
*
2008-11-07 03:06:28 +01:00
* @ param string $fileOrID
2007-07-19 12:40:28 +02:00
*/
2008-11-07 03:06:28 +01:00
function block ( $fileOrID ) {
$this -> blocked [ $fileOrID ] = $fileOrID ;
2007-07-19 12:40:28 +02:00
}
/**
* Clear either a single or all requirements .
* Caution : Clearing single rules works only with customCSS and customScript if you specified a { @ uniquenessID } .
*
* @ param $file String
*/
2008-11-07 03:06:28 +01:00
function clear ( $fileOrID = null ) {
2007-07-19 12:40:28 +02:00
if ( $fileOrID ) {
foreach ( array ( 'javascript' , 'css' , 'customScript' , 'customCSS' ) as $type ) {
2008-11-07 03:06:28 +01:00
if ( isset ( $this -> { $type }[ $fileOrID ])) {
$this -> disabled [ $type ][ $fileOrID ] = $this -> { $type }[ $fileOrID ];
unset ( $this -> { $type }[ $fileOrID ]);
2007-07-19 12:40:28 +02:00
}
}
} else {
2008-11-07 03:06:28 +01:00
$this -> disabled [ 'javascript' ] = $this -> javascript ;
$this -> disabled [ 'css' ] = $this -> css ;
$this -> disabled [ 'customScript' ] = $this -> customScript ;
$this -> disabled [ 'customCSS' ] = $this -> customCSS ;
2008-12-15 02:45:13 +01:00
$this -> disabled [ 'customHeadTags' ] = $this -> customHeadTags ;
2007-07-19 12:40:28 +02:00
2008-11-07 03:06:28 +01:00
$this -> javascript = array ();
$this -> css = array ();
$this -> customScript = array ();
$this -> customCSS = array ();
$this -> customHeadTags = array ();
2007-07-19 12:40:28 +02:00
}
}
2008-02-25 03:10:37 +01:00
/**
* Removes an item from the blocking - list .
* CAUTION : Does not " re-add " any previously blocked elements .
* @ param string $fileOrID
*/
2008-11-07 03:06:28 +01:00
function unblock ( $fileOrID ) {
if ( isset ( $this -> blocked [ $fileOrID ])) unset ( $this -> blocked [ $fileOrID ]);
2008-02-25 03:10:37 +01:00
}
/**
* Removes all items from the blocking - list .
*/
2009-03-29 03:31:44 +02:00
function unblock_all () {
$this -> blocked = array ();
2008-02-25 03:10:37 +01:00
}
2007-07-19 12:40:28 +02:00
/**
* Restore requirements cleared by call to Requirements :: clear
*/
2008-11-07 03:06:28 +01:00
function restore () {
$this -> javascript = $this -> disabled [ 'javascript' ];
$this -> css = $this -> disabled [ 'css' ];
$this -> customScript = $this -> disabled [ 'customScript' ];
$this -> customCSS = $this -> disabled [ 'customCSS' ];
2008-12-15 02:49:06 +01:00
$this -> customHeadTags = $this -> disabled [ 'customHeadTags' ];
2007-07-19 12:40:28 +02:00
}
/**
* Update the given HTML content with the appropriate include tags for the registered
2008-07-18 01:32:31 +02:00
* requirements . Needs to receive a valid HTML / XHTML template in the $content parameter ,
* including a < head > tag . The requirements will insert before the closing < head > tag automatically .
*
2007-07-19 12:40:28 +02:00
* @ todo Calculate $prefix properly
2008-07-18 01:32:31 +02:00
*
* @ param string $templateFilePath Absolute path for the *. ss template file
* @ param string $content HTML content that has already been parsed from the $templateFilePath through { @ link SSViewer } .
* @ return string HTML content thats augumented with the requirements before the closing < head > tag .
2007-07-19 12:40:28 +02:00
*/
2008-11-07 03:06:28 +01:00
function includeInHTML ( $templateFile , $content ) {
2007-07-19 12:40:28 +02:00
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: mark ( " Requirements::includeInHTML " );
2009-03-03 03:26:36 +01:00
if ( strpos ( $content , '</head' ) !== false && ( $this -> css || $this -> javascript || $this -> customCSS || $this -> customScript || $this -> customHeadTags )) {
2008-08-11 04:25:44 +02:00
$requirements = '' ;
$jsRequirements = '' ;
2008-11-07 03:06:28 +01:00
// Combine files - updates $this->javascript and $this->css
2008-11-12 05:31:33 +01:00
$this -> process_combined_files ();
2008-08-11 04:25:44 +02:00
2008-11-07 03:06:28 +01:00
foreach ( array_diff_key ( $this -> javascript , $this -> blocked ) as $file => $dummy ) {
2008-08-11 09:22:50 +02:00
$path = self :: path_for_file ( $file );
if ( $path ) {
$jsRequirements .= " <script type= \" text/javascript \" src= \" $path\ " ></ script > \n " ;
2008-08-11 04:25:44 +02:00
}
2007-07-19 12:40:28 +02:00
}
2008-08-11 07:29:41 +02:00
// add all inline javascript *after* including external files which
// they might rely on
2008-11-07 03:06:28 +01:00
if ( $this -> customScript ) {
foreach ( array_diff_key ( $this -> customScript , $this -> blocked ) as $script ) {
2008-08-11 04:25:44 +02:00
$jsRequirements .= " <script type= \" text/javascript \" > \n //<![CDATA[ \n " ;
$jsRequirements .= " $script\n " ;
$jsRequirements .= " \n //]]> \n </script> \n " ;
}
2007-07-19 12:40:28 +02:00
}
2008-08-11 04:25:44 +02:00
2008-11-07 03:06:28 +01:00
foreach ( array_diff_key ( $this -> css , $this -> blocked ) as $file => $params ) {
2008-08-11 09:22:50 +02:00
$path = self :: path_for_file ( $file );
if ( $path ) {
2008-08-11 04:25:44 +02:00
$media = ( isset ( $params [ 'media' ]) && ! empty ( $params [ 'media' ])) ? " media= \" { $params [ 'media' ] } \" " : " " ;
2008-08-11 09:22:50 +02:00
$requirements .= " <link rel= \" stylesheet \" type= \" text/css \" { $media } href= \" $path\ " /> \n " ;
2008-08-11 04:25:44 +02:00
}
2007-07-19 12:40:28 +02:00
}
2009-02-08 20:06:15 +01:00
2009-02-02 00:49:53 +01:00
foreach ( array_diff_key ( $this -> customCSS , $this -> blocked ) as $css ) {
2008-08-11 04:25:44 +02:00
$requirements .= " <style type= \" text/css \" > \n $css\n </style> \n " ;
}
2008-11-07 03:06:28 +01:00
foreach ( array_diff_key ( $this -> customHeadTags , $this -> blocked ) as $customHeadTag ) {
2008-08-11 04:25:44 +02:00
$requirements .= " $customHeadTag\n " ;
}
2008-11-07 03:06:28 +01:00
if ( $this -> write_js_to_body ) {
2008-08-11 07:29:41 +02:00
// Remove all newlines from code to preserve layout
$jsRequirements = preg_replace ( '/>\n*/' , '>' , $jsRequirements );
// We put script tags into the body, for performance.
// If your template already has script tags in the body, then we put our script tags at the top of the body.
// Otherwise, we put it at the bottom.
$p1 = strripos ( $content , '<script' );
$p2 = stripos ( $content , '<body' );
if ( $p1 !== false && $p1 > $p2 ) {
user_error ( " You have a script tag in the body, moving requirements to top of <body> for compatibilty. I recommend removing the script tag from your template's body. " , E_USER_NOTICE );
$content = eregi_replace ( " (<body[^>]*>) " , " \\ 1 " . $jsRequirements , $content );
} else {
$content = eregi_replace ( " (</body[^>]*>) " , $jsRequirements . " \\ 1 " , $content );
}
// Put CSS at the bottom of the head
$content = eregi_replace ( " (</head[^>]*>) " , $requirements . " \\ 1 " , $content );
2008-08-11 04:25:44 +02:00
} else {
2008-08-11 07:29:41 +02:00
$content = eregi_replace ( " (</head[^>]*>) " , $requirements . " \\ 1 " , $content );
$content = eregi_replace ( " (</head[^>]*>) " , $jsRequirements . " \\ 1 " , $content );
2008-08-11 04:25:44 +02:00
}
2008-08-11 07:29:41 +02:00
}
if ( isset ( $_GET [ 'debug_profile' ])) Profiler :: unmark ( " Requirements::includeInHTML " );
return $content ;
2007-07-19 12:40:28 +02:00
}
2008-11-12 05:31:33 +01:00
/**
* Attach requirements inclusion to X - Include - JS and X - Include - CSS headers on the HTTP response
*/
function include_in_response ( HTTPResponse $response ) {
$this -> process_combined_files ();
2008-11-13 02:31:31 +01:00
$jsRequirements = array ();
2008-11-13 02:39:30 +01:00
$cssRequirements = array ();
2008-11-12 05:31:33 +01:00
2008-11-13 02:31:31 +01:00
foreach ( array_diff_key ( $this -> javascript , $this -> blocked ) as $file => $dummy ) {
2008-11-12 05:31:33 +01:00
$path = $this -> path_for_file ( $file );
if ( $path ) $jsRequirements [] = $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 );
2008-11-12 22:44:35 +01:00
if ( $path ) $cssRequirements [] = isset ( $params [ 'media' ]) ? " $path :##: $params[media] " : $path ;
2008-11-12 05:31:33 +01:00
}
$response -> addHeader ( 'X-Include-CSS' , implode ( ',' , $cssRequirements ));
}
2007-07-19 12:40:28 +02:00
2009-03-10 23:08:52 +01:00
/**
* Add i18n files from the given javascript directory . Sapphire expects that the given directory
* will contain a number of java script files named by language : en_US . js , de_DE . js , etc .
* @ param $langDir The javascript lang directory , relative to the site root , e . g . , 'sapphire/javascript/lang'
*/
public function add_i18n_javascript ( $langDir ) {
if ( i18n :: get_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.
$this -> javascript ( SAPPHIRE_DIR . '/javascript/i18n.js' );
if ( substr ( $langDir , - 1 ) != '/' ) $langDir .= '/' ;
2009-04-29 01:40:35 +02:00
2009-03-10 23:08:52 +01:00
$this -> javascript ( $langDir . i18n :: default_locale () . '.js' );
$this -> javascript ( $langDir . i18n :: get_locale () . '.js' );
// Stub i18n implementation for when i18n is disabled.
} else {
$this -> javascript [ SAPPHIRE_DIR . '/javascript/i18nx.js' ] = true ;
2008-10-03 02:46:07 +02:00
}
2009-03-10 23:08:52 +01:00
}
2008-10-03 02:46:07 +02:00
2008-08-11 09:22:50 +02:00
/**
2008-11-07 03:06:28 +01:00
* Finds the path for specified file .
2008-08-11 09:22:50 +02:00
*
* @ param string $fileOrUrl
* @ return string | boolean
*/
protected static function path_for_file ( $fileOrUrl ) {
if ( preg_match ( '/^http[s]?/' , $fileOrUrl )) {
return $fileOrUrl ;
} elseif ( Director :: fileExists ( $fileOrUrl )) {
$prefix = Director :: absoluteBaseURL ();
$mtimesuffix = " ?m= " . filemtime ( Director :: baseFolder () . '/' . $fileOrUrl );
return " { $prefix } { $fileOrUrl } { $mtimesuffix } " ;
} else {
return false ;
}
}
2008-07-18 01:32:31 +02:00
/**
* Concatenate several css or javascript files into a single dynamically generated
* file ( stored in { @ link Director :: baseFolder ()}) . 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 by { @ 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 recommend to 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 wrong styling inheritance .
* Depending on the javascript logic , you also have to ensure that files are not included
* in more than one combine_files () call .
2008-07-18 05:47:17 +02:00
* Best practice is to include every javascript file in exactly * one * combine_files ()
* directive to avoid the issues mentioned above - this is enforced by this function .
2008-09-22 18:02:33 +02:00
*
* CAUTION : Combining CSS Files discards any " media " information .
2008-07-18 01:32:31 +02:00
*
* 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 >
*
* @ see http :// code . google . com / p / jsmin - php /
2008-07-18 05:47:17 +02:00
*
* @ todo Should we enforce unique inclusion of files , or leave it to the developer ? Can auto - detection cause breaks ?
*
2008-07-18 01:32:31 +02:00
* @ param string $combinedFileName Filename of the combined file ( will be stored in { @ link Director :: baseFolder ()} by default )
* @ param array $files Array of filenames relative to the webroot
*/
2008-11-07 03:06:28 +01:00
function combine_files ( $combinedFileName , $files ) {
2008-07-18 05:47:17 +02:00
// duplicate check
2008-11-07 03:06:28 +01:00
foreach ( $this -> combine_files as $_combinedFileName => $_files ) {
2008-07-18 05:47:17 +02:00
$duplicates = array_intersect ( $_files , $files );
if ( $duplicates ) {
user_error ( " Requirements::combine_files(): Already included files " . implode ( ',' , $duplicates ) . " in combined file ' { $_combinedFileName } ' " , E_USER_NOTICE );
return false ;
}
}
2008-11-07 03:06:28 +01:00
$this -> combine_files [ $combinedFileName ] = $files ;
2008-07-18 05:47:17 +02:00
}
2008-11-07 03:06:28 +01:00
/**
* Returns all combined files .
2008-07-18 05:47:17 +02:00
* @ return array
*/
2008-11-07 03:06:28 +01:00
function get_combine_files () {
return $this -> combine_files ;
2008-07-18 05:47:17 +02:00
}
/**
2008-08-12 01:18:56 +02:00
* Deletes all dynamically generated combined files from the filesystem .
2008-07-18 05:47:17 +02:00
*
* @ param string $combinedFileName If left blank , all combined files are deleted .
*/
2008-11-07 03:06:28 +01:00
function delete_combined_files ( $combinedFileName = null ) {
$combinedFiles = ( $combinedFileName ) ? array ( $combinedFileName => null ) : $this -> combine_files ;
2008-07-18 05:47:17 +02:00
foreach ( $combinedFiles as $combinedFile => $sourceItems ) {
$filePath = Director :: baseFolder () . '/' . $combinedFile ;
if ( file_exists ( $filePath )) {
unlink ( $filePath );
}
}
2008-07-18 01:32:31 +02:00
}
2008-11-12 05:31:33 +01:00
function clear_combined_files () {
2008-11-07 03:06:28 +01:00
$this -> combine_files = array ();
2008-08-12 01:18:56 +02:00
}
2008-11-07 03:06:28 +01:00
2008-07-18 01:32:31 +02:00
/**
2008-11-07 03:06:28 +01:00
* See { @ link combine_files ()}
*
*/
function process_combined_files () {
2009-04-27 02:19:16 +02:00
// The class_exists call prevents us from 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 ) || ! Requirements :: get_combined_files_enabled ()) {
2008-11-12 22:15:58 +01:00
return ;
}
2008-07-18 01:32:31 +02:00
// Make a map of files that could be potentially combined
$combinerCheck = array ();
2008-11-07 03:06:28 +01:00
foreach ( $this -> combine_files as $combinedFile => $sourceItems ) {
2008-07-18 01:32:31 +02:00
foreach ( $sourceItems as $sourceItem ) {
if ( isset ( $combinerCheck [ $sourceItem ]) && $combinerCheck [ $sourceItem ] != $combinedFile ){
2008-07-18 05:47:17 +02:00
user_error ( " Requirements::process_combined_files - file ' $sourceItem ' appears in two combined files: " . " ' { $combinerCheck [ $sourceItem ] } ' and ' $combinedFile ' " , E_USER_WARNING );
2008-07-18 01:32:31 +02:00
}
$combinerCheck [ $sourceItem ] = $combinedFile ;
}
}
// Figure out which ones apply to this pageview
$combinedFiles = array ();
$newJSRequirements = array ();
$newCSSRequirements = array ();
2008-11-07 03:06:28 +01:00
foreach ( $this -> javascript as $file => $dummy ) {
2008-07-18 01:32:31 +02:00
if ( isset ( $combinerCheck [ $file ])) {
$newJSRequirements [ $combinerCheck [ $file ]] = true ;
$combinedFiles [ $combinerCheck [ $file ]] = true ;
} else {
$newJSRequirements [ $file ] = true ;
}
}
2009-02-02 00:49:53 +01:00
2008-11-07 03:06:28 +01:00
foreach ( $this -> css as $file => $params ) {
2008-07-18 01:32:31 +02:00
if ( isset ( $combinerCheck [ $file ])) {
$newCSSRequirements [ $combinerCheck [ $file ]] = true ;
$combinedFiles [ $combinerCheck [ $file ]] = true ;
} else {
$newCSSRequirements [ $file ] = $params ;
}
}
2009-02-02 00:49:53 +01:00
2008-07-18 01:32:31 +02:00
// Process the combined files
2008-07-18 05:47:17 +02:00
$base = Director :: baseFolder () . '/' ;
2008-11-07 03:06:28 +01:00
foreach ( array_diff_key ( $combinedFiles , $this -> blocked ) as $combinedFile => $dummy ) {
$fileList = $this -> combine_files [ $combinedFile ];
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
// Determine if we need to build the combined include
if ( file_exists ( $base . $combinedFile ) && ! isset ( $_GET [ 'flush' ])) {
// file exists, check modification date of every contained file
$srcLastMod = 0 ;
foreach ( $fileList as $file ) {
$srcLastMod = max ( filemtime ( $base . $file ), $srcLastMod );
2008-07-18 01:32:31 +02:00
}
2008-07-18 05:47:17 +02:00
$refresh = $srcLastMod > filemtime ( $base . $combinedFile );
} else {
// file doesn't exist, or refresh was explicitly required
$refresh = true ;
}
2008-07-18 01:32:31 +02:00
2008-07-18 05:47:17 +02:00
if ( ! $refresh ) continue ;
$combinedData = " " ;
2008-11-07 03:06:28 +01:00
foreach ( array_diff ( $fileList , $this -> blocked ) as $file ) {
2008-07-18 05:47:17 +02:00
$fileContent = file_get_contents ( $base . $file );
// if we have a javascript file and jsmin is enabled, minify the content
2008-11-07 03:06:28 +01:00
if ( stripos ( $file , '.js' ) && $this -> combine_js_with_jsmin ) {
2008-08-17 03:08:10 +02:00
require_once ( 'thirdparty/jsmin/JSMin.php' );
2008-11-12 22:15:58 +01:00
set_time_limit ( 0 );
2008-07-18 05:47:17 +02:00
$fileContent = JSMin :: minify ( $fileContent );
2008-07-18 01:32:31 +02:00
}
2008-07-18 05:47:17 +02:00
// write a header comment for each file for easier identification and debugging
2008-11-12 05:31:33 +01:00
$combinedData .= " /****** FILE: $file *****/ \n " . $fileContent . " \n ; \n " ;
2008-07-18 01:32:31 +02:00
}
2008-07-18 05:47:17 +02:00
if ( ! file_exists ( dirname ( $base . $combinedFile ))) {
2008-11-27 23:34:37 +01:00
Filesystem :: makeFolder ( dirname ( $base . $combinedFile ));
2008-07-18 05:47:17 +02:00
}
2008-11-18 02:48:37 +01:00
$successfulWrite = false ;
2008-07-18 05:47:17 +02:00
$fh = fopen ( $base . $combinedFile , 'w' );
2008-11-18 02:48:37 +01:00
if ( $fh ) {
if ( fwrite ( $fh , $combinedData ) == strlen ( $combinedData )) $successfulWrite = true ;
fclose ( $fh );
unset ( $fh );
}
2009-02-02 00:49:53 +01:00
2008-11-18 02:48:37 +01:00
// 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 ' $base $combinedFile ' " , E_USER_WARNING );
2009-02-02 00:49:53 +01:00
return ;
2008-11-18 02:48:37 +01:00
}
2008-07-18 05:47:17 +02:00
}
2008-11-18 02:48:37 +01:00
// @todo Alters the original information, which means you can't call this
// method repeatedly - it will behave different on the second call!
$this -> javascript = $newJSRequirements ;
$this -> css = $newCSSRequirements ;
2008-11-07 03:06:28 +01:00
}
2008-11-07 03:24:31 +01:00
function get_custom_scripts () {
2007-07-19 12:40:28 +02:00
$requirements = " " ;
2008-11-07 03:06:28 +01:00
if ( $this -> customScript ) {
foreach ( $this -> customScript as $script ) {
2007-07-19 12:40:28 +02:00
$requirements .= " $script\n " ;
}
}
return $requirements ;
}
2008-11-07 03:06:28 +01:00
/**
* Register the given " themeable stylesheet " as required .
* Themeable stylesheets have globally unique names , just like templates and PHP files .
* Because of this , they can be replaced by similarly named CSS files in the theme directory .
*
* @ param $name String The identifier of the file . For example , css / MyFile . css would have the identifier " MyFile "
* @ param $media String Comma - separated list of media - types ( e . g . " screen,projector " )
*/
function themedCSS ( $name , $media = null ) {
global $_CSS_MANIFEST ;
$theme = SSViewer :: current_theme ();
if ( $theme && isset ( $_CSS_MANIFEST [ $name ]) && isset ( $_CSS_MANIFEST [ $name ][ 'themes' ])
&& isset ( $_CSS_MANIFEST [ $name ][ 'themes' ][ $theme ]))
Requirements :: css ( $_CSS_MANIFEST [ $name ][ 'themes' ][ $theme ], $media );
else if ( isset ( $_CSS_MANIFEST [ $name ]) && isset ( $_CSS_MANIFEST [ $name ][ 'unthemed' ])) $this -> css ( $_CSS_MANIFEST [ $name ][ 'unthemed' ], $media );
// Normal requirements fails quietly when there is no css - we should do the same
// else user_error("themedCSS - No CSS file '$name.css' found.", E_USER_WARNING);
}
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 );
2007-07-19 12:40:28 +02:00
}
2008-11-07 03:06:28 +01:00
2007-07-19 12:40:28 +02:00
}
2008-11-12 05:31:33 +01:00
?>