PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/jojo_core/classes/Jojo/Stitcher.php

http://jojocms.googlecode.com/
PHP | 331 lines | 240 code | 43 blank | 48 comment | 46 complexity | e445f64a021b171d085fec0c6d4121ee MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, MIT
  1. <?php
  2. /**
  3. * Jojo CMS
  4. * ================
  5. *
  6. * Copyright 2007-2008 Harvey Kane <code@ragepank.com>
  7. * Copyright 2007-2008 Michael Holt <code@gardyneholt.co.nz>
  8. * Copyright 2007 Melanie Schulz <mel@gardyneholt.co.nz>
  9. *
  10. * See the enclosed file license.txt for license information (LGPL). If you
  11. * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
  12. *
  13. * @author Harvey Kane <code@ragepank.com>
  14. * @author Michael Cochrane <mikec@jojocms.org>
  15. * @author Melanie Schulz <mel@gardyneholt.co.nz>
  16. * @license http://www.fsf.org/copyleft/lgpl.html GNU Lesser General Public License
  17. * @link http://www.jojocms.org JojoCMS
  18. * @package jojo_core
  19. */
  20. class Jojo_Stitcher {
  21. var $data;
  22. var $modified;
  23. var $dirty = true;
  24. var $type = 'css'; //css or javascript
  25. var $numfiles = 0;
  26. var $header = '';
  27. function Jojo_Stitcher($modified=0)
  28. {
  29. $this->modified = ($modified == 0) ? strtotime('00:00') : $modified;
  30. if (!defined('_PROTOCOL')) define('_PROTOCOL', 'http://');
  31. }
  32. function addFile($file)
  33. {
  34. static $_added;
  35. if (!file_exists($file)) {
  36. return false;
  37. }
  38. if (isset($_added[$file])) {
  39. return false;
  40. }
  41. $handle = fopen($file, "r");
  42. $data = filesize($file) > 0 ? fread($handle, filesize($file)) : '';
  43. fclose($handle);
  44. $this->data .= $data . "\n";
  45. $this->numfiles++;
  46. $this->dirty = true;
  47. $_added[$file] = true;;
  48. }
  49. function addText($text)
  50. {
  51. $this->data .= $text . "\n";
  52. $this->numfiles++;
  53. $this->dirty = true;
  54. }
  55. function getServerCache()
  56. {
  57. if (false && _CONTENTCACHE && !isset($_SERVER['HTTP_PRAGMA'])) {
  58. //$cacheuserid = isset($_USERID) ? $_USERID : 0;
  59. $cacheuserid = 0;
  60. $query = 'SELECT * FROM {contentcache} WHERE cc_url = ? AND cc_userid = ? AND cc_expires > ? LIMIT 1';
  61. $values = array(
  62. _PROTOCOL . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
  63. $cacheuserid,
  64. strtotime('now')
  65. );
  66. $contentcache = Jojo::selectQuery($query, $values);
  67. if (count($contentcache) == 1) {
  68. $this->header .= '/** [Page generated '.date('d M y h:i:sa') . ' based on copy cached '.date('d M h:i:s',$contentcache[0]['cc_cached']) . '. This copy expires '.date('d M h:i',$contentcache[0]['cc_expires']) . '] **/'."\n";
  69. $modified = $contentcache[0]['cc_cached'];
  70. $this->addText($contentcache[0]['cc_content']);
  71. $this->output(false);
  72. exit;
  73. }
  74. }
  75. }
  76. function setServerCache()
  77. {
  78. //global $_USERID;
  79. if (false && _CONTENTCACHE) {
  80. $this->optimize();
  81. //$cacheuserid = isset($_USERID) ? $_USERID : 0;
  82. $cacheuserid = 0;
  83. $query = 'REPLACE INTO {contentcache} SET cc_url = ?, cc_userid = ?, cc_content = ?, cc_cached = ?, cc_expires = ?';
  84. $values = array(
  85. _PROTOCOL . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
  86. $cacheuserid,
  87. $this->data,
  88. strtotime('now'),
  89. strtotime('+'._CONTENTCACHETIME . ' second')
  90. );
  91. Jojo::updateQuery($query, $values);
  92. }
  93. if (false && _CONTENTCACHE) {
  94. $this->header .= '/** [Code generated '.date('d M y h:i:sa',strtotime('now')) . ' and cached until '.date('d M y h:ia',strtotime('+'._CONTENTCACHETIME . ' second')) . '] **/'."\n";
  95. } else {
  96. $this->header .= '/** [Code generated '.date('d M y h:i:sa',strtotime('now')) . '] **/'."\n";
  97. }
  98. }
  99. function optimize()
  100. {
  101. if ($this->dirty && $this->type == 'css') {
  102. $this->data = $this->optimizeCSS($this->data);
  103. } elseif ($this->dirty && $this->type == 'javascript') {
  104. $this->data = $this->optimizeJS($this->data);
  105. }
  106. $this->dirty = false; //we can set this because we have just optimised the code
  107. }
  108. function output($optimize = true)
  109. {
  110. if ($optimize) {
  111. $this->optimize();
  112. }
  113. $this->sendCacheHeaders($this->modified);
  114. if (Jojo::getOption('enablegzip') == 'yes') Jojo::gzip();
  115. header('Content-type: text/'.$this->type);
  116. header('Cache-Control: ');
  117. header('Pragma: ');
  118. header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T',$this->modified));
  119. header('Expires: ');
  120. if (_DEBUG) {
  121. echo $this->header . $this->data;
  122. return;
  123. }
  124. echo $this->data;
  125. }
  126. function fetch()
  127. {
  128. $this->optimize();
  129. if (_DEBUG) {
  130. return $this->header . $this->data;
  131. }
  132. return $this->data;
  133. }
  134. /* TODO - a simple php4 JS optimisation function, which removes extra characters */
  135. function optimizeJS($js)
  136. {
  137. $original = $js;
  138. /* JSMin will only work on PHP5+ */
  139. require_once(_BASEPLUGINDIR . '/jojo_core/external/jsmin/jsmin.php');
  140. try {
  141. $js = JSMin::minify($js);
  142. } catch (Exception $e) { }
  143. $savings = strlen($original) - strlen($js);
  144. if ($savings != 0) {
  145. $this->header .= "/* optimization saves $savings bytes & " . ($this->numfiles - 1) . " HTTP requests */\n";
  146. }
  147. return $js;
  148. }
  149. /* a simple CSS optimisation function, which removes extra characters from CSS */
  150. function optimizeCSS($css)
  151. {
  152. $timer = Jojo::timer();
  153. $original = $css;
  154. /* Remove commented out blocks of code */
  155. $css = preg_replace('#/\*(.*)\*/#Ums', '', $css);
  156. preg_match_all('/^([\r\n\s\S]*)\{([\r\n\s\S]*)}/Um', $css, $matches);
  157. $temp = array();
  158. $css = array();
  159. foreach ($matches[1] as $k => $class) {
  160. $class = "\n" . rtrim(str_replace(array("\n", "\r"), '', $class));
  161. $class = substr($class, strrpos($class, "\n") + 1);
  162. $attribs = trim(str_replace("\r\n", "\n", $matches[2][$k])) . ';';
  163. $cleanAttribs = '';
  164. /* Clean attribs*/
  165. //preg_match_all('/([a-z \-]*):(.*);/mU', $attribs, $attribMatches);
  166. preg_match_all('/([a-z \-]*):(.*)(?<!data:image\/png|data:image\/gif|data:image\/jpg|data:image\/jpeg);/mU', $attribs, $attribMatches); //fix for uri data
  167. $attribs = '';
  168. foreach ($attribMatches[1] as $j => $attribName) {
  169. /* Replace strings that might only occure in attributes */
  170. $value = trim(str_replace(array(' 0px', '.0em'), array(' 0', 'em'), ' ' . $attribMatches[2][$j]));
  171. /* url('foo.jpg') => url(foo.jpg) */
  172. $value = preg_replace('/url\\(\'(.+?)\'\\)/i', 'url(\\1)', $value);
  173. $attribs .= sprintf("%s:%s;\n", trim($attribName), trim($value));
  174. }
  175. $attribs = trim($attribs);
  176. /* Check for a class already being declared */
  177. if (isset($temp[$class])) {
  178. /* Check for overwritten attributes */
  179. foreach ($temp[$class] as $tk => $tattribs) {
  180. if ($tattribs == $attribs) {
  181. /* Second declaration same as first, removing first */
  182. unset($css[$temp[$class][$tk]['pos']]);
  183. unset($temp[$class][$tk]);
  184. } else {
  185. /* Extract attributes from first declaration */
  186. preg_match_all('/([a-z \-]*):(.*);/mU', $temp[$class][$tk]['attribs'], $attribMatches);
  187. $firstAttribs = array();
  188. foreach ($attribMatches[1] as $j => $attribName) {
  189. $firstAttribs[trim($attribName)] = $attribMatches[2][$j];
  190. }
  191. /* Remove over-written attribs from first declaration
  192. and clean attribs in the same loop */
  193. $cleanAttribs = '';
  194. preg_match_all('/([a-z \-]*):(.*);/mU', $attribs, $attribMatches);
  195. foreach ($attribMatches[1] as $j => $attribName) {
  196. /* Replace strings that might only occure in attributes */
  197. $value = trim(str_replace(array(' 0px', '.0em'), array(' 0', 'em'), ' ' . $attribMatches[2][$j]));
  198. /* Trim whitespace */
  199. $attribName = trim($attribName);
  200. /* url('foo.jpg') => url(foo.jpg) */
  201. $value = preg_replace('/url\\(\'(.+?)\'\\)/i', 'url(\\1)', $value);
  202. $cleanAttribs .= sprintf("%s:%s;\n", $attribName, $value);
  203. if (isset($firstAttribs[$attribName])) {
  204. unset($firstAttribs[$attribName]);
  205. }
  206. }
  207. $attribs = trim($cleanAttribs);
  208. /* Save remaining attributes in first delcaration*/
  209. if (count($firstAttribs) == 0) {
  210. /* No attributes left, delete */
  211. unset($css[$temp[$class][$tk]['pos']]);
  212. unset($temp[$class][$tk]);
  213. } else {
  214. $newAttribs = '';
  215. foreach ($firstAttribs as $name => $value) {
  216. $newAttribs .= sprintf("%s:%s;\n", $name, $value);
  217. }
  218. $newAttribs = trim($newAttribs);
  219. $css[$temp[$class][$tk]['pos']] = array('class' => $class, 'attribs' => $newAttribs);
  220. $temp[$class][$tk]['attribs'] = $newAttribs;
  221. }
  222. }
  223. }
  224. }
  225. if ($attribs) {
  226. $css[] = array('class' => $class, 'attribs' => $attribs);
  227. $temp[$class][] = array('pos' => max(array_keys($css)), 'attribs' => $attribs);
  228. }
  229. }
  230. $optimizedText = '';
  231. foreach($css as $c) {
  232. if (_DEBUG) {
  233. $optimizedText .= sprintf("%s{\n%s\n}\n", $c['class'], $c['attribs']);
  234. } else {
  235. $optimizedText .= sprintf("%s{%s}\n", $c['class'], str_replace("\n", '', $c['attribs']));
  236. }
  237. }
  238. /* use shorthand colour codes eg #ffffff => #fff */
  239. $optimizedText = preg_replace('/#(0{3}|1{3}|2{3}|3{3}|4{3}|5{3}|6{3}|7{3}|8{3}|9{3}|a{3}|b{3}|c{3}|d{3}|e{3}|f{3}){2}/i', '#\\1', $optimizedText);
  240. $search = array(
  241. ', ', /* ', ' => ', ' (remove spaces after commas) */
  242. 'font-weight:bold;' , /* font-weight: bold; => font-weight: 700; */
  243. 'font-weight:normal;', /* font-weight: normal; => font-weight: 400; */
  244. );
  245. $replace = array(
  246. ',',
  247. 'font-weight:700;',
  248. 'font-weight:400;'
  249. );
  250. $optimizedText = str_replace($search, $replace, $optimizedText);
  251. $savings = strlen($original) - strlen($optimizedText);
  252. $this->header .= "/* Optimization saves $savings bytes & ".($this->numfiles - 1) . " HTTP requests\n";
  253. $this->header .= sprintf(" Optimization took: %0.2f ms */\n", Jojo::timer($timer) * 1000);
  254. return $optimizedText;
  255. }
  256. //http://simonwillison.net/2003/Apr/23/conditionalGet/
  257. function sendCacheHeaders($timestamp)
  258. {
  259. // A PHP implementation of conditional get, see
  260. // http://fishbowl.pastiche.org/archives/001132.html
  261. if (empty($timestamp)) $timestamp = time();
  262. $last_modified = gmdate('D, d M Y H:i:s', $timestamp).' GMT';//substr(date('r', $timestamp), 0, -5) . 'GMT';
  263. $etag = '"'.md5($last_modified) . '"';
  264. // Send the headers
  265. header("Last-Modified: $last_modified");
  266. header("ETag: $etag");
  267. // See if the client has provided the required headers
  268. $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
  269. stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
  270. false;
  271. $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
  272. stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) :
  273. false;
  274. if (!$if_modified_since && !$if_none_match) {
  275. return;
  276. }
  277. // At least one of the headers is there - check them
  278. if ($if_none_match && $if_none_match != $etag) {
  279. return; // etag is there but doesn't match
  280. }
  281. if ($if_modified_since && $if_modified_since != $last_modified) {
  282. return; // if-modified-since is there but doesn't match
  283. }
  284. // Nothing has changed since their last request - serve a 304 and exit
  285. header('HTTP/1.0 304 Not Modified');
  286. exit;
  287. }
  288. }