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

/vendors/php_speedy/controller/compressor.php

http://scp-soft.googlecode.com/
PHP | 1244 lines | 848 code | 225 blank | 171 comment | 98 complexity | a1b65e537e3641a94eeab326eb7462e9 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * Gzips and minifies the JavaScript and CSS within the head tags of a page.
  4. * Can also gzip and minify the page itself
  5. *
  6. **/
  7. class compressor {
  8. /**
  9. * Constructor
  10. * Sets the options and defines the gzip headers
  11. **/
  12. function compressor($options=false) {
  13. if(!empty($options['skip_startup'])) {
  14. return;
  15. }
  16. //Allow merging of other classes with this one
  17. foreach($options AS $key=>$value) {
  18. $this->$key = $value;
  19. }
  20. //Set options
  21. $this->set_options();
  22. //Define the gzip headers
  23. $this->set_gzip_headers();
  24. //Start things off
  25. $this->start();
  26. }
  27. /**
  28. * Options are read from config.php
  29. **/
  30. function set_options() {
  31. //Set paths with new options
  32. $this->view->set_paths($this->options['document_root']);
  33. //Set ignore file
  34. if(!empty($this->options['ignore_list'])) {
  35. $this->ignore(trim($this->options['ignore_list']));
  36. }
  37. //Read in options
  38. $full_options = array("javascript"=>array("cachedir"=>$this->options['javascript_cachedir'],
  39. "gzip"=>$this->options['gzip']['javascript'],
  40. "minify"=>$this->options['minify']['javascript'],
  41. "far_future_expires"=>$this->options['far_future_expires']['javascript']
  42. ),
  43. "css"=>array("cachedir"=>$this->options['css_cachedir'],
  44. "gzip"=>$this->options['gzip']['css'],
  45. "minify"=>$this->options['minify']['css'],
  46. "far_future_expires"=>$this->options['far_future_expires']['css'],
  47. "data_uris"=>$this->options['data_uris']['on']
  48. ),
  49. "page"=>array("gzip"=>$this->options['gzip']['page'],
  50. "minify"=>$this->options['minify']['page'],
  51. )
  52. );
  53. $this->options = $full_options; //overwrite ethe options array that we passed in
  54. //Make sure cachedir does not have trailing slash
  55. foreach($this->options AS $key=>$option) {
  56. if(!empty($option['cachedir'])) {
  57. if(substr($option['cachedir'],-1,1) == "/") {
  58. $cachedir = substr($option['cachedir'],0,-1);
  59. $option['cachedir'] = $cachedir;
  60. }
  61. }
  62. $this->options[$key] = $option;
  63. }
  64. $this->options['show_timer'] = false; //time the javascript and css compression?
  65. }
  66. /**
  67. * Start saving the output buffer
  68. *
  69. **/
  70. function start() {
  71. ob_start();
  72. }
  73. /**
  74. * Compress passes content directly
  75. *
  76. **/
  77. function compress($content) {
  78. $this->finish($content);
  79. }
  80. /**
  81. * Tells the script to ignore certain files
  82. *
  83. **/
  84. function ignore($files=false) {
  85. if(!empty($files)) {
  86. $files_array = explode(",",$files);
  87. }
  88. //Ignore these files
  89. if(!empty($files_array)) {
  90. foreach($files_array AS $key=>$value) {
  91. $this->ignore_files[] = trim($value);
  92. }
  93. }
  94. }
  95. /**
  96. * Do work and return output buffer
  97. *
  98. **/
  99. function finish($content=false) {
  100. $this->runtime = $this->startTimer();
  101. $this->times['start_compress'] = $this->returnTime($this->runtime);
  102. if(!$content) {
  103. $this->content = ob_get_clean();
  104. } else {
  105. $this->content = $content;
  106. }
  107. //Run the functions specified in options
  108. if(is_array($this->options)) {
  109. foreach($this->options AS $func=>$option) {
  110. if(method_exists($this,$func)) {
  111. if(!empty($option['gzip']) || !empty($option['minify']) || !empty($option['far_future_expires'])) {
  112. $this->$func($option,$func);
  113. }
  114. }
  115. }
  116. }
  117. //Delete old cache files
  118. if(!empty($this->compressed_files) && is_array($this->compressed_files)) {
  119. $this->compressed_files_string = implode("",$this->compressed_files); //Make a string with the names of the compressed files
  120. }
  121. if(!empty($this->options['cleanup']['on'])) {
  122. $this->do_cleanup(); //Delete any files that don't match the string
  123. }
  124. $this->times['end'] = $this->returnTime($this->runtime);
  125. //Echo content to the browser
  126. if(empty($this->supress_output)) {
  127. if(!empty($this->return_content)) {
  128. return $this->content;
  129. } else {
  130. echo $this->content;
  131. }
  132. }
  133. }
  134. /**
  135. * GZIP and minify the javascript as required
  136. *
  137. **/
  138. function javascript($options,$type) {
  139. //Remove any files in the remove list
  140. $this->do_remove();
  141. $this->content = $this->do_compress(array('cachedir'=>$options['cachedir'],
  142. 'tag'=>'script',
  143. 'type'=>'text/javascript',
  144. 'ext'=>'js',
  145. 'src'=>'src',
  146. 'self_close'=>false,
  147. 'gzip'=>$options['gzip'],
  148. 'minify'=>$options['minify'],
  149. 'far_future_expires'=>$options['far_future_expires'],
  150. 'header'=>$type,
  151. 'save_name'=>$type),$this->content);
  152. }
  153. /**
  154. * GZIP and minify the CSS as required
  155. *
  156. **/
  157. function css($options,$type) {
  158. $head = $this->get_head($this->content);
  159. if(!$head) { //Need a head
  160. return;
  161. }
  162. //Get links
  163. preg_match_all("!<link[^>]+text/css[^>]+>!is",$head,$matches);
  164. if(count($matches[0]) == 0) {
  165. return;
  166. }
  167. //find variants
  168. foreach($matches[0] AS $link) {
  169. preg_match_all("@(rel)=[\"'](.*?)[\"']@",$link,$variants,PREG_SET_ORDER); //|media
  170. if(is_array($variants)) {
  171. $marker = "";
  172. foreach($variants AS $variant_type) {
  173. $marker .= $variant_type[2];
  174. $return[$variant_type[1]] = $variant_type[2];
  175. }
  176. }
  177. //Sub this new marker into the link
  178. $marker = str_replace(" ","",$marker);
  179. $new_link = preg_replace("@type=('|\")(.*?)('|\")@","type=$1" . "%%MARK%%" . "$3",$link);
  180. $new_link = str_replace("%%MARK%%",md5($marker),$new_link);
  181. $this->content = str_replace($link,$new_link,$this->content);
  182. $return['real_type'] = $marker;
  183. $media_types[md5($marker)] = $return;
  184. }
  185. //print_r($media_types);
  186. $this->process_report['media_types'] = $media_types;
  187. //Compress separately for each media type
  188. foreach($media_types AS $key=>$value) {
  189. $this->content = $this->do_compress(array('cachedir'=>$options['cachedir'],
  190. 'tag'=>'link',
  191. 'type'=>$key,
  192. 'ext'=>'css',
  193. 'src'=>'href',
  194. 'rel'=>!empty($value['rel']) ? $value['rel'] : false,
  195. 'media'=>!empty($value['media']) ? $value['media'] : false,
  196. 'data_uris'=>$options['data_uris'],
  197. 'self_close'=>true,
  198. 'gzip'=>$options['gzip'],
  199. 'minify'=>$options['minify'],
  200. 'far_future_expires'=>$options['far_future_expires'],
  201. 'header'=>$type,
  202. 'save_name'=>$type.$value['real_type']),$this->content);
  203. //Replace out the markers
  204. $this->content = str_replace($key,'text/css',$this->content);
  205. }
  206. }
  207. /**
  208. * GZIP and minify the page itself as required
  209. *
  210. **/
  211. function page($options,$type) {
  212. //Minify page itself
  213. if(!empty($options['minify'])) {
  214. $this->content = $this->trimwhitespace($this->content);
  215. }
  216. //Gzip page itself
  217. if(!empty($options['gzip'])) {
  218. $content = $this->create_gz_compress($this->content);
  219. if($content) {
  220. $this->set_gzip_header();
  221. $this->content = $content;
  222. }
  223. }
  224. }
  225. /**
  226. * Return GZIP compressed content string with header
  227. *
  228. **/
  229. function create_gz_compress($content) {
  230. if(strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') && function_exists('gzcompress')) {
  231. $Size = strlen( $this->content );
  232. $Crc = crc32( $this->content );
  233. $content = "\x1f\x8b\x08\x00\x00\x00\x00\x00";
  234. $this->content = gzcompress( $this->content,2);
  235. $this->content = substr( $this->content, 0, strlen( $this->content) - 4 );
  236. $content .= ( $this->content );
  237. $content .= ( pack( 'V', $Crc ) );
  238. $content .= ( pack( 'V', $Size ) );
  239. return $content;
  240. } else {
  241. return false;
  242. }
  243. }
  244. /**
  245. * Sets the correct gzip header
  246. *
  247. **/
  248. function set_gzip_header() {
  249. if(strpos(" ".$_SERVER["HTTP_ACCEPT_ENCODING"], "x-gzip")) {
  250. $encoding = "x-gzip";
  251. }
  252. if(strpos(" ".$_SERVER["HTTP_ACCEPT_ENCODING"], "gzip")) {
  253. $encoding = "gzip";
  254. }
  255. if(!empty($encoding)) {
  256. header("Content-Encoding: ".$encoding);
  257. }
  258. }
  259. /**
  260. * Completely remove any JS scripts in the remove list
  261. *
  262. **/
  263. function do_remove() {
  264. if(empty($this->remove_files)) {
  265. return;
  266. }
  267. //Get non-immune scripts
  268. $script_array = $this->get_script_array($this->content,array('cachedir'=>$options['cachedir'],
  269. 'tag'=>'script',
  270. 'type'=>'text/javascript',
  271. 'ext'=>'js',
  272. 'src'=>'src'));
  273. //If we have scripts
  274. if(is_array($script_array)) {
  275. //Pull out remove immune
  276. preg_match("@<!-- REMOVE IMMUNE -->(.*?)<!-- END REMOVE IMMUNE -->@is", $this->content, $match);
  277. $_immune = $match[0];
  278. $this->content = str_replace($_immune,'@@@COMPRESSOR:TRIM:REMOVEIMMUNE@@@', $this->content);
  279. foreach($script_array AS $script) {
  280. foreach($this->remove_files AS $file) {
  281. if(!empty($file) && !empty($script)) {
  282. if(strstr($script,$file)) { //Remove the scripts from the source if they are on the remove list
  283. $this->content = str_replace($script,"",$this->content);
  284. $this->process_report['notice'][$script] = array('from'=>htmlspecialchars($script),
  285. 'notice'=>'The file was replaced by a standard library and removed');
  286. }
  287. }
  288. }
  289. }
  290. //Put back
  291. $this->content = str_replace("@@@COMPRESSOR:TRIM:REMOVEIMMUNE@@@",$_immune,$this->content);
  292. //Remove comments
  293. $this->content = str_replace("<!-- REMOVE IMMUNE -->","",$this->content);
  294. $this->content = str_replace("<!-- END REMOVE IMMUNE -->","",$this->content);
  295. }
  296. }
  297. /**
  298. * Compress JS or CSS and return source
  299. *
  300. **/
  301. function do_compress($options,$source) {
  302. //Save the original extension
  303. $options['original_ext'] = $options['ext'];
  304. //Change the extension
  305. if(!empty($options['gzip']) || !empty($options['far_future_expires'])) {
  306. $options['ext'] = "php";
  307. }
  308. //Set cachedir
  309. $cachedir = $options['cachedir'];
  310. //Get array of scripts
  311. $script_array = $this->get_script_array($source,$options);
  312. //Get date string for making hash
  313. $datestring = $this->get_file_dates($script_array,$options);
  314. //If only one script found
  315. if(!is_array($script_array)) {
  316. $_script_array = array($script_array);
  317. } else {
  318. $_script_array = $script_array;
  319. }
  320. //Get the cache hash
  321. $cache_file = '_cmp_' . $options['save_name'] . '_' . md5(implode("_",$_script_array).$datestring.implode("_",$options));
  322. $cache_file = urlencode($cache_file);
  323. //echo $cache_file . "\n";
  324. //Check if the cache file exists
  325. if (file_exists($cachedir . '/' . $cache_file . ".$options[ext]")) {
  326. $script_array = $this->get_file_locations($script_array,$options); //Put in locations and remove certain scripts
  327. $source = $this->_remove_scripts($script_array,$source);
  328. $newfile = $this->get_new_file($options,$cache_file);
  329. $source = str_replace("@@@marker@@@","",$source); //No longer use marker $source = str_replace("@@@marker@@@",$new_file,$source);
  330. //Move to top
  331. $source = preg_replace("!<head([^>]+)?>!is","$0 \n".$newfile."\n",$source);
  332. return $source;
  333. }
  334. //If the file didn't exist, continue ...
  335. $script_array = $this->get_file_locations($script_array,$options);
  336. //Create file
  337. $contents = "";
  338. if(is_array($script_array)) {
  339. foreach($script_array AS $key=>$info) {
  340. //Get the code
  341. if (file_exists($info['src'])) {
  342. $file_contents = file_get_contents($info['src']);
  343. //Mess with the CSS source
  344. if($options['header'] == "css") {
  345. $file_contents = $this->convert_paths_to_absolute($file_contents,$info); //Absolute paths
  346. if($options['data_uris']) {
  347. $file_contents = $this->convert_css_bgr_to_data($file_contents,$info); //CSS background images to data URIs
  348. }
  349. $file_contents = $this->add_media_header($file_contents,$info); //Add media type header
  350. }
  351. $contents .= $file_contents . "\n";
  352. $source = $this->_remove_scripts($script_array,$source);
  353. }
  354. }
  355. }
  356. if(!empty($contents)) {
  357. //Allow for minification of javascript
  358. if($options['header'] == "javascript" && $options['minify'] && substr(phpversion(),0,1) == 5) { //Only minify on php5+
  359. $contents = $this->jsmin->minify($contents);
  360. }
  361. //Allow for minification of CSS
  362. if($options['header'] == "css" && $options['minify']) { //Minify CSS
  363. $contents = $this->minify_text($contents);
  364. }
  365. //Allow for gzipping and headers
  366. if($options['gzip'] || $options['far_future_expires']) {
  367. $contents = $this->gzip_header[$options['header']] . $contents;
  368. }
  369. //Write to cache and display
  370. if($contents) {
  371. if ($fp = fopen($cachedir . '/' . $cache_file . '.' . $options['ext'], 'wb')) {
  372. fwrite($fp, $contents);
  373. fclose($fp);
  374. //Set permissions, required by some hosts
  375. chmod($cachedir . '/' . $cache_file . '.' . $options['ext'], octdec("0755"));
  376. //Create the link to the new file
  377. $newfile = $this->get_new_file($options,$cache_file);
  378. $source = str_replace("@@@marker@@@","",$source);
  379. $source = preg_replace("!<head([^>]+)?>!is","$0 \n".$newfile."\n",$source);
  380. $this->process_report['scripts'][] = array('type'=>$options['header'] . " " . $options['rel'],
  381. 'from'=>$script_array,
  382. 'to'=>$cachedir . '/' . $cache_file . '.' . $options['ext']);
  383. }
  384. }
  385. }
  386. return $source;
  387. }
  388. /**
  389. * Replaces the script or css links in the source with a marker
  390. *
  391. */
  392. function _remove_scripts($script_array,$source) {
  393. $maxKey = array_pop(array_keys($script_array));
  394. foreach($script_array AS $key=>$value) {
  395. if($key == $maxKey) { //Remove script
  396. $source = str_replace($value['location'],"@@@marker@@@",$source);
  397. } else {
  398. $source = str_replace($value['location'],"",$source);
  399. }
  400. }
  401. return $source;
  402. }
  403. /**
  404. * Returns the filename for our new compressed file
  405. *
  406. **/
  407. function get_new_file($options,$cache_file,$not_modified=false) {
  408. $relative_cachedir = str_replace($this->view->prevent_trailing_slash($this->unify_dir_separator($this->view->paths['full']['document_root'])),"",$this->view->prevent_trailing_slash($this->unify_dir_separator($options['cachedir'])));
  409. $newfile = "<" . $options['tag'] . " type=\"" . $options['type'] . "\" $options[src]=\"http://" . $_SERVER['HTTP_HOST'] . "/" . $this->view->prevent_leading_slash($relative_cachedir) ."/$cache_file." . $options['ext'] . "$not_modified\"";
  410. if(!empty($options['rel'])) {
  411. $newfile .= " rel=\"" . $options['rel'] . "\"";
  412. }
  413. if(!empty($options['media'])) {
  414. $newfile .= " media=\"" . $options['media'] . "\"";
  415. }
  416. if(!empty($options['self_close'])) {
  417. $newfile .= " />";
  418. } else {
  419. $newfile .= "></" . $options['tag'] . ">";
  420. }
  421. $this->compressed_files[] = $newfile;
  422. return $newfile;
  423. }
  424. /**
  425. * Returns the last modified dates of the files being compressed
  426. * In this way we can see if any changes have been made
  427. **/
  428. function get_file_dates($files,$options) {
  429. $files = $this->get_file_locations($files,$options);
  430. if(!is_array($files)) {
  431. return;
  432. }
  433. foreach($files AS $key=>$value) {
  434. if(file_exists($value['src'])) {
  435. $thedate = filemtime($value['src']);
  436. $dates[] = $thedate;
  437. }
  438. }
  439. if(is_array($dates)) {
  440. return implode(".",$dates);
  441. }
  442. }
  443. /**
  444. * Gets an array of scripts/css files to be processed
  445. *
  446. **/
  447. function get_script_array($source,$options) {
  448. $head = $this->get_head($source);
  449. if($head) {
  450. $regex = "!<" . $options['tag'] . "[^>]+type=['\"](" . $options['type'] . ")['\"]([^>]+)?>(</" . $options['tag'] . ">)?!is";
  451. preg_match_all($regex, $head, $matches);
  452. }
  453. if(!empty($matches[0])) {
  454. $script_array = $matches[0];
  455. } else {
  456. $script_array = "";
  457. }
  458. if(empty($script_array)) { //No file
  459. return $source;
  460. }
  461. //Make sure src element present
  462. foreach($script_array AS $key=>$value) {
  463. if(!strstr($value,$options['src'])) {
  464. unset($script_array[$key]);
  465. }
  466. }
  467. //Remove empty sources and any externally linked files
  468. foreach($script_array AS $key=>$value) {
  469. $regex = "!" . $options['src'] . "=['\"](.*?)['\"]!is";
  470. preg_match($regex, $value, $src);
  471. if(!$src[1]){
  472. unset($script_array[$key]);
  473. }
  474. if(strlen($src[1])> 7 && strcasecmp(substr($src[1],0,7),'http://')==0) {
  475. if(!strstr($src[1],$_SERVER['HTTP_HOST'])) {
  476. unset($script_array[$key]);
  477. $this->process_report['skipped'][$src[1]] = array('from'=>$src[1],
  478. 'reason'=>'Cannot compress external files');
  479. }
  480. }
  481. }
  482. //Remove ignore files
  483. if(!empty($this->ignore_files)) {
  484. foreach($script_array AS $return_key=>$src) {
  485. foreach($this->ignore_files AS $ignore) {
  486. if(strstr($src,$ignore)) {
  487. $this->process_report['notice'][$src] = array('from'=>$src,
  488. 'notice'=>'The file was on the ignore list and skipped');
  489. unset($script_array[$return_key]);
  490. }
  491. }
  492. }
  493. }
  494. return $script_array;
  495. }
  496. /**
  497. * Gets the path locations of the scripts being compressed
  498. *
  499. **/
  500. function get_file_locations($script_array,$options) {
  501. if(!is_array($script_array)) {
  502. return;
  503. }
  504. //Remove empty sources
  505. foreach($script_array AS $key=>$value) {
  506. preg_match("!" . $options['src'] . "=['\"](.*?)['\"]!is", $value, $src);
  507. if(!$src[1]) {
  508. unset($script_array[$key]);
  509. }
  510. }
  511. //Create file
  512. foreach($script_array AS $key=>$value) {
  513. //Get the src
  514. preg_match("!" . $options['src'] . "=['\"](.*?)['\"]!is", $value, $src);
  515. $src[1] = str_replace("http://".$_SERVER['HTTP_HOST'],"",$src[1]);
  516. if(substr($src[1],0,1) == "/") {
  517. $current_src = $this->view->prevent_trailing_slash($this->view->paths['full']['document_root']) . $src[1];
  518. } else {
  519. $current_src = $this->view->paths['full']['current_directory'] . $src[1];
  520. }
  521. if($current_src != $this->strip_querystring($current_src)) {
  522. $this->process_report['notice'][$current_src] = array('from'=>$current_src,
  523. 'notice'=>'The querystring was stripped from this script');
  524. }
  525. $current_src = $this->strip_querystring($current_src);
  526. //Make sure script exists
  527. if(file_exists($current_src)) {
  528. //Make sure script has the correct extension
  529. $extentsion_length = strlen($options['original_ext']);
  530. if(".".substr($this->view->get_basename($current_src),(-1*$extentsion_length)) == ".".$options['original_ext']) {
  531. $return_array[] = array('src'=>$current_src,
  532. 'location'=>$value);
  533. } else {
  534. $this->process_report['skipped'][$current_src] = array('from'=>$current_src,
  535. 'reason'=>'Must have ' . $options['original_ext'] . ' extension');
  536. }
  537. } else {
  538. if(!strstr($current_src,'php_speedy_control')) {
  539. $this->process_report['skipped'][$current_src] = array('from'=>$current_src,
  540. 'reason'=>'Not on server');
  541. }
  542. }
  543. }
  544. return $return_array;
  545. }
  546. /**
  547. * Sets the headers to be sent in the javascript and css files
  548. *
  549. **/
  550. function set_gzip_headers() {
  551. //When will the file expire?
  552. $offset = 6000000 * 60 ;
  553. $ExpStr = "Expires: " .
  554. gmdate("D, d M Y H:i:s",
  555. time() + $offset) . " GMT";
  556. $types = array("css","javascript");
  557. foreach($types AS $type) {
  558. //Always send etag
  559. $this->gzip_header[$type] = '<?php
  560. $hash = md5($_SERVER[\'SCRIPT_FILENAME\']);
  561. header ("Etag: \"" . $hash . "\"");
  562. ?>';
  563. //Send 304?
  564. $this->gzip_header[$type] .= '<?php
  565. if (isset($_SERVER[\'HTTP_IF_NONE_MATCH\']) &&
  566. stripslashes($_SERVER[\'HTTP_IF_NONE_MATCH\']) == \'"\' . $hash . \'"\') {
  567. // Return visit and no modifications, so do not send anything
  568. header ("HTTP/1.0 304 Not Modified");
  569. header (\'Content-Length: 0\');
  570. exit();
  571. }
  572. ?>';
  573. if(!empty($this->options[$type]['gzip'])) { ////ob_start ("ob_gzhandler");
  574. $this->gzip_header[$type] .= '<?php
  575. ob_start("compress_output_option");
  576. function compress_output_option($contents) {
  577. // Determine supported compression method
  578. $gzip = strstr($_SERVER[\'HTTP_ACCEPT_ENCODING\'], \'gzip\');
  579. $deflate = strstr($_SERVER[\'HTTP_ACCEPT_ENCODING\'], \'deflate\');
  580. // Determine used compression method
  581. $encoding = $gzip ? \'gzip\' : ($deflate ? \'deflate\' : \'none\');
  582. // Check for buggy versions of Internet Explorer
  583. if (!strstr($_SERVER[\'HTTP_USER_AGENT\'], \'Opera\') &&
  584. preg_match(\'/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i\', $_SERVER[\'HTTP_USER_AGENT\'], $matches)) {
  585. $version = floatval($matches[1]);
  586. if ($version < 6)
  587. $encoding = \'none\';
  588. if ($version == 6 && !strstr($_SERVER[\'HTTP_USER_AGENT\'], \'EV1\'))
  589. $encoding = \'none\';
  590. }
  591. if (isset($encoding) && $encoding != \'none\')
  592. {
  593. // Send compressed contents
  594. $contents = gzencode($contents, 9, $gzip ? FORCE_GZIP : FORCE_DEFLATE);
  595. header ("Content-Encoding: " . $encoding);
  596. header (\'Content-Length: \' . strlen($contents));
  597. }
  598. return $contents;
  599. }
  600. ?>';
  601. }
  602. if(!empty($this->options[$type]['far_future_expires'])) {
  603. $this->gzip_header[$type] .= '<?php
  604. header("Cache-Control: must-revalidate");
  605. header("' . $ExpStr . '");
  606. ?>';
  607. }
  608. $this->gzip_header[$type] .= '<?php
  609. header("Content-type: text/' . $type .'; charset: UTF-8");
  610. ?>';
  611. } // end FE
  612. }
  613. /**
  614. * Returns a path or url without the querystring
  615. *
  616. **/
  617. function strip_querystring($path) {
  618. if ($commapos = strpos($path, '?')) {
  619. return substr($path, 0, $commapos);
  620. } else {
  621. return $path;
  622. }
  623. }
  624. /**
  625. * Strips whitespace and comments from a text string
  626. *
  627. **/
  628. function minify_text($txt) {
  629. // Compress whitespace.
  630. $txt = preg_replace('/\s+/', ' ', $txt);
  631. // Remove comments.
  632. $txt = preg_replace('/\/\*.*?\*\//', '', $txt);
  633. //Further minification (Thanks to Andy & David)
  634. //$txt = preg_replace('/\s*(,|;|:|{|})\s/','$1', $txt);
  635. return $txt;
  636. }
  637. /**
  638. * Safely trim whitespace from an HTML page
  639. * Adapted from smarty code http://www.smarty.net/
  640. **/
  641. function trimwhitespace($source)
  642. {
  643. // Pull out the script blocks
  644. preg_match_all("!<script[^>]+>.*?</script>!is", $source, $match);
  645. $_script_blocks = $match[0];
  646. $source = preg_replace("!<script[^>]+>.*?</script>!is",
  647. '@@@COMPRESSOR:TRIM:SCRIPT@@@', $source);
  648. // Pull out the pre blocks
  649. preg_match_all("!<pre>.*?</pre>!is", $source, $match);
  650. $_pre_blocks = $match[0];
  651. $source = preg_replace("!<pre>.*?</pre>!is",
  652. '@@@COMPRESSOR:TRIM:PRE@@@', $source);
  653. // Pull out the textarea blocks
  654. preg_match_all("!<textarea[^>]+>.*?</textarea>!is", $source, $match);
  655. $_textarea_blocks = $match[0];
  656. $source = preg_replace("!<textarea[^>]+>.*?</textarea>!is",
  657. '@@@COMPRESSOR:TRIM:TEXTAREA@@@', $source);
  658. // remove all leading spaces, tabs and carriage returns NOT
  659. // preceeded by a php close tag.
  660. $source = trim(preg_replace('/((?<!\?>)\n)[\s]+/m', '\1', $source));
  661. //Remove comments
  662. //$source = preg_replace("/<!--.*-->/U","",$source);
  663. // replace textarea blocks
  664. $this->trimwhitespace_replace("@@@COMPRESSOR:TRIM:TEXTAREA@@@",$_textarea_blocks, $source);
  665. // replace pre blocks
  666. $this->trimwhitespace_replace("@@@COMPRESSOR:TRIM:PRE@@@",$_pre_blocks, $source);
  667. // replace script blocks
  668. $this->trimwhitespace_replace("@@@COMPRESSOR:TRIM:SCRIPT@@@",$_script_blocks, $source);
  669. return $source;
  670. }
  671. /**
  672. * Helper function for trimwhitespace
  673. *
  674. **/
  675. function trimwhitespace_replace($search_str, $replace, &$subject) {
  676. $_len = strlen($search_str);
  677. $_pos = 0;
  678. for ($_i=0, $_count=count($replace); $_i<$_count; $_i++)
  679. if (($_pos=strpos($subject, $search_str, $_pos))!==false)
  680. $subject = substr_replace($subject, $replace[$_i], $_pos, $_len);
  681. else
  682. break;
  683. }
  684. /**
  685. * Gets the directory we are in
  686. *
  687. **/
  688. function get_current_path($trailing=false) {
  689. $current_dir = $this->view->paths->relative->current_directory;
  690. //Remove trailing slash
  691. if($trailing) {
  692. if(substr($current_dir,-1,1) == "/") {
  693. $current_dir = substr($current_dir,0,-1);
  694. }
  695. }
  696. return $current_dir;
  697. }
  698. /**
  699. * Gets the head part of the $source
  700. *
  701. **/
  702. function get_head($source) {
  703. preg_match("!<head([^>]+)?>.*?</head>!is", $source, $matches);
  704. if(!empty($matches[0])) {
  705. $head = $matches[0];
  706. // Pull out the comment blocks, so as to avoid touching conditional comments
  707. $head = preg_replace("@<!--.*?-->@is",
  708. '@@@COMPRESSOR:TRIM:HEADCOMMENT@@@', $head);
  709. return $head;
  710. }
  711. }
  712. /**
  713. * Removes old cache files
  714. *
  715. **/
  716. function do_cleanup() {
  717. //Get all directories
  718. foreach($this->options AS $key=>$value) {
  719. if(!empty($value['cachedir'])) {
  720. $active_dirs[] = $value['cachedir'];
  721. }
  722. }
  723. if(!empty($active_dirs)) {
  724. foreach($active_dirs AS $path) {
  725. $files = $this->get_files_in_dir($path);
  726. foreach($files AS $file) {
  727. if (strstr($file,"_cmp_") && !strstr($this->compressed_files_string,$file)) {
  728. if(file_exists($path . "/" . $file)) {
  729. unlink($path . "/" . $file);
  730. }
  731. } // end if
  732. }
  733. }
  734. }
  735. }
  736. /**
  737. * Returns list of files in a directory
  738. *
  739. **/
  740. function get_files_in_dir($path) {
  741. // open this directory
  742. $myDirectory = opendir($path);
  743. // get each entry
  744. while($entryName = readdir($myDirectory))
  745. {
  746. $dirArray[] = $entryName;
  747. }
  748. // close directory
  749. closedir($myDirectory);
  750. return $dirArray;
  751. }
  752. //Adds CSS media info
  753. function add_media_header($content,$path) {
  754. preg_match("@(media)=[\"'](.*?)[\"']@",$path['location'],$media); //|media
  755. if($media[2]) {
  756. $content = "@media " . $media[2] . " {" . $content;
  757. $content .= " }";
  758. }
  759. return $content;
  760. }
  761. //Find background images in the CSS and convert their paths to absolute
  762. function convert_paths_to_absolute($content,$path) {
  763. preg_match_all( "/url\((.*?)\)/is",$content,$matches);
  764. if(count($matches[1]) > 0) {
  765. $counter = 0;
  766. foreach($matches[1] AS $key=>$file) {
  767. if(strstr($file,"data:")) { //Don't touch data URIs
  768. continue;
  769. }
  770. $counter++;
  771. $original_file = trim($file);
  772. $file = preg_replace("@'|\"@","",$original_file);
  773. if(substr($file,0,1) != "/" && substr($file,0,5) != "http:") { //Not absolute
  774. $full_path_to_image = str_replace($this->view->get_basename($path['src']),"",$path['src']);
  775. $absolute_path = "/". $this->view->prevent_leading_slash(str_replace($this->unify_dir_separator($this->view->paths['full']['document_root']),"",$this->unify_dir_separator($full_path_to_image . $file)));
  776. $marker = md5($counter);
  777. $markers[$marker] = $absolute_path;
  778. $content = str_replace($original_file,$marker,$content);
  779. }
  780. }
  781. }
  782. if(!empty($markers) && is_array($markers)) {
  783. //Replace the markers for the real path
  784. foreach($markers AS $md5=>$real_path) {
  785. $content = str_replace($md5,$real_path,$content);
  786. }
  787. }
  788. return $content;
  789. }
  790. /**
  791. * Take CSS background images and convert to data URIs
  792. **/
  793. function convert_css_bgr_to_data($content,$path) {
  794. preg_match_all( "/url\((.*?)\)/is",$content,$matches);
  795. if(count($matches[1]) > 0) {
  796. $matches[1] = array_unique($matches[1]); //Unique
  797. foreach($matches[1] AS $key=>$file) {
  798. $original_file = trim($file);
  799. //Get full path
  800. $file_path = $this->view->ensure_trailing_slash($this->view->paths['full']['document_root']) . $this->view->prevent_leading_slash($original_file);
  801. $file_path = trim($file_path);
  802. //Get mime type
  803. $mime = $this->get_mimetype($file_path);
  804. //Get file contents
  805. $contents = @file_get_contents($file_path);
  806. //Base64 encode contents
  807. $base64 = base64_encode($contents);
  808. //Set new data uri
  809. $data_uri = ('data:' . $mime . ';base64,' . $base64);
  810. //Find the element this refers to
  811. $regex = "([a-z0-9\s\.\:#_\-@]+)\{([^\}]+?" . str_replace("/","\/",str_replace(".","\.",$original_file)) ."[^\}]+?)\}";
  812. preg_match_all("/".$regex."/is",$content,$elements);
  813. //IE only conditional style
  814. if(is_array($elements[1])) {
  815. foreach($elements[1] AS $selector) {
  816. $this->ie_only_css[] = " " . $selector . " { *background-image:url(" . $original_file.")}";
  817. }
  818. }
  819. //Replace
  820. $content = str_replace($original_file,$data_uri,$content);
  821. }
  822. }
  823. //Add IE only css
  824. if(is_array($this->ie_only_css)) {
  825. $content .= implode(" ",$this->ie_only_css);
  826. }
  827. return $content;
  828. }
  829. //Make the sep the same
  830. function unify_dir_separator($path) {
  831. if (DIRECTORY_SEPARATOR != '/') {
  832. return str_replace (DIRECTORY_SEPARATOR, '/', $path);
  833. } else {
  834. return $path;
  835. }
  836. }
  837. /**
  838. * Get file extension
  839. *
  840. **/
  841. function get_file_extension($file) {
  842. return array_pop(explode('.',$file));
  843. }
  844. /**
  845. * Get mime from extension
  846. *
  847. **/
  848. function get_mimetype($value='') {
  849. $ct['htm'] = 'text/html';
  850. $ct['html'] = 'text/html';
  851. $ct['txt'] = 'text/plain';
  852. $ct['asc'] = 'text/plain';
  853. $ct['bmp'] = 'image/bmp';
  854. $ct['gif'] = 'image/gif';
  855. $ct['jpeg'] = 'image/jpeg';
  856. $ct['jpg'] = 'image/jpeg';
  857. $ct['jpe'] = 'image/jpeg';
  858. $ct['png'] = 'image/png';
  859. $ct['ico'] = 'image/vnd.microsoft.icon';
  860. $ct['mpeg'] = 'video/mpeg';
  861. $ct['mpg'] = 'video/mpeg';
  862. $ct['mpe'] = 'video/mpeg';
  863. $ct['qt'] = 'video/quicktime';
  864. $ct['mov'] = 'video/quicktime';
  865. $ct['avi'] = 'video/x-msvideo';
  866. $ct['wmv'] = 'video/x-ms-wmv';
  867. $ct['mp2'] = 'audio/mpeg';
  868. $ct['mp3'] = 'audio/mpeg';
  869. $ct['rm'] = 'audio/x-pn-realaudio';
  870. $ct['ram'] = 'audio/x-pn-realaudio';
  871. $ct['rpm'] = 'audio/x-pn-realaudio-plugin';
  872. $ct['ra'] = 'audio/x-realaudio';
  873. $ct['wav'] = 'audio/x-wav';
  874. $ct['css'] = 'text/css';
  875. $ct['zip'] = 'application/zip';
  876. $ct['pdf'] = 'application/pdf';
  877. $ct['doc'] = 'application/msword';
  878. $ct['bin'] = 'application/octet-stream';
  879. $ct['exe'] = 'application/octet-stream';
  880. $ct['class']= 'application/octet-stream';
  881. $ct['dll'] = 'application/octet-stream';
  882. $ct['xls'] = 'application/vnd.ms-excel';
  883. $ct['ppt'] = 'application/vnd.ms-powerpoint';
  884. $ct['wbxml']= 'application/vnd.wap.wbxml';
  885. $ct['wmlc'] = 'application/vnd.wap.wmlc';
  886. $ct['wmlsc']= 'application/vnd.wap.wmlscriptc';
  887. $ct['dvi'] = 'application/x-dvi';
  888. $ct['spl'] = 'application/x-futuresplash';
  889. $ct['gtar'] = 'application/x-gtar';
  890. $ct['gzip'] = 'application/x-gzip';
  891. $ct['js'] = 'application/x-javascript';
  892. $ct['swf'] = 'application/x-shockwave-flash';
  893. $ct['tar'] = 'application/x-tar';
  894. $ct['xhtml']= 'application/xhtml+xml';
  895. $ct['au'] = 'audio/basic';
  896. $ct['snd'] = 'audio/basic';
  897. $ct['midi'] = 'audio/midi';
  898. $ct['mid'] = 'audio/midi';
  899. $ct['m3u'] = 'audio/x-mpegurl';
  900. $ct['tiff'] = 'image/tiff';
  901. $ct['tif'] = 'image/tiff';
  902. $ct['rtf'] = 'text/rtf';
  903. $ct['wml'] = 'text/vnd.wap.wml';
  904. $ct['wmls'] = 'text/vnd.wap.wmlscript';
  905. $ct['xsl'] = 'text/xml';
  906. $ct['xml'] = 'text/xml';
  907. $extension = $this->get_file_extension($value);
  908. if (!$type = $ct[strtolower($extension)]) {
  909. $type = 'text/html';
  910. }
  911. return $type;
  912. }
  913. //Start script timing
  914. function startTimer() {
  915. $mtime = microtime();
  916. $mtime = explode(" ",$mtime);
  917. $mtime = $mtime[1] + $mtime[0];
  918. $starttime = $mtime;
  919. return $starttime;
  920. }
  921. //Return current time
  922. function returnTime($starttime) {
  923. $mtime = microtime();
  924. $mtime = explode(" ",$mtime);
  925. $mtime = $mtime[1] + $mtime[0];
  926. $endtime = $mtime;
  927. $totaltime = ($endtime - $starttime);
  928. return $totaltime;
  929. }
  930. } // end class
  931. ?>