1. <?php
  2.  
  3. /**
  4. * @desc Allows Javascript and CSS to be compressed, cached and outputted (optionally with gzip)
  5. * @author DKi Design <darkimmortal@dkimmortal.com>
  6. *
  7. * FYI: Cache creation and/or updates will fail if only one location/url combo is changed (to use multiple instances, both the CSS and JS locations and urls must be changed).
  8. */
  9. class jcCache {
  10.  
  11.  /**
  12.  * @var  array   Stores the CSS files (location, modification date)
  13.  */
  14.  public $css=array("l"=>array(),"d"=>array());
  15.  
  16.  /**
  17.  * @var  array   Stores the JS files (location, modification date)
  18.  */
  19.  public $js=array("l"=>array(),"d"=>array());
  20.  
  21.  /**
  22.  * @var  bool   Skip files that are too large (would most likely crash server)
  23.  */
  24.  public $toobig=true;
  25.  
  26.  /**
  27.  * @var  string  Error log (accessible through showErrors())
  28.  */
  29.  public $errors="";
  30.  
  31.  /**
  32.  * @var time     Timestamp of modification data of CSS cache
  33.  */
  34.  private $csscache;
  35.  
  36.  /**
  37.  * @var  time    Timestamp of modification data of JS cache
  38.  */
  39.  private $jscache;
  40.  
  41.  /**
  42.  * @var  int     Number of extra caches used due to insufficent server memory for the javascript compressor to run on all of it at once
  43.  */
  44.  private $extraCaches;
  45.  
  46.  /**
  47.  * @var  string  CSS Cache location
  48.  */
  49.  public $cssCacheLocation="jccache.css";
  50.  
  51.  /**
  52.  * @var  string  JS Cache location
  53.  */
  54.  public $jsCacheLocation="jccache.js";
  55.  
  56.  /**
  57.  * @var  string  CSS Cache location as URL (if relative then relative to page that is outputting)
  58.  */
  59.  public $cssCacheURL="jccache.css";
  60.  
  61.  /**
  62.  * @var  string  JS Cache location as URL (if relative then relative to page that is outputting)
  63.  */
  64.  public $jsCacheURL="jccache.js";
  65.  
  66.  /**
  67.  * @var  bool    Made new JS Cache?
  68.  */
  69.  private $jsCacheMade=false;
  70.  
  71.  /**
  72.  * @var  bool    Made new CSS Cache?
  73.  */
  74.  private $cssCacheMade=false;
  75.  
  76.  /**
  77.  * @var  bool    Force cache regeneration? (not intended for live script)
  78.  */
  79.  public $forceCacheRegen=false;
  80.  
  81.  /**
  82.  * @var  bool    Prevent cache regeneration? (dunno why but its there anyway)
  83.  */
  84.  public $preventCacheRegen=false;
  85.  
  86.  /**
  87.  * @var  bool    Minify js/css?
  88.  */
  89.  public $compress=true;
  90.  
  91.  
  92.  /**
  93.  * @desc Initialises the cache files and prepares the class for use. Must be called AFTER properties are set but BEFORE any methods are used.
  94.  */
  95.  public function init(){
  96.   if(!file_exists($this->cssCacheLocation)){
  97.    $this->errors.="\nNOTICE: CSS cache ({$this->cssCacheLocation}) not found - writing new file";
  98.    $cssh=@fopen($this->cssCacheLocation,'w');
  99.    if(!$cssh){
  100.     $this->errors.="\nFATAL ERROR: CSS cache could not be created at: {$this->cssCacheLocation}";
  101.     $this->showErrors();
  102.     die();
  103.    }
  104.    fclose($cssh);
  105.    $this->forceCacheRegen=true;
  106.    
  107.    if(!@touch($this->cssCacheLocation,1)){
  108.     $this->errors.="\nWARNING: Access time of the CSS cache ({$this->cssCacheLocation}) could not be changed. It must be forcefully updated via the forceCacheRegen option.";
  109.    } else {
  110.     $this->errors.="\nNOTICE: Access time of the CSS cache ({$this->cssCacheLocation}) was changed successfully.";
  111.    }
  112.   }
  113.   if(!file_exists($this->jsCacheLocation) || filesize($this->jsCacheLocation) < 1){
  114.    $this->errors.="\nNOTICE: JS cache ({$this->jsCacheLocation}) not found / too small - writing new file";
  115.    $jsh=fopen($this->jsCacheLocation,'w');
  116.    if(!$jsh){
  117.     $this->errors.="\nFATAL ERROR: JS cache could not be created at: {$this->jsCacheLocation}";
  118.     $this->showErrors();
  119.     die();
  120.    }
  121.    fclose($jsh);
  122.    $this->forceCacheRegen=true;
  123.    
  124.    if(!@touch($this->jsCacheLocation,1)){
  125.     $this->errors.="\nWARNING: Access time of the JS cache ({$this->jsCacheLocation}) could not be changed. It must be forcefully updated via the forceCacheRegen option.";
  126.    } else {
  127.     $this->errors.="\nNOTICE: Access time of the JS cache ({$this->jsCacheLocation}) was changed successfully.";
  128.    }
  129.   }
  130.   if(!is_writable($this->cssCacheLocation)){
  131.    $this->errors.="\nFATAL ERROR: CSS cache ({$this->cssCacheLocation}) is not writable.";
  132.    $this->showErrors();
  133.    die();
  134.   }
  135.   if(!is_writable($this->jsCacheLocation)){
  136.    $this->errors.="\nFATAL ERROR: JS cache ({$this->jsCacheLocation}) is not writable.";
  137.    $this->showErrors();
  138.    die();
  139.   }
  140.   $this->csscache=filemtime($this->cssCacheLocation);
  141.  
  142.   $this->jscache=filemtime($this->jsCacheLocation);
  143.   $this->errors.="\nNOTICE: CSS Cache ready for reading/writing at {$this->cssCacheLocation} and was last updated at ".date(DATE_RFC2822,$this->csscache);
  144.   $this->errors.="\nNOTICE: JS Cache ready for reading/writing at {$this->jsCacheLocation} and was last updated at ".date(DATE_RFC2822,$this->jscache);
  145.  
  146.  }
  147.    
  148.  
  149.  /**
  150.  * @desc Adds a Javascript file
  151.  * @param Server-side location of file
  152.  */
  153.  public function addJS($file){
  154.   if(file_exists($file) and is_readable($file)){
  155.    array_push($this->js["l"],$file);
  156.    array_push($this->js["d"],filemtime($file));
  157.   } else {
  158.    $this->errors.="\nError loading Javascript file: $file";
  159.   }
  160.  }
  161.  
  162.  /**
  163.  * @desc Adds a CSS file
  164.  * @param Server-side location of file
  165.  */
  166.  public function addCSS($file){
  167.   if(file_exists($file) and is_readable($file)){
  168.    array_push($this->css["l"],$file);
  169.    array_push($this->css["d"],filemtime($file));
  170.   } else {
  171.    $this->errors.="\nError loading CSS file: $file";
  172.   }
  173.  }
  174.  
  175.  /**
  176.  * @desc Compresses CSS code (removes comments and whitespace)
  177.  * @param    string  CSS code
  178.  *
  179.  * @return   string  Compressed CSS Code
  180.  */
  181.  public function compressCSS($css){
  182.   $css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
  183.   $css = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '   ', '    '), '', $css);
  184.   $css = str_replace(array("('",'("'), '(', $css);
  185.   $css = str_replace(array("')",'")'), ')', $css);
  186.   return $css;
  187.  }
  188.  
  189.  /**
  190.  * @desc Compresses Javascript code (via rgrove's high-speed PHP5 Javascript Minifier)
  191.  * @param    string  Javascript code
  192.  * @param    bool    Compress?
  193.  *
  194.  * @return   string  Minified Javascript Code
  195.  */
  196.  public function compressJS($js,$compress=true){
  197.   if(!$compress)
  198.    return $js;
  199.   return JSMin::minify($js);
  200.  }
  201.  
  202.  /**
  203.  * @desc Outputs JS files
  204.  */
  205.  public function outputJS(){
  206.   //foreach($this->js["d"] as $date){
  207.   $date=max($this->js["d"]);
  208.    if(($date > $this->jscache or $this->forceCacheRegen) and !$this->jsCacheMade and !$this->preventCacheRegen){
  209.     $this->errors.="\n".$this->js['l']." LAST UPDATED: ".date(DATE_RFC2822,$date);
  210.    
  211.     $this->jsCacheMade=true;
  212.     $this->newJS();
  213.    }
  214.   //}
  215.   //echo "<script type='text/javascript' src='{$this->jsCacheURL}?". ($this->jsCacheMade) ? random_string::output() : "" ."' ></script>";
  216.   $randcode=$this->jsCacheMade ? "?r=" . random_string::output() : "";
  217.   $compress="";//$this->compress ? "cp.php?type=js&amp;compress=true&amp;filename=" : "";
  218.   echo "\n<script type='text/javascript' src='$compress{$this->jsCacheURL}$randcode'></script>";
  219.   //for($curr = 1; $curr <= $this->extraCaches; $curr++){
  220.   //}
  221.  
  222.  }
  223.  
  224.  /**
  225.  * @desc Outputs CSS files
  226.  */
  227.  public function outputCSS(){
  228.   foreach($this->css["d"] as $date){
  229.    if(($date > $this->csscache or $this->forceCacheRegen) and !$this->cssCacheMade and !$this->preventCacheRegen){
  230.     $this->cssCacheMade=true;
  231.     $this->newCSS();
  232.    }
  233.   }
  234.   //echo "<link rel='stylesheet' type='text/css' href='{$this->cssCacheURL}?". ($this->cssCacheMade) ? random_string::output() : "" ."' />";
  235.   $randcode=$this->cssCacheMade ? "?r=" . random_string::output() : "";
  236.   $compress=$this->compress ? "cp.php?type=css&amp;compress=true&amp;filename=" : "";
  237.   //echo "\n<link rel='stylesheet' type='text/css' href='$compress{$this->cssCacheURL}$randcode' />";
  238.   echo "\n<link rel='stylesheet' type='text/css' href='".$this->cssCacheURL."' />";
  239.  }
  240.  
  241.  /**
  242.  * @desc Outputs everything
  243.  */
  244.  public function output(){
  245.   $this->outputCSS();
  246.   $this->outputJS();
  247.  }
  248.  
  249.  /**
  250.  * @desc Generates new JS cache
  251.  */
  252.  public function newJS(){
  253.   foreach($this->js["l"] as $loc){
  254.    $js2=@file_get_contents($loc,null,null,0,$this->toobig?5000000:null);
  255.    if(!$js2)
  256.     $this->errors.="\nWARNING: JS File at $loc could not be read and so is not part of the cache and will not be received by browsers.";
  257.    else
  258.     $js.=$js2;
  259.   }
  260.   $meme=round(str_ireplace("M","",ini_get("memory_limit"))*1024*1024/100, 0);
  261.   //$meme=10000;
  262.   $this->errors.="\n$meme\n";
  263.   if(strlen($js) > $meme){
  264.    $this->extraCaches=ceil(strlen($js)/$meme)+1;
  265.    $this->errors.="\ngay {$this->extraCaches}\n";
  266.    for($cac = 1; $cac <= $this->extraCaches; $cac++){
  267.     $jspart=substr($js,($cac-1)*$meme,$cac*$meme);
  268.     $cache=$this->compressJS($jspart);
  269.     file_put_contents($this->jsCacheLocation."_".$cac,$cache);
  270.    }
  271.    $js="";
  272.    for($cac = 1; $cac <= $this->extraCaches; $cac++){
  273.     $loc=$this->jsCacheLocation."_".$cac;
  274.     $js2 = @file_get_contents($loc,null,null,0,$this->toobig?5000000:null);
  275.     if(!$js2)
  276.      $this->errors.="\nWARNING: JS File at $loc could not be read and so is not part of the cache and will not be received by browsers.";
  277.     else
  278.      $cache.=$js2;
  279.     unlink($loc);
  280.    }              
  281.   } else {
  282.    $cache=$this->compressJS($js, $this->compress);
  283.   /*    $totalsize=strlen($cache);
  284.    $dun=0;
  285.    //$cursize=0;
  286.    //while($cursize <= $totalsize){
  287.    for($cursize = 0; $cursize <= $totalsize; $cursize+=$this->perFile){
  288.     $dun++;
  289.     file_put_contents(str_replace(".js",$dun.".js",$this->jsCacheLocation),substr($cache, $cursize, $this->perFile));
  290.     //$currsize+=$this->perFile;
  291.    }
  292.    $this->extraCaches=$dun;*/
  293.    
  294.   }
  295.   //touch($this->jsCacheLocation, filemtime($this->jsCacheLocation) - 24*3600); // time zone difference
  296.  //    debug($cache);
  297.   //file_put_contents($this->jsCacheLocation,$cache);
  298.   file_put_contents($this->jsCacheLocation, $cache);
  299.   $this->errors.="\nNOTICE: New Javascript cache written successfully";
  300.  
  301.  }
  302.  
  303.  /**
  304.  * @desc Generates new CSS cache
  305.  */
  306.  public function newCSS(){
  307.   foreach($this->css["l"] as $loc){
  308.    $css2=@file_get_contents($loc,null,null,0,$this->toobig?5000000:null);
  309.    if(!$css2)
  310.     $this->errors.="\nWARNING: CSS File at $loc could not be read and so is not part of the cache and will not be received by browsers.";
  311.    else
  312.     $css.=$css2;
  313.   }
  314.   $cache=$this->compressCSS($css);
  315.   file_put_contents($this->cssCacheLocation,$cache);
  316.   //touch($this->cssCacheLocation, filemtime($this->cssCacheLocation) - 24*3600); // time zone difference
  317.   $this->errors.="\nNOTICE: New CSS cache written successfully";
  318.  }
  319. } // End Class
  320.  
  321.  
  322.  
  323. ?>