PageRenderTime 34ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/booster/booster_inc.php

http://github.com/Schepp/CSS-JS-Booster
PHP | 2119 lines | 1171 code | 190 blank | 758 comment | 293 complexity | 297a206ed86b005d1ebf68a6ab344b30 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * CSS-JS-BOOSTER
  4. *
  5. * An easy to use PHP-Library that combines, optimizes, dataURI-fies, re-splits,
  6. * compresses and caches your CSS and JS for quicker loading times.
  7. *
  8. * PHP version 5
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Lesser General Public License as published
  12. * by the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public License
  21. * along with this program.
  22. * If not, see <http://www.gnu.org/licenses/lgpl-3.0.txt>
  23. *
  24. * @category PHP
  25. * @package CSS-JS-Booster
  26. * @author Christian Schepp Schaefer <schaepp@gmx.de> <http://twitter.com/derSchepp>
  27. * @copyright 2010 Christian Schepp Schaefer
  28. * @license http://www.gnu.org/copyleft/lesser.html The GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0
  29. * @link http://github.com/Schepp/CSS-JS-Booster
  30. */
  31. // Starting zlib-compressed output
  32. @ini_set('zlib.output_compression',2048);
  33. @ini_set('zlib.output_compression_level',4);
  34. // Starting gzip-compressed output if zlib-compression is turned off
  35. if (
  36. isset($_SERVER['HTTP_ACCEPT_ENCODING'])
  37. && substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')
  38. && function_exists('ob_gzhandler')
  39. && (!ini_get('zlib.output_compression') || ini_get('zlib.output_compression') == '' || strtolower(ini_get('zlib.output_compression')) == 'off' || intval(ini_get('zlib.output_compression')) != 2048)
  40. && !function_exists('booster_wp')
  41. )
  42. {
  43. $booster_use_ob_gzhandler = TRUE;
  44. @ob_start('ob_gzhandler');
  45. }
  46. else
  47. {
  48. @ob_start();
  49. }
  50. /**
  51. * CSS-JS-BOOSTER
  52. */
  53. class Booster {
  54. // Global configuration /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  55. /**
  56. * Defines the markup language to use.
  57. *
  58. * replaces old $css_markuptype
  59. * Defaults to "XHTML".
  60. * @var string
  61. * @access public
  62. */
  63. public $markuptype = 'XHTML';
  64. /**
  65. * Used to store globally the date of last change of newest file
  66. *
  67. * @var integer
  68. * @access public
  69. */
  70. public $filestime = 0;
  71. /**
  72. * Defines the server's root directory
  73. *
  74. * Use this if you changed the root with mod_userdir or something else
  75. * Defaults to $_SERVER['DOCUMENT_ROOT']
  76. * @var string
  77. * @access public
  78. */
  79. public $document_root = '';
  80. /**
  81. * Defines the a base offset if $_SERVER['DOCUMENT_ROOT'] is not equal to http://domain/
  82. * but e.g. points to http://domain/~user/
  83. *
  84. * Use this if you changed the root with mod_userdir or something else
  85. * Defaults to '/';
  86. * Change for example to '/~user/';
  87. * @var string
  88. * @access public
  89. */
  90. public $base_offset = '/';
  91. /**
  92. * Defines the directory to use for caching
  93. *
  94. * The directory is relative to "booster"-folder and should be write-enabled
  95. * Defaults to "booster_cache".
  96. * @var string
  97. * @access public
  98. */
  99. public $booster_cachedir = 'booster_cache';
  100. /**
  101. * Switch cache directory automatic cleanup on sundays on/off
  102. *
  103. * @var boolean
  104. * @access public
  105. */
  106. public $booster_cachedir_autocleanup = TRUE;
  107. /**
  108. * Used to remember if the working-path has already been calculated.
  109. *
  110. * @var boolean
  111. * @access private
  112. * @see setcachedir
  113. */
  114. private $booster_cachedir_transformed = FALSE;
  115. /**
  116. * Switch debug mode on/off for debugging CSS and JS
  117. *
  118. * @var boolean
  119. * @access public
  120. */
  121. public $debug = FALSE;
  122. /**
  123. * Switch debug mode on/off for development of this library
  124. *
  125. * @var boolean
  126. * @access public
  127. */
  128. public $librarydebug = FALSE;
  129. /**
  130. * Defines the file to use for logging in librarydebug-mode
  131. *
  132. * The file is located inside cache-folder
  133. * Starts empty, defaults later to "booster_cache/debug_log".
  134. * @var string
  135. * @access private
  136. */
  137. private $debug_log = '';
  138. /**
  139. * Variable in which to put error messages
  140. *
  141. * @var string
  142. * @access private
  143. */
  144. private $errormessage = '';
  145. /**
  146. * Variable in which we store if mod_rewrite is active (if we can detect it)
  147. *
  148. * @var bool
  149. * @access public
  150. */
  151. public $mod_rewrite = TRUE;
  152. // CSS specific configuration ///////////////////////////////////////////////////////////////////////////////////////////////////////
  153. /**
  154. * Defines source to take the CSS stylesheets from.
  155. *
  156. * It accepts foldernames, filenames, multiple files and folders comma-delimited in strings or as array.
  157. * When passing foldernames, containing files will be processed in alphabetical order.
  158. * The variable also accepts a stylesheet-string if you set css_stringmode to "TRUE"
  159. * Defaults to "css".
  160. * @var mixed
  161. * @access public
  162. * @see $css_stringmode
  163. */
  164. public $css_source = 'css';
  165. /**
  166. * Defines media-attribute for CSS markup output
  167. *
  168. * Specify differing media-types like "print", "handheld", etc.
  169. * Defaults to "all".
  170. * @var string
  171. * @access public
  172. */
  173. public $css_media = 'all';
  174. /**
  175. * Defines rel-attribute for CSS markup output
  176. *
  177. * Specify differing relations like "alternate stylesheet"
  178. * Defaults to "stylesheet".
  179. * @var string
  180. * @access public
  181. */
  182. public $css_rel = 'stylesheet';
  183. /**
  184. * Defines a title-attribute for CSS markup output
  185. *
  186. * If you like to title multiple stylesheets
  187. * Defaults to "".
  188. * @var string
  189. * @access public
  190. */
  191. public $css_title = '';
  192. /**
  193. * Defines in how many parts the CSS output shall be split
  194. *
  195. * As newer browsers support more than 2 concurrent parallel connections
  196. * to a webserver you can decrease loading-time by splitting the output up
  197. * into more than one file.
  198. * Defaults to "2".
  199. * @var number
  200. * @access public
  201. */
  202. public $css_totalparts = 2;
  203. /**
  204. * Defines which part to ouput when retrieving CSS in multiple parts
  205. *
  206. * Used by accompagning script "booster_css.php"
  207. * Defaults to "0".
  208. * @var number
  209. * @access public
  210. */
  211. public $css_part = 0;
  212. /**
  213. * Defines if we want these styles to be lazy loaded
  214. *
  215. * Usefull for CSS with large inlined images to load after the rest
  216. * @var boolean
  217. * @access public
  218. */
  219. public $css_lazyload = FALSE;
  220. /**
  221. * Defines if to switch to a more powerful hosted minifier
  222. *
  223. * You can use the full YUI Compressor included in CSS-JS-Booster instead of the
  224. * included minification functions for stylesheets.
  225. * But be carefull, it will only work on dedicated servers with Java installed.
  226. * @var boolean
  227. * @access public
  228. */
  229. public $css_hosted_minifier = FALSE;
  230. /**
  231. * Defines the path to a hosted minifier
  232. *
  233. * Will store the local CSS minifier path relative to this file
  234. * @var string
  235. * @access private
  236. * @see $css_hosted_minifier
  237. */
  238. private $css_hosted_minifier_path = 'yuicompressor/yuicompressor-2.4.2.jar';
  239. /**
  240. * Defines if source-file retrieval shall be recursive
  241. *
  242. * Only matters when passing folders as source-parameter.
  243. * If set to "TRUE" contents of folders found inside source-folder are also fetched.
  244. * Defaults to "FALSE".
  245. * @var boolean
  246. * @access public
  247. */
  248. public $css_recursive = FALSE;
  249. /**
  250. * Switches on string-mode, when passing styleheet-strings as source
  251. *
  252. * Instead of folders and files to read and parse you can also pass
  253. * stylesheet-code as source. But, this only works if you switch string-mode on.
  254. * Defaults to "FALSE".
  255. * @var boolean
  256. * @access public
  257. * @see $css_source
  258. */
  259. public $css_stringmode = FALSE;
  260. /**
  261. * Defines the base-folder for all files referenced in stylesheet-string
  262. *
  263. * When being in string-mode, the booster prepends this path going out from the caller-location
  264. * in order to find all referenced files.
  265. * Defaults to "./".
  266. * @var string
  267. * @access public
  268. * @see $css_stringmode
  269. */
  270. public $css_stringbase = './';
  271. /**
  272. * Used to store the date of last change of a stylesheet-string
  273. *
  274. * Is set to the file-time of the calling script during construction.
  275. * @var integer
  276. * @access private
  277. * @see $css_stringmode
  278. */
  279. private $css_stringtime = 0;
  280. /**
  281. * Used to store if we are dealing with an older IE
  282. *
  283. * Is set to TRUE if IE < 8.
  284. * @var bool
  285. * @access public
  286. */
  287. public $css_mhtml_enabled_ie = FALSE;
  288. // JavaScript specific configuration ///////////////////////////////////////////////////////////////////////////////////////////////////
  289. /**
  290. * Defines source to take the JS from
  291. *
  292. * It accepts foldernames, filenames, multiple files and folders comma-delimited in strings or as array.
  293. * When passing foldernames, containing files will be processed in alphabetical order.
  294. * The variable also accepts a javascript-string if you set js_stringmode to "TRUE"
  295. * Defaults to "js".
  296. * @var mixed
  297. * @access public
  298. * @see $js_stringmode
  299. */
  300. public $js_source = 'js';
  301. /**
  302. * Defines in how many parts the JS output shall be split [Deprecated, still in there for backward compatibility]
  303. *
  304. * Newer browsers support more than 2 concurrent parallel connections
  305. * but NOT for JS-files. So here one single output-file would be best.
  306. * You can still uppen the number of output-files here if like. [Deprecated, still in there for backward compatibility]
  307. * Defaults to "1".
  308. * @var integer
  309. * @access public
  310. */
  311. public $js_totalparts = 1;
  312. /**
  313. * Defines which part to ouput when retrieving JS in multiple parts [Deprecated, still in there for backward compatibility]
  314. *
  315. * Used by accompagning script "booster_js.php" [Deprecated, still in there for backward compatibility]
  316. * Defaults to "0".
  317. * @var integer
  318. * @access public
  319. */
  320. public $js_part = 0;
  321. /**
  322. * Defines if Google Closure Compiler should be used
  323. *
  324. * Used by accompagning script "booster_js.php"
  325. * Defaults to "TRUE".
  326. * @var boolean
  327. * @access public
  328. */
  329. public $js_minify = TRUE;
  330. /**
  331. * Defines if to switch to more powerful a hosted minifier
  332. *
  333. * You can use the full Google Closure Compiler included in CSS-JS-Booster instead of the
  334. * webservice, in order to minify javascript.
  335. * But be carefull, it will only work on dedicated servers with Java installed.
  336. * @var boolean
  337. * @access public
  338. */
  339. public $js_hosted_minifier = FALSE;
  340. /**
  341. * Defines the path to a hosted minifier
  342. *
  343. * Will store the local Google Closure Compiler path relative to this file
  344. * @var string
  345. * @access private
  346. * @see $js_hosted_minifier
  347. */
  348. private $js_hosted_minifier_path = 'compiler/compiler.jar';
  349. /**
  350. * Defines if source-file retrieval shall be recursive
  351. *
  352. * Only matters when passing folders as source-parameter.
  353. * If set to "TRUE" contents of folders found inside source-folder are also fetched.
  354. * Defaults to "FALSE".
  355. * @var boolean
  356. * @access public
  357. */
  358. public $js_recursive = FALSE;
  359. /**
  360. * Switches on string-mode, when passing javascript-strings as source
  361. *
  362. * Instead of folders and files to read and parse you can also pass
  363. * javascript-code as source. But, this only works if you switch string-mode on.
  364. * Defaults to "FALSE".
  365. * @var boolean
  366. * @access public
  367. * @see $js_source
  368. */
  369. public $js_stringmode = FALSE;
  370. /**
  371. * Defines the base-folder for all files referenced in javascript-string
  372. *
  373. * When being in string-mode, the booster prepends this path going out from the caller-location
  374. * in order to find all referenced files.
  375. * Defaults to "./".
  376. * @var string
  377. * @access public
  378. * @see $js_stringmode
  379. */
  380. public $js_stringbase = './';
  381. /**
  382. * Used to store the date of last change of a javascript-string
  383. *
  384. * Is set to the file-time of the calling script during construction.
  385. * @var integer
  386. * @access private
  387. * @see $js_stringmode
  388. */
  389. private $js_stringtime = 0;
  390. /**
  391. * Define if you want the javacript to get executed async or defered (HTML5)
  392. *
  393. * Default is "", file gets loaded and executed instantly, blocking the HTML parser until done.
  394. *
  395. * Set to "async" if you want the script to load while HTML parser is moving on.
  396. * Will get executed as soon as script is loaded. Automatically gets a "defer"-backup-attribute for IE.
  397. * Will get deactivated when document.write() is detected by Booster
  398. *
  399. * Set to "defer" if you want the script to load and execute after HTML parser has finished parsing the page.
  400. * Will get deactivated when document.write() is detected by Booster
  401. *
  402. * @var string
  403. * @access public
  404. */
  405. public $js_executionmode = '';
  406. /**
  407. * Define which JavaScript function to run once a async or defered has reached execution status (HTML5)
  408. *
  409. * Default is "", no JavaScript function is being executed.
  410. *
  411. * Set to any function contained within the boosted JavaScript, e.g. "initialize();"
  412. *
  413. * @var string
  414. * @access public
  415. */
  416. public $js_onload = '';
  417. // Start of functions ///////////////////////////////////////////////////////////////////////////////////////////////////////
  418. /**
  419. * Constructor
  420. *
  421. * Sets @var $css_stringtime to caller file time
  422. *
  423. * @return void
  424. * @access public
  425. */
  426. public function __construct()
  427. {
  428. $this->filestime = filemtime(__FILE__);
  429. $this->document_root = $_SERVER['DOCUMENT_ROOT'];
  430. $this->css_stringtime = filemtime(realpath($_SERVER['SCRIPT_FILENAME']));
  431. $this->css_hosted_minifier_path = realpath(dirname(__FILE__).'/'.$this->css_hosted_minifier_path);
  432. $this->js_stringtime = filemtime(realpath($_SERVER['SCRIPT_FILENAME']));
  433. $this->js_hosted_minifier_path = realpath(dirname(__FILE__).'/'.$this->js_hosted_minifier_path);
  434. // Checking if Apache runs with mod_rewrite
  435. if(function_exists('apache_get_modules') && $apache_modules = apache_get_modules())
  436. {
  437. if(!in_array('mod_rewrite',$apache_modules)) $this->mod_rewrite = FALSE;
  438. }
  439. }
  440. /**
  441. * Looks after some parameters and outputs an error if applicable
  442. *
  443. * @return void
  444. * @access private
  445. */
  446. private function errorcheck()
  447. {
  448. // Calculate absolute path only for this function
  449. $booster_cachedir = str_replace('\\','/',dirname(__FILE__)).'/'.$this->booster_cachedir;
  450. // Throw a warning and quit if cache-directory doesn't exist or isn't writable
  451. if(!@is_dir($booster_cachedir) && !@mkdir($booster_cachedir,0777))
  452. {
  453. $this->errormessage = "\r\nYou need to create a directory \r\n".$this->get_absolute_path($booster_cachedir)."\r\n with CHMOD 0777 rights.\r\nAfterwards, delete your browser's cache and reload.\r\n";
  454. }
  455. // Also check here for the right PHP version
  456. if(strnatcmp(phpversion(),'5.0.0') < 0)
  457. {
  458. $this->errormessage = "\r\nYou need to upgrade to PHP 5 or higher to have CSS-JS-Booster work. You currently are running on PHP ".phpversion().".\r\n";
  459. }
  460. // Check for incorrect document root (e.g. due to Apache's mod_userdir)
  461. if($this->document_root == $_SERVER['DOCUMENT_ROOT'] && substr($this->getpath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$_SERVER['DOCUMENT_ROOT'])),0,2) == '..')
  462. {
  463. $this->errormessage = "\r\n".'$_SERVER[\'DOCUMENT_ROOT\'] variable is set to '.$_SERVER['DOCUMENT_ROOT'].'. But you seem to have a differing real document root. Please set the variable $booster->document_root to reflect your real document root'."\r\n";
  464. }
  465. // Check for incorrect base path (e.g. due to Apache's mod_userdir)
  466. if($this->base_offset.ltrim(str_replace($this->document_root,'',$_SERVER['SCRIPT_FILENAME']),'/') != parse_url($_SERVER['SCRIPT_NAME'],PHP_URL_PATH))
  467. {
  468. $this->errormessage = "\r\n".'$booster->base_offset variable is set to '.$this->base_offset.'. But this server\'s document root seems to be differently offsetted. Please set the variable $booster->base_offset to reflect this offset'."\r\n".$this->base_offset.ltrim(str_replace($this->document_root,'',$_SERVER['SCRIPT_FILENAME']),'/')."\r\n".parse_url($_SERVER['SCRIPT_NAME'],PHP_URL_PATH)."\r\n";
  469. }
  470. }
  471. /**
  472. * Setcachedir calculates correct cache-path once and checks directory's writability
  473. * and adjusts things not adjustable while constructing
  474. *
  475. * @return void
  476. * @access public
  477. */
  478. public function setcachedir()
  479. {
  480. // Turn on strict error reporting when library debug is on
  481. if($this->librarydebug)
  482. {
  483. ini_set("display_errors", 1);
  484. error_reporting(E_ALL);
  485. }
  486. // Check if @var $booster_cachedir_transformed is still "FALSE"
  487. if(!$this->booster_cachedir_transformed)
  488. {
  489. $this->booster_cachedir_transformed = TRUE;
  490. $this->booster_cachedir = str_replace('\\','/',dirname(__FILE__)).'/'.$this->booster_cachedir;
  491. $this->debug_log = $this->booster_cachedir.'/debug_log.txt';
  492. // Automatic cleanup of old files in booster_cache folder, if switched on, and only on sundays
  493. $today = getdate();
  494. if($this->booster_cachedir_autocleanup && $today['wday'] == 0)
  495. {
  496. if(is_dir($this->booster_cachedir))
  497. {
  498. $handle=opendir($this->booster_cachedir);
  499. while(false !== ($file = readdir($handle)))
  500. {
  501. // If it is a file and the filetype matches and it isn't the log file
  502. // and last file access time is one week old or older, then delete
  503. if($file[0] != '.' &&
  504. strtolower(pathinfo($this->booster_cachedir.'/'.$file,PATHINFO_EXTENSION)) == 'txt' &&
  505. $this->booster_cachedir.'/'.$file != $this->debug_log &&
  506. fileatime($this->booster_cachedir.'/'.$file) < ($_SERVER['REQUEST_TIME'] - 604800)
  507. )
  508. {
  509. @unlink($this->booster_cachedir.'/'.$file);
  510. }
  511. }
  512. closedir($handle);
  513. }
  514. }
  515. }
  516. }
  517. /**
  518. * Get_absolute_path calculates absolute path of a any path, stripping all . and ..
  519. *
  520. * @param string $path
  521. * @return string absolute $path
  522. * @access public
  523. */
  524. public function get_absolute_path($path)
  525. {
  526. $path = str_replace(array('/', '\\'), '/', $path);
  527. $parts = array_filter(explode('/', $path), 'strlen');
  528. $absolutes = array();
  529. foreach ($parts as $part) {
  530. if ('.' == $part) continue;
  531. if ('..' == $part) {
  532. array_pop($absolutes);
  533. } else {
  534. $absolutes[] = $part;
  535. }
  536. }
  537. return implode('/', $absolutes);
  538. }
  539. /**
  540. * Getpath calculates the relative path between @var $path1 and @var $path2
  541. *
  542. * @param string $path1
  543. * @param string $path2
  544. * @param string $path1_sep Sets the folder-delimiter, defaults to '/'
  545. * @return string relative path between @var $path1 and @var $path2
  546. * @access public
  547. */
  548. public function getpath($path1 = '',$path2 = '',$path1_sep = '/')
  549. {
  550. $path2 = str_replace('\\','/',realpath($path2));
  551. $path1 = str_replace('\\','/',realpath($path1));
  552. $path2 = explode($path1_sep, $path2);
  553. $path1 = explode($path1_sep, $path1);
  554. $path = '.';
  555. $fix = '';
  556. $diff = 0;
  557. for($i = -1; ++$i < max(($rC = count($path2)), ($dC = count($path1)));)
  558. {
  559. if(isset($path2[$i]) and isset($path1[$i]))
  560. {
  561. if($diff)
  562. {
  563. $path .= $path1_sep.'..';
  564. $fix .= $path1_sep.$path1[$i];
  565. continue;
  566. }
  567. if($path2[$i] != $path1[$i])
  568. {
  569. $diff = 1;
  570. $path .= $path1_sep.'..';
  571. $fix .= $path1_sep.$path1[$i];
  572. continue;
  573. }
  574. }
  575. elseif(!isset($path2[$i]) and isset($path1[$i]))
  576. {
  577. for($j = $i-1; ++$j < $dC;)
  578. {
  579. $fix .= $path1_sep.$path1[$j];
  580. }
  581. break;
  582. }
  583. elseif(isset($path2[$i]) and !isset($path1[$i]))
  584. {
  585. for($j = $i-1; ++$j < $rC;)
  586. {
  587. $fix = $path1_sep.'..'.$fix;
  588. }
  589. break;
  590. }
  591. }
  592. $pathfix = $path.$fix;
  593. // Remove any unneccessary "./"
  594. $pathfix = preg_replace('/(?<!\.)\.\//','',$pathfix);
  595. return $pathfix;
  596. }
  597. /**
  598. * Getfiles returns all files of a certain type within a folder
  599. *
  600. * @param string $source folder to look for files
  601. * @param string $type sets file-type/suffix (for security reasons)
  602. * @param boolean $recursive tells the script to scan all subfolders, too
  603. * @param array $files prepopulated array of files to append to and return
  604. * @return array filenames sorted alphabetically
  605. * @access protected
  606. */
  607. public function getfiles($source = '',$type = '',$recursive = FALSE,$files = array())
  608. {
  609. // Remove any trailing slash
  610. $source = rtrim($source,'/');
  611. // Check if @var $source really is a folder
  612. if(is_dir(str_replace('\\','/',dirname(__FILE__)).'/'.$source))
  613. {
  614. // For library debugging purposes we log findings
  615. if($this->librarydebug)
  616. {
  617. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfiles detected a folder:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  618. }
  619. $handle=opendir(str_replace('\\','/',dirname(__FILE__)).'/'.$source);
  620. while(false !== ($file = readdir($handle)))
  621. {
  622. if($file[0] != '.')
  623. {
  624. // If it is a folder
  625. if(is_dir(str_replace('\\','/',dirname(__FILE__)).'/'.$source.'/'.$file))
  626. {
  627. // If the @var $recursive is set to "TRUE" start fetching the subfolder
  628. if($recursive) $files = $this->getfiles($source.'/'.$file,$type,$recursive,$files);
  629. }
  630. // If it is a file and if the filetype matches
  631. else if(strtolower(pathinfo(str_replace('\\','/',dirname(__FILE__)).'/'.$source.'/'.$file,PATHINFO_EXTENSION)) == $type)
  632. {
  633. // For library debugging purposes we log findings
  634. if($this->librarydebug)
  635. {
  636. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfiles detected a file inside a folder:\r\n-----------------\r\n".$file.' inside '.$source."\r\n-----------------\r\n",FILE_APPEND);
  637. }
  638. // Add to file-list
  639. array_push($files,$source.'/'.$file);
  640. }
  641. }
  642. }
  643. closedir($handle);
  644. // Sort list alphabetically
  645. array_multisort($files, SORT_ASC, $files);
  646. }
  647. // If @var $source is a file, add it to the file-list
  648. elseif(is_file($source) && strtolower(pathinfo($source,PATHINFO_EXTENSION)) == $type)
  649. {
  650. // For library debugging purposes we log findings
  651. if($this->librarydebug)
  652. {
  653. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfiles detected a file:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  654. }
  655. array_push($files,$source);
  656. }
  657. // Return file-list
  658. return $files;
  659. }
  660. /**
  661. * Getfilestime returns the timestamp of the newest file of a certain type within a folder
  662. *
  663. * @param mixed $source single folder or multiple comma-delimited folders or array of folders in which to look for files
  664. * @param string $type sets file-type/suffix (for security reasons)
  665. * @param boolean $recursive tells the script to scan all subfolders, too
  666. * @param integer $this->filestime prepopulated timestamp to also check against
  667. * @return integer timestamp of the newest of all scanned files
  668. * @access public
  669. */
  670. public function getfilestime($source = '',$type = '',$recursive = FALSE)
  671. {
  672. // Load @var $source with an array made form @var $source parameter
  673. if(is_array($source)) $sources = $source;
  674. else $sources = explode(',',$source);
  675. reset($sources);
  676. for($j=0;$j<sizeof($sources);$j++)
  677. {
  678. // For library debugging purposes we log findings
  679. if($this->librarydebug)
  680. {
  681. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfilestime found sources:\r\n-----------------\r\n".implode(', ',$sources)."\r\n-----------------\r\n",FILE_APPEND);
  682. }
  683. $source = current($sources);
  684. // Remove any trailing slash
  685. $source = rtrim($source,'/');
  686. // Check if @var $source really is a folder
  687. if(is_dir(str_replace('\\','/',dirname(__FILE__)).'/'.$source))
  688. {
  689. // For library debugging purposes we log findings
  690. if($this->librarydebug)
  691. {
  692. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfilestime detected a folder:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  693. }
  694. // Get a list (array) of all folders and files inside that folder
  695. $files = $this->getfiles($source,$type,$recursive);
  696. // For library debugging purposes we log findings
  697. if($this->librarydebug)
  698. {
  699. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfilestime retrieved folder contents:\r\n-----------------\r\n".implode(', ',$files)."\r\n-----------------\r\n",FILE_APPEND);
  700. }
  701. // Check all list-item's timestamps
  702. for($i=0;$i<count($files);$i++)
  703. {
  704. // In case it is a folder, run this funtion on the folder
  705. if(is_dir(str_replace('\\','/',dirname(__FILE__)).'/'.$files[$i]))
  706. {
  707. if($recursive) $this->filestime = $this->getfilestime($files[$i],$type,$recursive);
  708. }
  709. // In case it is a file, get its timestamp
  710. if(is_file(str_replace('\\','/',dirname(__FILE__)).'/'.$files[$i]))
  711. {
  712. // For library debugging purposes we log findings
  713. if($this->librarydebug)
  714. {
  715. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfilestime detected a file inside a folder:\r\n-----------------\r\n".$files[$i].' inside '.$source."\r\n-----------------\r\n",FILE_APPEND);
  716. }
  717. if(filemtime(str_replace('\\','/',dirname(__FILE__)).'/'.$files[$i]) > $this->filestime) $this->filestime = filemtime(str_replace('\\','/',dirname(__FILE__)).'/'.$files[$i]);
  718. }
  719. }
  720. }
  721. // If @var $source is a file check its file time
  722. elseif(is_file(str_replace('\\','/',dirname(__FILE__)).'/'.$source) && filemtime(str_replace('\\','/',dirname(__FILE__)).'/'.$source) > $this->filestime && strtolower(pathinfo(str_replace('\\','/',dirname(__FILE__)).'/'.$source,PATHINFO_EXTENSION)) == $type)
  723. {
  724. // For library debugging purposes we log findings
  725. if($this->librarydebug)
  726. {
  727. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." getfilestime detected a file:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  728. }
  729. $this->filestime = filemtime(str_replace('\\','/',dirname(__FILE__)).'/'.$source);
  730. }
  731. next($sources);
  732. }
  733. }
  734. /**
  735. * Getfilescontents puts together all contents from files of a certain type within a folder
  736. *
  737. * @param string $source folder to look for files or file or code-string
  738. * @param string $type sets file-type/suffix (for security reasons)
  739. * @param boolean $recursive tells the script to scan all subfolders, too
  740. * @param string $filescontent prepopulated string to append to and return
  741. * @return string Return all file contents
  742. * @access protected
  743. */
  744. protected function getfilescontents($source = '',$type = '',$recursive = FALSE,$filescontent = '')
  745. {
  746. // Remove any trailing slash
  747. $source = rtrim($source,'/');
  748. // Prepare content storage
  749. $currentfilecontent = '';
  750. // If @var $source is a folder, get file-list and call itself on them
  751. if(is_dir($source))
  752. {
  753. $files = $this->getfiles($source,$type,$recursive);
  754. for($i=0;$i<count($files);$i++) $filescontent .= $this->getfilescontents($files[$i],$type,$recursive);
  755. }
  756. // If @var $source is a file
  757. elseif(is_file($source))
  758. {
  759. if(strtolower(pathinfo($source,PATHINFO_EXTENSION)) == $type)
  760. {
  761. if($this->librarydebug) file_put_contents($this->debug_log,"processing file: ".$source."\r\n",FILE_APPEND);
  762. $currentfilecontent = file_get_contents($source);
  763. }
  764. }
  765. // If @var $source is a string and we are in stringmode
  766. elseif(
  767. ($type == 'css' && $this->css_stringmode) ||
  768. ($type == 'js' && $this->js_stringmode)
  769. ) $currentfilecontent = $source;
  770. // If @var $source can't be identified
  771. else
  772. {
  773. if($type == 'css') $currentfilecontent = "\r\n".'/* Could not locate '.$source.' */'."\r\n";
  774. if($type == 'js') $currentfilecontent = "\r\n".'// Could not locate '.$source."\r\n";
  775. }
  776. // Find and resolve import-rules
  777. if($type == 'css')
  778. {
  779. preg_match_all('/@import\surl\([\'"]*?([^\'")]+\.css)[\'"]*?\);/ims',$currentfilecontent,$treffer,PREG_PATTERN_ORDER);
  780. for($i=0;$i<count($treffer[0]);$i++)
  781. {
  782. // Buffer findings
  783. $import = $treffer[1][$i];
  784. // If it is a full URL, extract only the path
  785. if(substr($import,0,strlen($_SERVER['SERVER_NAME']) + 7) == 'http://'.$_SERVER['SERVER_NAME']) $import = parse_url($import,PHP_URL_PATH);
  786. // If it is an absolute path
  787. if(substr($import,0,1) == '/') $importfile = str_replace('\\','/',realpath(rtrim($this->document_root,'/'))).$import;
  788. // Else if it is a relative path
  789. else $importfile = str_replace('\\','/',realpath(dirname($source))).'/'.$import;
  790. if($this->librarydebug) file_put_contents($this->debug_log,"found file in @import-rule: ".$importfile."\r\n",FILE_APPEND);
  791. $diroffset = dirname($treffer[1][$i]);
  792. if(file_exists($importfile))
  793. {
  794. $importfilecontent = $this->getfilescontents($importfile,$type);
  795. $importfilecontent = preg_replace('/(url\([\'"]*)([^\/])/ims','\1'.$diroffset.'/\2',$importfilecontent);
  796. $currentfilecontent = str_replace($treffer[0][$i],$importfilecontent."\r\n",$currentfilecontent);
  797. }
  798. // @todo media-type sensivity
  799. #if(trim($treffer[2][$i]) != '') $mediatype = trim($treffer[2][$i]);
  800. #else $mediatype = 'all';
  801. #if($this->librarydebug) $filescontent .= "/* importfile: ".$importfile." */\r\n";
  802. #if(file_exists($importfile)) $currentfilecontent = str_replace($treffer[0][$i],"@media ".$mediatype." {\r\n".$this->getfilescontents($importfile,$type)."}\r\n",$currentfilecontent);
  803. }
  804. }
  805. // Append to @var $filescontent
  806. $filescontent .= $currentfilecontent."\r\n\r\n";
  807. return $filescontent;
  808. }
  809. /**
  810. * Css_minify does some soft minifications to the stylesheets, avoiding damage by optimizing too much
  811. *
  812. * Replaces CSSTidy 1.3 which in some cases was destroying stylesheets
  813. * Removing unnecessessary whitespaces, tabs and newlines
  814. * Leaving commments in there as they may be browser hacks or needed to fulfill the terms of a license
  815. *
  816. * @param string styles-string
  817. * @return string minified styles-string
  818. * @access protected
  819. */
  820. protected function css_minify($filescontent = '')
  821. {
  822. // For library debugging purposes
  823. if($this->librarydebug)
  824. {
  825. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_minify input:\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  826. }
  827. // If somebody wants to use the included minifier
  828. if($this->css_hosted_minifier && is_readable($this->css_hosted_minifier_path))
  829. {
  830. // Implementation by Vincent Voyer (http://twitter.com/vvoyer)
  831. // must create tmp files because closure compiler can't work with direct input..
  832. $tmp_file_path = sys_get_temp_dir().'/'.uniqid();
  833. file_put_contents($tmp_file_path, $filescontent);
  834. $filescontent = `java -jar $this->css_hosted_minifier_path $tmp_file_path --type css --charset utf-8`;
  835. unlink($tmp_file_path);
  836. }
  837. // Use own subtle minification implementation
  838. else
  839. {
  840. // Backup any values within single or double quotes
  841. preg_match_all('/(\'[^\']*?\'|"[^"]*?")/ims',$filescontent,$treffer,PREG_PATTERN_ORDER);
  842. for($i=0;$i<count($treffer[1]);$i++)
  843. {
  844. $filescontent = str_replace($treffer[1][$i],'##########'.$i.'##########',$filescontent);
  845. }
  846. // For library debugging purposes
  847. if($this->librarydebug)
  848. {
  849. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_minify after string backup:\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  850. }
  851. // Remove traling semicolon of selector's last property
  852. $filescontent = preg_replace('/;[\s\r\n\t]*?}[\s\r\n\t]*/ims',"}\r\n",$filescontent);
  853. // Remove any whitespaces/tabs/newlines between semicolon and property-name
  854. $filescontent = preg_replace('/;[\s\r\n\t]*?([\r\n]?[^\s\r\n\t])/ims',';$1',$filescontent);
  855. // Remove any whitespaces/tabs/newlines surrounding property-colon
  856. $filescontent = preg_replace('/[\s\r\n\t]*:[\s\r\n\t]*?([^\s\r\n\t])/ims',':$1',$filescontent);
  857. // Remove any whitespaces/tabs/newlines surrounding selector-comma
  858. $filescontent = preg_replace('/[\s\r\n\t]*,[\s\r\n\t]*?([^\s\r\n\t])/ims',',$1',$filescontent);
  859. // Remove any whitespaces/tabs/newlines surrounding opening parenthesis
  860. $filescontent = preg_replace('/[\s\r\n\t]*{[\s\r\n\t]*?([^\s\r\n\t])/ims','{$1',$filescontent);
  861. // Remove any whitespaces/tabs/newlines between numbers and units
  862. $filescontent = preg_replace('/([\d\.]+)[\s\r\n\t]+(px|em|pt|%)/ims','$1$2',$filescontent);
  863. // Shorten zero-values
  864. $filescontent = preg_replace('/([^\d\.]0)(px|em|pt|%)/ims','$1',$filescontent);
  865. // Constrain multiple newlines
  866. $filescontent = preg_replace('/[\r\n]+/ims',"\n",$filescontent);
  867. // Constrain multiple whitespaces
  868. $filescontent = preg_replace('/\p{Zs}+/ims',' ',$filescontent);
  869. // Restore backupped values within single or double quotes
  870. for($i=0;$i<count($treffer[1]);$i++)
  871. {
  872. $filescontent = str_replace('##########'.$i.'##########',$treffer[1][$i],$filescontent);
  873. }
  874. }
  875. // For library debugging purposes we log minify output
  876. if($this->librarydebug)
  877. {
  878. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_minify output:\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  879. }
  880. return $filescontent;
  881. }
  882. /**
  883. * Css_datauri embeds external files like images into the stylesheet
  884. *
  885. * Depending on the browser and operating system, this funtion does the following:
  886. * IE 6 and 7 on XP and IE 7 on Vista or higher don't understand data-URIs, but a proprietary format named MHTML.
  887. * So they get served that.
  888. * Any other common browser understands data-URIs, even IE 8 up to a file-size of 24KB, so those get data-URI-embedding
  889. * IE 6 on Vista or higher doesn't understand any of the embeddings so it just gets standard styles.
  890. *
  891. * @param integer $this->filestime timestamp of the last modification of the content following
  892. * @param string $filescontent stylesheet-content
  893. * @return string stylesheet-content with data-URI or MHTML embeddings
  894. * @see function Setcachedir
  895. * @access protected
  896. */
  897. protected function css_datauri($filescontent = '',$dir = '')
  898. {
  899. // For library debugging purposes we log file contents
  900. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n",FILE_APPEND);
  901. if($this->librarydebug && isset($_SERVER['HTTP_USER_AGENT'])) file_put_contents($this->debug_log,"HTTP_USER_AGENT: ".$_SERVER['HTTP_USER_AGENT']."\r\n",FILE_APPEND);
  902. if($this->librarydebug) file_put_contents($this->debug_log,"Browser->Family: ".$this->browser->family."\r\n",FILE_APPEND);
  903. if($this->librarydebug) file_put_contents($this->debug_log,"Browser->Familyversion: ".floatval($this->browser->familyversion)."\r\n",FILE_APPEND);
  904. if($this->librarydebug) file_put_contents($this->debug_log,"Browser->Platform: ".$this->browser->platform."\r\n",FILE_APPEND);
  905. if($this->librarydebug) file_put_contents($this->debug_log,"Browser->Platformversion: ".floatval($this->browser->platformversion)."\r\n",FILE_APPEND);
  906. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n",FILE_APPEND);
  907. // Call Setcachedir to make sure, cache-path has been calculated
  908. $this->setcachedir();
  909. // Prepare different RegExes
  910. // Media-files (currently images and fonts)
  911. $regex_embed = '/url\([\'"]*(.+?\.)(gif|png|jpg|otf|ttf|woff)[\'"]*\)/msi';
  912. $regex_embed_ie = '/url\([\'"]*(.+?\.)(gif|png|jpg|eot)[\'"]*\)/msi';
  913. // identifier for the cache-files
  914. $identifier = md5($filescontent);
  915. // --------------------------------------------------------------------------------------
  916. // If any MHTML-capable IE browser
  917. if($this->css_mhtml_enabled_ie)
  918. {
  919. // The @var $mhtmlarray collects references to all processed images so that we can look up if we already have embedded a certain image
  920. $mhtmlarray = array();
  921. // The external absolute path to where "booster_mhtml.php" resides
  922. $mhtmlpath = $this->base_offset.$this->getpath(str_replace('\\','/',dirname(__FILE__)),rtrim($this->document_root,'/'));
  923. // Cachefile for the extra MHTML-data
  924. $mhtmlfile = $this->booster_cachedir.'/'.$identifier.'_datauri_mhtml_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  925. // Get Domainname
  926. if(isset($_SERVER['SCRIPT_URI']))
  927. {
  928. $mhtmlhost = parse_url($_SERVER['SCRIPT_URI'],PHP_URL_HOST);
  929. }
  930. else
  931. {
  932. $mhtmlhost = $_SERVER['HTTP_HOST'];
  933. }
  934. // Start putting together the styles and MHTML
  935. $mhtmlcontent = "Content-Type: multipart/related; boundary=\"_ANY_STRING_WILL_DO_AS_A_SEPARATOR\"\r\n\r\n";
  936. preg_match_all($regex_embed_ie,$filescontent,$treffer,PREG_PATTERN_ORDER);
  937. if(!$this->debug) for($i=0;$i<count($treffer[0]);$i++)
  938. {
  939. // Calculate full image path
  940. // If it is an absolute path
  941. if(substr($treffer[1][$i],0,1) == '/')
  942. {
  943. $imagefile = rtrim($this->document_root,'/').$treffer[1][$i].$treffer[2][$i];
  944. }
  945. // If it is a relative path
  946. else
  947. {
  948. $imagefile = str_replace('\\','/',dirname(__FILE__)).'/'.$dir.'/'.$treffer[1][$i].$treffer[2][$i];
  949. }
  950. // Create a new anchor-tag for the MHTML-file
  951. $imagetag = 'img'.$i;
  952. // If image-file exists and if file-size is lower than 24 KB
  953. if(file_exists($imagefile) && filesize($imagefile) < 24000)
  954. {
  955. // Replace reference to image with reference to MHTML-file with corresponding anchor
  956. ((isset($_GET['cachedir'])) ? $booster_cachedir = str_replace('>','..',rtrim(preg_replace('/[^a-z0-9,\-_\.\/>]/i','',$_GET['cachedir']),'/')) : $booster_cachedir = 'booster_cache');
  957. $filescontent = str_replace($treffer[0][$i],'url(mhtml:http://'.$mhtmlhost.$mhtmlpath.'/booster_mhtml.php?dir='.$identifier.'&cachedir='.$booster_cachedir.'&nocache='.$this->filestime.'!'.$imagetag.')',$filescontent);
  958. // Look up in our list if we did not already process that exact file, if not append it
  959. if(!isset($mhtmlarray[$imagetag]))
  960. {
  961. $mhtmlcontent .= "--_ANY_STRING_WILL_DO_AS_A_SEPARATOR\r\n";
  962. $mhtmlcontent .= "Content-Location:".$imagetag."\r\n";
  963. $mhtmlcontent .= "Content-Transfer-Encoding:base64\r\n\r\n";
  964. $mhtmlcontent .= base64_encode(file_get_contents($imagefile))."==\r\n";
  965. // Put file on our processed-list
  966. $mhtmlarray[$imagetag] = 1;
  967. }
  968. }
  969. }
  970. // Fix the caching problems of IE7, see: http://www.phpied.com/the-proper-mhtml-syntax/
  971. $mhtmlcontent .= "--_ANY_STRING_WILL_DO_AS_A_SEPARATOR--\r\n";
  972. $mhtmlcontent .= "\r\n\r\n";
  973. // Scan for any left file-references and adjust their path
  974. $filescontent = $this->css_datauri_cleanup($filescontent,$dir);
  975. // Store the cache-files
  976. @file_put_contents($mhtmlfile,$mhtmlcontent);
  977. }
  978. // If any modern data-URI-compatible browser
  979. else
  980. {
  981. preg_match_all($regex_embed,$filescontent,$treffer,PREG_PATTERN_ORDER);
  982. if(!$this->debug) for($i=0;$i<count($treffer[0]);$i++)
  983. {
  984. // Calculate full image path
  985. // If it is an absolute path
  986. if(substr($treffer[1][$i],0,1) == '/')
  987. {
  988. $imagefile = rtrim($this->document_root,'/').$treffer[1][$i].$treffer[2][$i];
  989. }
  990. // If it is a relative path
  991. else
  992. {
  993. $imagefile = str_replace('\\','/',dirname(__FILE__)).'/'.$dir.'/'.$treffer[1][$i].$treffer[2][$i];
  994. }
  995. if($this->debug) $filescontent .= "/* embed-file: ".$imagefile." */\r\n";
  996. // Switch to right MIME-type
  997. switch(strtolower($treffer[2][$i]))
  998. {
  999. default:
  1000. case 'gif':
  1001. case 'jpg':
  1002. case 'png':
  1003. $mimetype = 'image/'.strtolower($treffer[2][$i]);
  1004. break;
  1005. case 'eot':
  1006. $mimetype = 'application/vnd.ms-fontobject';
  1007. break;
  1008. case 'otf':
  1009. case 'ttf':
  1010. case 'woff':
  1011. $mimetype = 'application/octet-stream';
  1012. break;
  1013. }
  1014. // If image-file exists and if file-size is lower than 24 KB
  1015. if(file_exists($imagefile) && filesize($imagefile) < 24000) $filescontent = str_replace($treffer[0][$i],'url(data:'.$mimetype.';base64,'.base64_encode(file_get_contents($imagefile)).')',$filescontent);
  1016. }
  1017. // Scan for any left file-references and adjust their path
  1018. $filescontent = $this->css_datauri_cleanup($filescontent,$dir);
  1019. }
  1020. // --------------------------------------------------------------------------------------
  1021. return $filescontent;
  1022. }
  1023. /**
  1024. * Css_datauri_cleanup prepends $dir to the path of all file-references found
  1025. *
  1026. * @param string $filescontent contents to scan
  1027. * @param string $dir folder name to prepend
  1028. * @return string content with adjusted paths
  1029. * @access protected
  1030. */
  1031. protected function css_datauri_cleanup($filescontent = '',$dir = '')
  1032. {
  1033. // Calculate absolute path for booster-folder
  1034. $booster_path = '/'.$this->getpath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$this->document_root));
  1035. // Scan for any left file-references and adjust their path
  1036. $regex_url = '/(url\([\'"]??)([^\'"\)]+?\.[^\'"\)]+?)([\'"]??\))/msi';
  1037. preg_match_all($regex_url,$filescontent,$treffer,PREG_PATTERN_ORDER);
  1038. for($i=0;$i<count($treffer[0]);$i++)
  1039. {
  1040. $search = $treffer[1][$i].$treffer[2][$i].$treffer[3][$i];
  1041. $replace = $treffer[1][$i].$booster_path.'/';
  1042. if($this->css_stringmode) $path_prefix = $this->css_stringmode;
  1043. else $path_prefix = $dir;
  1044. if($path_prefix != '') $replace .= $path_prefix.'/';
  1045. $replace .= $treffer[2][$i].$treffer[3][$i];
  1046. if(
  1047. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),0,5) != 'http:' &&
  1048. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),0,6) != 'https:' &&
  1049. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),0,5) != 'data:' &&
  1050. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),0,6) != 'mhtml:' &&
  1051. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),0,1) != '/' &&
  1052. substr(str_replace(array('"',"'"),'',$treffer[2][$i]),strlen(str_replace(array('"',"'"),'',$treffer[2][$i])) - 4,4) != '.htc'
  1053. ) $filescontent = str_replace($search,$replace,$filescontent);
  1054. }
  1055. return $filescontent;
  1056. }
  1057. /**
  1058. * Css_split takes a multiline CSS-string and splits it according to @var $css_totalparts and @var $css_part
  1059. *
  1060. * @param string $filescontent contents to split
  1061. * @return string requested part-number of splitted content
  1062. * @access protected
  1063. */
  1064. protected function css_split($filescontent = '')
  1065. {
  1066. // If sum of parts is 1 or requested part-number is 0 return full string
  1067. if($this->css_totalparts == 1 || $this->css_part == 0 || $this->css_stringmode)
  1068. {
  1069. // For library debugging purposes we log file contents
  1070. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split input content (solo part ".$this->css_part."):\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  1071. return $filescontent;
  1072. }
  1073. // Else process string
  1074. else
  1075. {
  1076. // Identifier for split-files
  1077. $identifier = md5($filescontent);
  1078. // If any MHTML-capable IE browser
  1079. if($this->css_mhtml_enabled_ie) $cachefilesuffix = 'datauri_ie';
  1080. // If any modern data-URI-compatible browser
  1081. else $cachefilesuffix = 'datauri';
  1082. // Since split processing consumes a lot of time we also cache here
  1083. $cachefilecontent = $this->booster_cachedir.'/'.$identifier.'_splitcontent_'.$cachefilesuffix.'_cache.txt';
  1084. $cachefiledata = $this->booster_cachedir.'/'.$identifier.'_splitdata_'.$cachefilesuffix.'_cache.txt';
  1085. // For library debugging purposes we log file contents
  1086. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split input content (part ".$this->css_part."):\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  1087. if(file_exists($cachefilecontent) && file_exists($cachefiledata))
  1088. {
  1089. $filescontent = file_get_contents($cachefilecontent);
  1090. $line_infos = unserialize(file_get_contents($cachefiledata));
  1091. // For library debugging purposes we log file contents
  1092. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split data files found (part ".$this->css_part."):\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  1093. }
  1094. else
  1095. {
  1096. // Backup any values within single or double quotes
  1097. preg_match_all('/(\'[^\']*?\'|"[^"]*?")/ims',$filescontent,$treffer,PREG_PATTERN_ORDER);
  1098. for($i=0;$i<count($treffer[1]);$i++)
  1099. {
  1100. $filescontent = str_replace($treffer[1][$i],'##########'.$i.'##########',$filescontent);
  1101. }
  1102. // Insert newline at certain points as preparation for parsing
  1103. $filescontent = str_replace("{","\n{",$filescontent);
  1104. $filescontent = str_replace("}","\n}",$filescontent);
  1105. $filescontent = str_replace("/*","\n/*",$filescontent);
  1106. $filescontent = str_replace("*/","\n*/",$filescontent);
  1107. // Restore backupped values within single or double quotes
  1108. for($i=0;$i<count($treffer[1]);$i++)
  1109. {
  1110. $filescontent = str_replace('##########'.$i.'##########',$treffer[1][$i],$filescontent);
  1111. }
  1112. // For library debugging purposes we log file contents
  1113. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split prepared content (part ".$this->css_part."):\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  1114. // In order for @-rule-blocks like @media-blocks, but also @font-face not to get stupidly ripped apart
  1115. // while splitting the file into multiple parts, we need to parse it take some notes for us later.
  1116. // In this array we will store some information for later when we split
  1117. $line_infos = array();
  1118. // As we split line-based we will take notices by lines
  1119. $currentline = 0;
  1120. // Here we note during the parsing if we are currently inside a block or not, and of which type
  1121. $currentblock = '';
  1122. $currentblockancestors = array();
  1123. // Here we note during the parsing if we are currently inside a comment or not
  1124. $currentcomment = '';
  1125. // Here we note during the parsing if we are currently inside a comment or not
  1126. $comment_on = 0;
  1127. // Here we note during the parsing if we are currently inside a property and if yes save its selector
  1128. $currentselector = '';
  1129. // Here we note during the parsing if we are currently inside a selector's properties
  1130. $property_on = 0;
  1131. // Here we note during the parsing if we are currently inside a singlequote-protected string
  1132. $singlequote_on = 0;
  1133. // Here we note during the parsing if we are currently inside a doublequote-protected string
  1134. $doublequote_on = 0;
  1135. // Now we cycle through every character
  1136. for($i=0;$i<strlen($filescontent);$i++)
  1137. {
  1138. // Here we store current character and do different things depending on what it is
  1139. $currentchar = substr($filescontent,$i,1);
  1140. switch($currentchar)
  1141. {
  1142. // We run into or out of a single-quoted file-reference or generated content, remember that
  1143. case "'":
  1144. if($doublequote_on == 0 && $comment_on == 0) $singlequote_on = 1 - $singlequote_on;
  1145. break;
  1146. // We run into or out of a double-quoted file-reference or generated content, remember that
  1147. case '"':
  1148. if($singlequote_on == 0 && $comment_on == 0) $doublequote_on = 1 - $doublequote_on;
  1149. break;
  1150. // We probably ran into a comment
  1151. case '*':
  1152. if($singlequote_on == 0 && $doublequote_on == 0 && $comment_on == 0)
  1153. {
  1154. if($comment_on == 0 && substr($filescontent,$i - 1,1) == '/')
  1155. {
  1156. // Remember that we are inside some /*-comment
  1157. $comment_on = 1;
  1158. $currentcomment = 'comment';
  1159. // For library debugging purposes we log pre-parser structure findings
  1160. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"* comment start (part ".$this->css_part."): ".$currentcomment."\r\n",FILE_APPEND);
  1161. }
  1162. elseif(preg_match('/\A([a-zA-Z\.#\*:][^\{\}@;\/]+)\{/ims', substr($filescontent,$i), $treffer) == 1)
  1163. {
  1164. // remember in what selector we are
  1165. $currentselector = $treffer[1];
  1166. // remove selector for now (we will put it back in later)
  1167. $filescontent = substr_replace($filescontent,'',$i,strlen($currentselector) + 1);
  1168. // store selector for this line
  1169. $line_infos[$currentline]['selector'] = $currentselector;
  1170. // Remember that we are inside some selector's properties
  1171. $property_on = 1;
  1172. $i--;
  1173. // For library debugging purposes we log pre-parser structure findings
  1174. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"} selector start (part ".$this->css_part."): ".$currentselector." -> ".$line_infos[$currentline]['selector']."\r\n",FILE_APPEND);
  1175. }
  1176. }
  1177. break;
  1178. // We maybe leave a comment
  1179. case '/':
  1180. if($singlequote_on == 0 && $doublequote_on == 0 && $comment_on == 1 && substr($filescontent,$i - 1,1) == '*')
  1181. {
  1182. // Remember that we finished being inside some comment
  1183. $comment_on = 0;
  1184. $currentcomment = '';
  1185. // For library debugging purposes we log pre-parser structure findings
  1186. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"/ comment end (part ".$this->css_part."): ".$currentcomment."\r\n",FILE_APPEND);
  1187. }
  1188. break;
  1189. // Newline
  1190. case "\n":
  1191. if(!isset($line_infos[$currentline])) $line_infos[$currentline] = array();
  1192. // If not yet done: store @-rule for this line
  1193. if(!isset($line_infos[$currentline]['block'])) $line_infos[$currentline]['block'] = $currentblock;
  1194. // Store type of comment for this line
  1195. $line_infos[$currentline]['comment'] = $currentcomment;
  1196. // Store selector for this line
  1197. if(!isset($line_infos[$currentline]['selector'])) $line_infos[$currentline]['selector'] = $currentselector;
  1198. // For library debugging purposes we log pre-parser structure findings
  1199. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"-----------------\r\n
  1200. newline (part ".$this->css_part."):\r\n
  1201. currentline: ".$currentline."\r\n
  1202. currentblock: ".$currentblock."\r\n
  1203. block: ".$line_infos[$currentline]['block']."\r\n
  1204. currentcomment: ".$currentcomment."\r\n
  1205. comment: ".$line_infos[$currentline]['comment']."\r\n
  1206. currentselector: ".$currentselector."\r\n
  1207. selector: ".$line_infos[$currentline]['selector']."\r\n
  1208. -----------------\r\n",FILE_APPEND);
  1209. $currentline++;
  1210. if(!isset($line_infos[$currentline])) $line_infos[$currentline] = array();
  1211. break;
  1212. // That's what we are here for: is this a block-creating @-rule like @media{} or @font-face{}?
  1213. case "@":
  1214. if($singlequote_on == 0 && $doublequote_on == 0 && $comment_on == 0)
  1215. {
  1216. if(preg_match('/\A@([^\{\}@]+)\{/ims', substr($filescontent,$i), $treffer) == 1)
  1217. {
  1218. // remember in what @-rule we are
  1219. if($currentblock != '') array_push($currentblockancestors,$currentblock);
  1220. $currentblock = $treffer[1];
  1221. // remove @-rule-opener for now (we will put it back in later)
  1222. $filescontent = substr_replace($filescontent,'',$i,strlen($currentblock) + 2);
  1223. // store @-rule for this line
  1224. $line_infos[$currentline]['block'] = $currentblock;
  1225. $line_infos[$currentline]['blockclosing'] = FALSE;
  1226. $i--;
  1227. // For library debugging purposes we log pre-parser structure findings
  1228. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"@ block start (part ".$this->css_part."): ".$currentblock." -> ".$line_infos[$currentline]['block']."\r\n",FILE_APPEND);
  1229. }
  1230. }
  1231. break;
  1232. // This closing parenthesis could be closing some selector's properties or an @-rule, lets see...
  1233. case "}":
  1234. if($singlequote_on == 0 && $doublequote_on == 0 && $comment_on == 0)
  1235. {
  1236. // We are currently inside some selector's properties
  1237. if($property_on == 1)
  1238. {
  1239. $property_on = 0;
  1240. // remember that we are in no @-rule
  1241. $currentselector = '';
  1242. // Store selector for this line
  1243. $line_infos[$currentline]['selector'] = $currentselector;
  1244. // Remove closing parenthesis for now (we will put it back in later)
  1245. $filescontent = substr_replace($filescontent,'',$i,1);
  1246. $i--;
  1247. // For library debugging purposes we log pre-parser structure findings
  1248. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"} selector end (part ".$this->css_part."): ".$currentselector." -> ".$line_infos[$currentline]['selector']."\r\n",FILE_APPEND);
  1249. }
  1250. // Or else it must be a closing parenthesis of an @-rule
  1251. else
  1252. {
  1253. // remember that we are in no @-rule
  1254. if(count($currentblockancestors) > 0) $currentblock = array_pop($currentblockancestors);
  1255. else $currentblock = '';
  1256. // Store no-@-rule for this line
  1257. $line_infos[$currentline]['block'] = $currentblock;
  1258. $line_infos[$currentline]['blockclosing'] = TRUE;
  1259. // Remove closing parenthesis for now (we will put it back in later)
  1260. $filescontent = substr_replace($filescontent,'',$i,1);
  1261. $i--;
  1262. // For library debugging purposes we log pre-parser structure findings
  1263. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log," } block end (part ".$this->css_part."): ".$currentblock." -> ".$line_infos[$currentline]['block']."\r\n",FILE_APPEND);
  1264. }
  1265. }
  1266. break;
  1267. default:
  1268. if($singlequote_on == 0 && $doublequote_on == 0 && $comment_on == 0 && $property_on == 0)
  1269. {
  1270. if(preg_match('/\A([a-zA-Z\.#\*:][^\{\}@;\/]+)\{/ims', substr($filescontent,$i), $treffer) == 1)
  1271. {
  1272. // remember in what selector we are
  1273. $currentselector = $treffer[1];
  1274. // remove selector for now (we will put it back in later)
  1275. $filescontent = substr_replace($filescontent,'',$i,strlen($currentselector) + 1);
  1276. // store selector for this line
  1277. $line_infos[$currentline]['selector'] = $currentselector;
  1278. // Remember that we are inside some selector's properties
  1279. $property_on = 1;
  1280. $i--;
  1281. // For library debugging purposes we log pre-parser structure findings
  1282. if($this->librarydebug && $this->css_part == 1) file_put_contents($this->debug_log,"\A([a-zA-Z\.#\*:][^\{\}@;\/]+)\{ selector start (part ".$this->css_part."): ".$currentselector." -> ".$line_infos[$currentline]['selector']."\r\n",FILE_APPEND);
  1283. }
  1284. }
  1285. break;
  1286. }
  1287. }
  1288. // Store one further line-entry in array
  1289. $line_infos[$currentline] = array('block'=>$currentblock,'blockclosing'=>FALSE,'comment'=>$currentcomment,'selector'=>$currentselector);
  1290. // Store in cache files
  1291. if(!file_exists($cachefilecontent)) file_put_contents($cachefilecontent,$filescontent);
  1292. if(!file_exists($cachefiledata)) file_put_contents($cachefiledata,serialize($line_infos));
  1293. }
  1294. // For library debugging purposes we log pre-parser structure findings
  1295. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." (part ".$this->css_part."):\r\n".var_export($line_infos, TRUE)."\r\n-----------------\r\n",FILE_APPEND);
  1296. // For library debugging purposes we log file contents
  1297. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split output content (part ".$this->css_part."):\r\n-----------------\r\n".$filescontent."\r\n-----------------\r\n",FILE_APPEND);
  1298. // Finished with out pre-parsing, beginning split process ///////////////////////////////////////////////////
  1299. // Split at every new line
  1300. $filescontentlines = explode("\n",$filescontent);
  1301. // Prepare storage for parts
  1302. $filescontentparts = array();
  1303. $i = 0;
  1304. // Create all parts
  1305. for($j=0;$j<intval($this->css_totalparts);$j++)
  1306. {
  1307. // Here we note during the processing if we are currently inside a block or not, and of which type
  1308. $currentblock = '';
  1309. // Here we note during the processing if we are currently inside a comment or not, and of which type
  1310. $currentcomment = '';
  1311. // Here we note during the processing if we are currently inside a property or not, and of which type
  1312. $currentselector = '';
  1313. // Here we will store this part's content
  1314. $filescontentparts[$j] = '';
  1315. // Starting to process the different parts
  1316. while(
  1317. (
  1318. // If we are not building the final part, stop at around (file / total parts) length
  1319. (
  1320. $j != (intval($this->css_totalparts) - 1) &&
  1321. strlen($filescontentparts[$j]) < ceil(strlen($filescontent) / $this->css_totalparts)
  1322. ) ||
  1323. // If we are building the final part, no stop required
  1324. $j == (intval($this->css_totalparts) - 1)
  1325. ) &&
  1326. // Stop if there is no corresponding line in the source (like at the end for example)
  1327. isset($filescontentlines[$i])
  1328. )
  1329. {
  1330. // If a comment begins
  1331. if($j > 0 && $line_infos[$i]['comment'] != '' && $currentcomment == '')
  1332. {
  1333. if($i == 0) $filescontentparts[$j] .= '/*';
  1334. $currentcomment = $line_infos[$i]['comment'];
  1335. }
  1336. // If at this place an @-rule-status should start or stop (=changes)
  1337. if($line_infos[$i]['block'] != $currentblock)
  1338. {
  1339. // If an @-rule begins
  1340. if($line_infos[$i]['block'] != '' && !$line_infos[$i]['blockclosing'])
  1341. {
  1342. if($currentblock != '' && $line_infos[$i]['blockclosing']) $filescontentparts[$j] .= '}';
  1343. $filescontentparts[$j] .= '@'.$line_infos[$i]['block'].'{';
  1344. $currentblock = $line_infos[$i]['block'];
  1345. }
  1346. // If an @-rule ends
  1347. else
  1348. {
  1349. $filescontentparts[$j] .= '}';
  1350. $currentblock = $line_infos[$i]['block'];
  1351. }
  1352. }
  1353. // If at this place a selector should start or stop (=changes)
  1354. if($line_infos[$i]['selector'] != $currentselector)
  1355. {
  1356. // If a selector begins
  1357. if($line_infos[$i]['selector'] != '')
  1358. {
  1359. if($currentselector != '') $filescontentparts[$j] .= '}';
  1360. $filescontentparts[$j] .= $line_infos[$i]['selector'].'{';
  1361. $currentselector = $line_infos[$i]['selector'];
  1362. }
  1363. // If a selector ends
  1364. else
  1365. {
  1366. $filescontentparts[$j] .= '}';
  1367. $currentselector = $line_infos[$i]['selector'];
  1368. }
  1369. }
  1370. $filescontentparts[$j] .= $filescontentlines[$i]."\n";
  1371. $i++;
  1372. }
  1373. // If at the end of this part any comment is left open, close it
  1374. if($line_infos[$i - 1]['comment'] != '') $filescontentparts[$j] .= '*/';
  1375. // If at the end of this part any selector is left open, close it
  1376. if($line_infos[$i - 1]['selector'] != '') $filescontentparts[$j] .= '}';
  1377. // If at the end of this part any @-rule-block is left open, close it
  1378. if($line_infos[$i - 1]['block'] != '') $filescontentparts[$j] .= '}';
  1379. // Doing a last cleanup of all the specialformatting of our pre-parser ///////////////////////////////////////////
  1380. // Backup any values within single or double quotes
  1381. preg_match_all('/(\'[^\']*?\'|"[^"]*?")/ims',$filescontentparts[$j],$treffer,PREG_PATTERN_ORDER);
  1382. for($k=0;$k<count($treffer[1]);$k++)
  1383. {
  1384. $filescontentparts[$j] = str_replace($treffer[1][$k],'##########'.$k.'##########',$filescontentparts[$j]);
  1385. }
  1386. // Cleanup newlines
  1387. $filescontentparts[$j] = preg_replace('/[\r\n]+/ims',"\n",$filescontentparts[$j]);
  1388. $filescontentparts[$j] = preg_replace('/\n[\t]+/ims',"\n",$filescontentparts[$j]);
  1389. $filescontentparts[$j] = preg_replace('/\{[\s\t]+/ims',"{",$filescontentparts[$j]);
  1390. $filescontentparts[$j] = str_replace("\n{","{",$filescontentparts[$j]);
  1391. $filescontentparts[$j] = str_replace("\n}","}",$filescontentparts[$j]);
  1392. $filescontentparts[$j] = str_replace(" \n","\n",$filescontentparts[$j]);
  1393. $filescontentparts[$j] = str_replace("\n\n*/","\n*/",$filescontentparts[$j]);
  1394. // Restore backupped values within single or double quotes
  1395. for($k=0;$k<count($treffer[1]);$k++)
  1396. {
  1397. $filescontentparts[$j] = str_replace('##########'.$k.'##########',$treffer[1][$k],$filescontentparts[$j]);
  1398. }
  1399. }
  1400. // For library debugging purposes we log file contents
  1401. if($this->librarydebug) file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." css_split result for part (part ".$this->css_part."):\r\n".$filescontentparts[$this->css_part - 1]."\r\n-----------------\r\n",FILE_APPEND);
  1402. // Return only the requested part
  1403. return $filescontentparts[$this->css_part - 1];
  1404. }
  1405. }
  1406. /**
  1407. * Css fetches and optimizes all stylesheet-files
  1408. *
  1409. * @return string optimized stylesheet-code
  1410. * @access public
  1411. */
  1412. public function css()
  1413. {
  1414. // Call Setcachedir to make sure, cache-path has been calculated
  1415. $this->setcachedir();
  1416. // Empty storage for stylesheet-contents to come
  1417. $filescontent = '';
  1418. // Specify file extension "css" for security reasons
  1419. $type = 'css';
  1420. // Prepare @var $sources as an array
  1421. // if @var $css_source is an array
  1422. if(is_array($this->css_source)) $sources = $this->css_source;
  1423. // if @var $css_source is not an array and @var $css_stringmode is not set
  1424. elseif(!$this->css_stringmode) $sources = explode(',',$this->css_source);
  1425. // if @var $css_stringmode is set
  1426. else $sources = array($this->css_source);
  1427. // if @var $css_stringmode is not set: newest filedate within the source array
  1428. if(!$this->css_stringmode) $this->getfilestime($sources,$type,$this->css_recursive);
  1429. // if @var $css_stringmode is set
  1430. else $this->filestime = $this->css_stringtime;
  1431. // identifier for the cache-files
  1432. $identifier = md5(
  1433. implode('',$sources).
  1434. intval($this->debug).
  1435. intval($this->librarydebug).
  1436. intval($this->css_hosted_minifier)
  1437. );
  1438. // Defining the cache-filename
  1439. // If any MHTML-capable IE browser
  1440. if($this->css_mhtml_enabled_ie) $cachefile = $this->booster_cachedir.'/'.$identifier.'_datauri_ie_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  1441. // If any other and (then we assume) data-URI-compatible browser
  1442. else $cachefile = $this->booster_cachedir.'/'.$identifier.'_datauri_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  1443. // Interim cache file
  1444. $interim_cachefile = str_replace('.txt','.working.txt',$cachefile);
  1445. // If that cache-file is there, fetch its contents
  1446. if(
  1447. file_exists($cachefile) &&
  1448. filemtime($cachefile) >= $this->filestime &&
  1449. filemtime($cachefile) >= filemtime(str_replace('\\','/',dirname(__FILE__)))
  1450. )
  1451. {
  1452. $filescontent .= file_get_contents($cachefile)."\n";
  1453. // Split results up in order to have multiple parts load in parallel and get the currently requested part back
  1454. $filescontent = $this->css_split($filescontent);
  1455. }
  1456. // Check for interim file existance, and if it is no older than 2 minutes
  1457. elseif(file_exists($interim_cachefile) && filemtime($interim_cachefile) > ($_SERVER['REQUEST_TIME'] - 120))
  1458. {
  1459. $filescontent .= file_get_contents($interim_cachefile)."\n";
  1460. }
  1461. // if that cache-file does not exist or is too old, create it
  1462. else
  1463. {
  1464. // but before, find and delete all cache files older than booster_inc.php
  1465. $booster_filetime = filemtime(str_replace('\\','/',__FILE__));
  1466. if(is_dir($this->booster_cachedir))
  1467. {
  1468. $handle=opendir($this->booster_cachedir);
  1469. while(false !== ($file = readdir($handle)))
  1470. {
  1471. // If it is a file and the filetype matches and it isn't the log file
  1472. // and last file modified time is older than booster library itself, then delete
  1473. if($file[0] != '.' &&
  1474. strtolower(pathinfo($this->booster_cachedir.'/'.$file,PATHINFO_EXTENSION)) == 'txt' &&
  1475. $this->booster_cachedir.'/'.$file != $this->debug_log &&
  1476. filemtime($this->booster_cachedir.'/'.$file) < $booster_filetime
  1477. )
  1478. {
  1479. @unlink($this->booster_cachedir.'/'.$file);
  1480. }
  1481. }
  1482. closedir($handle);
  1483. }
  1484. // In order to deflect other concurrent clients' requests for this file while it being compiled
  1485. // we first create a non-optimized cache-file to serve it to them during compile time.
  1486. reset($sources);
  1487. for($i=0;$i<sizeof($sources);$i++)
  1488. {
  1489. $source = current($sources);
  1490. // Remove any trailing slash
  1491. $source = rtrim($source,'/');
  1492. if($source != '')
  1493. {
  1494. // If current source is a folder or file, get its contents
  1495. if(file_exists($source) || is_dir($source) || is_file($source)) $currentfilescontent = $this->getfilescontents($source,$type,$this->css_recursive);
  1496. // If current source is already a string
  1497. else $currentfilescontent = $source;
  1498. // Prepare @var $dir that we need to prepend as path to any images we find to get the full path
  1499. // if @var $css_source is a folder
  1500. if(is_dir($source)) $dir = $source;
  1501. // if @var $css_source is a file
  1502. elseif(is_file($source)) $dir = dirname($source);
  1503. // if @var $css_source is code-string
  1504. else $dir = rtrim($this->getpath(dirname($_SERVER['SCRIPT_FILENAME']).'/'.$this->css_stringbase,str_replace('\\','/',dirname(__FILE__))),'/');
  1505. $filescontent .= $this->css_datauri_cleanup($currentfilescontent,$dir);
  1506. }
  1507. next($sources);
  1508. }
  1509. // Write interim cachefile
  1510. file_put_contents($interim_cachefile,$filescontent);
  1511. // Now we create the optimized version
  1512. $filescontent = '';
  1513. reset($sources);
  1514. for($i=0;$i<sizeof($sources);$i++)
  1515. {
  1516. $source = current($sources);
  1517. // Remove any trailing slash
  1518. $source = rtrim($source,'/');
  1519. if($source != '')
  1520. {
  1521. // If current source is a folder or file, get its contents
  1522. if(file_exists($source) || is_dir($source) || is_file($source)) $currentfilescontent = $this->getfilescontents($source,$type,$this->css_recursive);
  1523. // If current source is already a string
  1524. else $currentfilescontent = $source;
  1525. // Prepare @var $dir that we need to prepend as path to any images we find to get the full path
  1526. // if @var $css_source is a folder
  1527. if(is_dir($source)) $dir = $source;
  1528. // if @var $css_source is a file
  1529. elseif(is_file($source)) $dir = dirname($source);
  1530. // if @var $css_source is code-string
  1531. else $dir = rtrim($this->getpath(dirname($_SERVER['SCRIPT_FILENAME']).'/'.$this->css_stringbase,str_replace('\\','/',dirname(__FILE__))),'/');
  1532. // Optimize stylesheets with CSS Minify
  1533. if(!$this->debug) $currentfilescontent = $this->css_minify($currentfilescontent);
  1534. // Embed media to save HTTP-requests
  1535. $filescontent .= $this->css_datauri($currentfilescontent,$dir);
  1536. }
  1537. next($sources);
  1538. }
  1539. // Write cache-file
  1540. file_put_contents($cachefile,$filescontent);
  1541. // Delete interim cache-file
  1542. if(file_exists($interim_cachefile)) @unlink($interim_cachefile);
  1543. // Split results up in order to have multiple parts load in parallel and get the currently requested part back
  1544. $filescontent = $this->css_split($filescontent."\n");
  1545. }
  1546. // Return the currently requested part of the stylesheets
  1547. return $filescontent;
  1548. }
  1549. /**
  1550. * Mhtmltime returns the last-modified-timestamp of the MHTML-cache-file
  1551. *
  1552. * @return integer timestamp of the requested MHTML-cache-file
  1553. * @access public
  1554. */
  1555. public function mhtmltime()
  1556. {
  1557. $mhtmlfile = $this->booster_cachedir.'/'.preg_replace('/[^a-z0-9,\-_]/i','',$this->css_source).'_datauri_mhtml_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  1558. if(file_exists($mhtmlfile)) return filemtime($mhtmlfile);
  1559. else return 0;
  1560. }
  1561. /**
  1562. * Mhtml reads and returns the contents of the requested MHTML-cache-file
  1563. *
  1564. * @return string contents of the MHTML-cache-file
  1565. * @access public
  1566. */
  1567. public function mhtml()
  1568. {
  1569. $mhtmlfile = $this->booster_cachedir.'/'.preg_replace('/[^a-z0-9,\-_]/i','',$this->css_source).'_datauri_mhtml_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  1570. if(file_exists($mhtmlfile)) return file_get_contents($mhtmlfile);
  1571. else return '';
  1572. }
  1573. /**
  1574. * Css_markup creates HTML-<link>-tags for all CSS
  1575. *
  1576. * @return string the markup
  1577. * @access public
  1578. */
  1579. public function css_markup()
  1580. {
  1581. // Check for configuration errors
  1582. $this->errorcheck();
  1583. // Preparing call
  1584. $this->debug_log = str_replace('\\','/',dirname(__FILE__)).'/'.$this->booster_cachedir.'/debug_log.txt';
  1585. // For CSS debugging we don't split the contents up
  1586. if($this->debug) $this->css_totalparts = 1;
  1587. // Empty storage for markup to come
  1588. $markup = '';
  1589. $linkcode = '';
  1590. $linkarray = array();
  1591. // Calculate absolute path for booster-folder
  1592. $booster_path = '/'.$this->getpath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$this->document_root));
  1593. // If sources were defined as array
  1594. if(is_array($this->css_source)) $sources = $this->css_source;
  1595. // If sources were defined as string, convert them into an array
  1596. else $sources = explode(',',$this->css_source);
  1597. // Empty folder/file-storage for full pathed source-files
  1598. $timestamp_dirs = array();
  1599. // Fill folder/file-storage-array with prefixed folders/files
  1600. reset($sources);
  1601. for($i=0;$i<sizeof($sources);$i++)
  1602. {
  1603. array_push($timestamp_dirs,$booster_path.'/'.current($sources));
  1604. next($sources);
  1605. }
  1606. // Make sure $source now ends up as string fed from $sources to use as URL-parameter
  1607. $source = implode(',',$sources);
  1608. // Make sure $timestamp_dir now ends up as string fed from $timestamp_dirs to use as URL-parameter
  1609. $timestamp_dir = implode(',',$timestamp_dirs);
  1610. // For library debugging purposes we log timestamp object list
  1611. if($this->librarydebug)
  1612. {
  1613. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." timestamp CSS objects:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  1614. }
  1615. // Populate $this->filestime with newest file's timestamp
  1616. $this->getfilestime($source,'css');
  1617. // Put together the markup linking to our booster-css-files
  1618. // Append timestamps of the $timestamp_dir to make sure browser reloads once the CSS was updated
  1619. for($j=0;$j<intval($this->css_totalparts);$j++)
  1620. {
  1621. $linkurl = $this->base_offset.ltrim($booster_path,'/').'/booster_css.php'.
  1622. ($this->mod_rewrite ? '/' : '?').
  1623. 'dir='.htmlentities(str_replace('..','%3E',$source),ENT_QUOTES).
  1624. '&amp;cachedir='.htmlentities(str_replace('..','%3E',$this->booster_cachedir),ENT_QUOTES).
  1625. ($this->css_hosted_minifier ? '&amp;css_hosted_minifier=1' : '').
  1626. '&amp;totalparts='.intval($this->css_totalparts).
  1627. '&amp;part='.($j+1).
  1628. ($this->debug ? '&amp;debug=1' : '').
  1629. ($this->librarydebug ? '&amp;librarydebug=1' : '').
  1630. (!$this->js_minify ? '&amp;js_minify=0' : '').
  1631. '&amp;nocache='.$this->filestime;
  1632. $linkcode .= '<link rel="'.$this->css_rel.
  1633. '" media="'.$this->css_media.'"'.
  1634. ($this->css_title != '' ? ' title="'.htmlentities($this->css_title,ENT_QUOTES).'"' : '').
  1635. ' type="text/css" href="'.$linkurl.'" '.
  1636. ($this->markuptype == 'XHTML' ? '/' : '').'>'."\r\n";
  1637. $linkarray[] = '"'.$linkurl.'"';
  1638. }
  1639. // Insert markup for normal browsers and IEs (CC's now replacing former UA-sniffing)
  1640. if($this->css_lazyload) $markup .= '<script type="text/javascript">//<![CDATA['."\r\n".'var booster_stylesheets = new Array('.implode(',',$linkarray).');'.file_get_contents(str_replace('\\','/',dirname(__FILE__)).'/booster.css_lazyload.min.js')."\r\n".'//]]>'."\r\n".'</script>'."\r\n";
  1641. $markup .= '<!--[if IE]><![endif]-->'."\r\n";
  1642. $markup .= '<!--[if (gte IE 8)|!(IE)]><!-->'."\r\n";
  1643. if($this->css_lazyload) $markup .= '<noscript>'."\r\n";
  1644. $markup .= $linkcode;
  1645. if($this->css_lazyload) $markup .= '</noscript>'."\r\n";
  1646. $markup .= '<!--<![endif]-->'."\r\n";
  1647. $markup .= '<!--[if lte IE 7 ]>'."\r\n";
  1648. if($this->css_lazyload) $markup .= '<noscript>'."\r\n";
  1649. $markup .= str_replace('booster_css.php','booster_css_ie.php',$linkcode);
  1650. if($this->css_lazyload) $markup .= '</noscript>'."\r\n";
  1651. $markup .= '<![endif]-->'."\r\n";
  1652. /*
  1653. $markup .= '<!--[if lte IE 6 ]>'."\r\n";
  1654. $markup .= '<script type="text/javascript">try {document.execCommand("BackgroundImageCache", false, true);} catch(err) {}</script>'."\r\n";
  1655. $markup .= '<![endif]-->'."\r\n";
  1656. */
  1657. // If there are errors, output them
  1658. if($this->errormessage != '')
  1659. {
  1660. $this->errormessage = trim($this->errormessage,"\r\n");
  1661. $markup .= '<style type="text/css">'."\r\nhtml:before {display: block; padding: 1em; background-color: #FFF9D0; color: #912C2C; border: 1px solid #912C2C; font-family: Calibri, 'Lucida Grande', Arial, Verdana, sans-serif; white-space: pre; content: \"".str_replace("\r\n","\\00000A","CSS-JS-Booster problems:\r\n\r\n".$this->errormessage)."\";}\r\n".'</style>'."\r\n";
  1662. }
  1663. return $markup;
  1664. }
  1665. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1666. /**
  1667. * Js_minify takes a JS-string and tries to minify it with the Google Closure Webservice
  1668. *
  1669. * If the input JavaScript surpasses Google's POST-limit of 200.000 bytes it switches to
  1670. * minification through Douglas Crockford's JSMin
  1671. *
  1672. * @param string $filescontent contents to minify
  1673. * @return string minified Javascript
  1674. * @access protected
  1675. */
  1676. protected function js_minify($filescontent = '')
  1677. {
  1678. // If somebody wants to use the included minifier
  1679. if($this->js_hosted_minifier && is_readable($this->js_hosted_minifier_path))
  1680. {
  1681. // Implementation by Vincent Voyer (http://twitter.com/vvoyer)
  1682. // must create tmp files because closure compiler can't work with direct input..
  1683. $tmp_file_path = sys_get_temp_dir().'/'.uniqid();
  1684. file_put_contents($tmp_file_path, $filescontent);
  1685. $js_minified = `java -jar $this->js_hosted_minifier_path --charset utf-8 --js $tmp_file_path`;
  1686. unlink($tmp_file_path);
  1687. }
  1688. // Use Google Closure Webservice or JSMin
  1689. else
  1690. {
  1691. // URL-encoded file contents
  1692. $filescontent_urlencoded = urlencode($filescontent);
  1693. // Google Closure has a max limit of 200KB POST size, and will break JS with eval-command
  1694. if(strlen($filescontent_urlencoded) < 200000 && preg_match('/[^a-z]eval\(/ism',$filescontent) == 0)
  1695. {
  1696. // Working vars
  1697. $js_minified = '';
  1698. $host = "closure-compiler.appspot.com";
  1699. $service_uri = "/compile";
  1700. $vars = 'js_code='.$filescontent_urlencoded.'&compilation_level=SIMPLE_OPTIMIZATIONS&output_format=text&output_info=compiled_code';
  1701. // Compose HTTP request header
  1702. $header = "Host: $host\r\n";
  1703. $header .= "User-Agent: PHP Script\r\n";
  1704. $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
  1705. $header .= "Content-Length: ".strlen($vars)."\r\n";
  1706. $header .= "Connection: close\r\n\r\n";
  1707. $fp = pfsockopen($host, 80, $errno, $errstr);
  1708. // If we cannot open connection to Google Closure
  1709. if(!$fp) $js_minified = $filescontent;
  1710. else
  1711. {
  1712. fputs($fp, "POST $service_uri HTTP/1.0\r\n");
  1713. fputs($fp, $header.$vars);
  1714. while (!feof($fp)) {
  1715. $js_minified .= fgets($fp);
  1716. }
  1717. fclose($fp);
  1718. $js_minified = "/* Minified by Google Closure Webservice */\r\n\r\n".preg_replace('/^HTTP.+[\r\n]{2}/ims','',$js_minified)."\r\n";
  1719. }
  1720. }
  1721. // Switching over to Douglas Crockford's JSMin (which in turn breaks IE's conditional compilation)
  1722. else
  1723. {
  1724. /**
  1725. * Inclusion of JSMin
  1726. */
  1727. include_once('jsmin/jsmin.php');
  1728. $js_minified = "/* Minified by JSMin */\r\n\r\n".JSMin::minify($filescontent)."\r\n";
  1729. }
  1730. }
  1731. return $js_minified;
  1732. }
  1733. /**
  1734. * Js fetches and optimizes all javascript-files
  1735. *
  1736. * @return string optimized javascript-code
  1737. * @access public
  1738. */
  1739. public function js()
  1740. {
  1741. // Call Setcachedir to make sure, cache-path has been calculated
  1742. $this->setcachedir();
  1743. // Empty storage for javascript-contents to come
  1744. $filescontent = '';
  1745. // Specify file extension "js" for security reasons
  1746. $type = 'js';
  1747. // Prepare @var $sources as an array
  1748. // if @var $js_source is an array
  1749. if(is_array($this->js_source)) $sources = $this->js_source;
  1750. // if @var $js_source is not an array and @var $js_stringmode is not set
  1751. elseif(!$this->js_stringmode) $sources = explode(',',$this->js_source);
  1752. // if @var $js_stringmode is set
  1753. else $sources = array($this->js_source);
  1754. // if @var $js_stringmode is not set: newest filedate within the source array
  1755. if(!$this->js_stringmode) $this->getfilestime($sources,$type,$this->js_recursive);
  1756. // if @var $js_stringmode is set
  1757. else $this->filestime = $this->js_stringtime;
  1758. // identifier for the cache-files
  1759. $identifier = md5(
  1760. implode('',$sources).
  1761. intval($this->debug).
  1762. intval($this->librarydebug).
  1763. intval($this->js_minify).
  1764. intval($this->js_hosted_minifier)
  1765. );
  1766. // Defining the cache-filename
  1767. $cachefile = $this->booster_cachedir.'/'.$identifier.'_js_'.(($this->debug) ? 'debug_' : '').'cache.txt';
  1768. // Interim cache file
  1769. $interim_cachefile = str_replace('.txt','.working.txt',$cachefile);
  1770. // If cache-file exists and cache-file date is newer than code-date, read from there
  1771. if(
  1772. file_exists($cachefile) &&
  1773. filemtime($cachefile) >= $this->filestime &&
  1774. filemtime($cachefile) >= filemtime(str_replace('\\','/',dirname(__FILE__)))
  1775. )
  1776. {
  1777. $filescontent .= file_get_contents($cachefile);
  1778. }
  1779. // Check for interim file existance, and if it is no older than 2 minutes
  1780. elseif(file_exists($interim_cachefile) && filemtime($interim_cachefile) > ($_SERVER['REQUEST_TIME'] - 120))
  1781. {
  1782. $filescontent .= file_get_contents($interim_cachefile);
  1783. }
  1784. // There is no cache-file or it is outdated, create it
  1785. else
  1786. {
  1787. reset($sources);
  1788. for($i=0;$i<sizeof($sources);$i++)
  1789. {
  1790. $source = current($sources);
  1791. // Remove any trailing slash
  1792. $source = rtrim($source,'/');
  1793. // If current source is a folder or file, get its contents
  1794. if(file_exists($source) || is_dir($source) || is_file($source)) $filescontent .= $this->getfilescontents($source,$type,$this->js_recursive);
  1795. // If current source is already a string
  1796. else $filescontent .= $source;
  1797. next($sources);
  1798. }
  1799. // In order to deflect other concurrent clients' requests for this file while it being compiled
  1800. // we first create a non-optimized cache-file to serve it to them during compile time.
  1801. file_put_contents($interim_cachefile,$filescontent);
  1802. // Check for document.write inside JS. If found disable any lazy-loadings.
  1803. if(strpos($filescontent,'document.write'))
  1804. {
  1805. $this->js_executionmode = '';
  1806. }
  1807. // Minify
  1808. if(!$this->debug && $this->js_minify) $filescontent = $this->js_minify($filescontent);
  1809. // Write cache-file
  1810. file_put_contents($cachefile,$filescontent);
  1811. // Delete interim cache-file
  1812. if(file_exists($interim_cachefile)) @unlink($interim_cachefile);
  1813. }
  1814. $filescontent .= "\n";
  1815. // Return the currently requested part of the javascript
  1816. return $filescontent;
  1817. }
  1818. /**
  1819. * Js_markup creates HTML-<script>-tags for all JS
  1820. *
  1821. * @return string the markup
  1822. * @access public
  1823. */
  1824. public function js_markup()
  1825. {
  1826. // Check for configuration errors
  1827. $this->errorcheck();
  1828. // Preparing call
  1829. $this->debug_log = str_replace('\\','/',dirname(__FILE__)).'/'.$this->booster_cachedir.'/debug_log.txt';
  1830. // Empty storage for markup to come
  1831. $markup = '';
  1832. // Calculate absolute path for booster-folder
  1833. $booster_path = '/'.$this->getpath(str_replace('\\','/',dirname(__FILE__)),str_replace('\\','/',$this->document_root));
  1834. // If sources were defined as array
  1835. if(is_array($this->js_source)) $sources = $this->js_source;
  1836. // If sources were defined as string, convert them into an array
  1837. else $sources = explode(',',$this->js_source);
  1838. // Empty folder/file-storage for full pathed source-files
  1839. $timestamp_dirs = array();
  1840. // Fill folder/file-storage-array with prefixed folders/files
  1841. reset($sources);
  1842. for($i=0;$i<sizeof($sources);$i++)
  1843. {
  1844. array_push($timestamp_dirs,$booster_path.'/'.current($sources));
  1845. next($sources);
  1846. }
  1847. // Make sure $source now ends up as string fed from $sources to use as URL-parameter
  1848. $source = implode(',',$sources);
  1849. // Make sure $timestamp_dir now ends up as string fed from $timestamp_dirs to use as URL-parameter
  1850. $timestamp_dir = implode(',',$timestamp_dirs);
  1851. // For library debugging purposes we log timestamp object list
  1852. if($this->librarydebug)
  1853. {
  1854. file_put_contents($this->debug_log,"-----------------\r\n".date("d.m.Y H:i:s")." timestamp JS objects:\r\n-----------------\r\n".$source."\r\n-----------------\r\n",FILE_APPEND);
  1855. }
  1856. // Populate $this->filestime with newest file's timestamp
  1857. $this->getfilestime($source,'js');
  1858. // If there are errors, output them
  1859. if($this->errormessage != '')
  1860. {
  1861. $this->errormessage = trim($this->errormessage,"\r\n");
  1862. $markup .= '<script type="text/javascript">throw("CSS-JS-Booster problems:\r\n\r\n'.str_replace('"',"'",$this->errormessage).'");</script>'."\r\n";
  1863. }
  1864. // Put together the markup linking to our booster-js-files
  1865. // Append timestamps of the $timestamp_dir to make sure browser reloads once the JS was updated
  1866. $markup .= '<script type="text/javascript"';
  1867. switch($this->js_executionmode)
  1868. {
  1869. case 'async':
  1870. $markup .= ($this->markuptype == 'XHTML' ? ' async="async" defer="defer"' : ' async defer');
  1871. break;
  1872. case 'defer':
  1873. $markup .= ($this->markuptype == 'XHTML' ? ' defer="defer"' : ' defer');
  1874. break;
  1875. }
  1876. $markup .= ($this->js_onload != '' ? ' onload="'.str_replace('"','\\"',$this->js_onload).'"' : '');
  1877. $markup .= ' src="'.$this->base_offset.ltrim($booster_path,'/').'/booster_js.php'.
  1878. ($this->mod_rewrite ? '/' : '?').
  1879. 'dir='.htmlentities(str_replace('..','%3E',$source),ENT_QUOTES).
  1880. '&amp;cachedir='.htmlentities(str_replace('..','%3E',$this->booster_cachedir),ENT_QUOTES).
  1881. ($this->js_hosted_minifier ? '&amp;js_hosted_minifier=1' : '').
  1882. ($this->debug ? '&amp;debug=1' : '').
  1883. ($this->librarydebug ? '&amp;librarydebug=1' : '').
  1884. (!$this->js_minify ? '&amp;js_minify=0' : '').
  1885. '&amp;nocache='.$this->filestime.'"></script>'."\r\n";
  1886. return $markup;
  1887. }
  1888. }
  1889. ?>