PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/www/assets/dbfcb592/clientscripts.php

http://smartbook.googlecode.com/
PHP | 575 lines | 389 code | 43 blank | 143 comment | 47 complexity | 4f02f67a0cf8b8c79585c1c39d253632 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Combines multiple javascript files and serve up as gzip if possible.
  4. * Allowable scripts and script dependencies can be specified in a "packages.php" file
  5. * with the following format. This "packages.php" is optional, if absent the filenames
  6. * without ".js" extension are used.
  7. *
  8. * <code>
  9. * <?php
  10. * $packages = array(
  11. * 'package1' => array('file1.js', 'file2.js'),
  12. * 'package2' => array('file3.js', 'file4.js'));
  13. *
  14. * $dependencies = array(
  15. * 'package1' => array('package1'),
  16. * 'package2' => array('package1', 'package2')); //package2 requires package1 first.
  17. * </code>
  18. *
  19. * To serve up 'package1', specify the url, a maxium of 25 packages separated with commas is allows.
  20. *
  21. * clientscripts.php?js=package1
  22. *
  23. * for 'package2' (automatically resolves 'package1') dependency
  24. *
  25. * clientscripts.php?js=package2
  26. *
  27. * The scripts comments are removed and whitespaces removed appropriately. The
  28. * scripts may be served as zipped if the browser and php server allows it. Cache
  29. * headers are also sent to inform the browser and proxies to cache the file.
  30. * Moreover, the post-processed (comments removed and zipped) are saved in this
  31. * current directory for the next requests.
  32. *
  33. * If the url contains the parameter "mode=debug", the comments are not removed
  34. * and headers invalidating the cache are sent. In debug mode, the script can still
  35. * be zipped if the browser and server supports it.
  36. *
  37. * E.g. clientscripts.php?js=package2&mode=debug
  38. *
  39. * @link http://www.pradosoft.com/
  40. * @copyright Copyright &copy; 2007 PradoSoft
  41. * @license http://www.pradosoft.com/license/
  42. * @author Wei Zhuo<weizhuo[at]gmail[dot]com>
  43. * @version $Id$
  44. * @package System.Web.Javascripts
  45. * @since 3.1
  46. */
  47. //run script for as long as needed
  48. set_time_limit(0);
  49. //set error_reporting directive
  50. @error_reporting(E_ERROR | E_WARNING | E_PARSE);
  51. function get_client_script_files()
  52. {
  53. $package_file = dirname(__FILE__).'/packages.php';
  54. if(is_file($package_file))
  55. return get_package_files($package_file, get_script_requests());
  56. else
  57. return get_javascript_files(get_script_requests());
  58. }
  59. /**
  60. * @param array list of requested libraries
  61. */
  62. function get_script_requests($max=25)
  63. {
  64. $param = isset($_GET['js']) ? $_GET['js'] : '';
  65. if(preg_match('/([a-zA-z0-9\-_])+(,[a-zA-z0-9\-_]+)*/', $param))
  66. return array_unique(explode(',', $param, $max));
  67. return array();
  68. }
  69. /**
  70. * @param string packages.php filename
  71. * @param array packages requests
  72. */
  73. function get_package_files($package_file, $request)
  74. {
  75. list($packages, $dependencies) = include($package_file);
  76. if(!(is_array($packages) && is_array($dependencies)))
  77. {
  78. error_log('Prado client script: invalid package file '.$package_file);
  79. return array();
  80. }
  81. $result = array();
  82. $found = array();
  83. foreach($request as $library)
  84. {
  85. if(isset($dependencies[$library]))
  86. {
  87. foreach($dependencies[$library] as $dep)
  88. {
  89. if(isset($found[$dep]))
  90. continue;
  91. $result = array_merge($result, (array)$packages[$dep]);
  92. $found[$dep]=true;
  93. }
  94. }
  95. else
  96. error_log('Prado client script: no such javascript library "'.$library.'"');
  97. }
  98. return $result;
  99. }
  100. /**
  101. * @param string requested javascript files
  102. * @array array list of javascript files.
  103. */
  104. function get_javascript_files($request)
  105. {
  106. $result = array();
  107. foreach($request as $file)
  108. $result[] = $file.'.js';
  109. return $result;
  110. }
  111. /**
  112. * @param array list of javascript files.
  113. * @return string combined the available javascript files.
  114. */
  115. function combine_javascript($files)
  116. {
  117. $content = '';
  118. $base = dirname(__FILE__);
  119. foreach($files as $file)
  120. {
  121. $filename = $base.'/'.$file;
  122. if(is_file($filename)) //relies on get_client_script_files() for security
  123. $content .= "\x0D\x0A".file_get_contents($filename); //add CR+LF
  124. else
  125. error_log('Prado client script: missing file '.$filename);
  126. }
  127. return $content;
  128. }
  129. /**
  130. * @param string javascript code
  131. * @param array files names
  132. * @return string javascript code without comments.
  133. */
  134. function minify_javascript($content, $files)
  135. {
  136. return JSMin::minify($content);
  137. }
  138. /**
  139. * @param boolean true if in debug mode.
  140. */
  141. function is_debug_mode()
  142. {
  143. return isset($_GET['mode']) && $_GET['mode']==='debug';
  144. }
  145. /**
  146. * @param string javascript code
  147. * @param string gzip code
  148. */
  149. function gzip_content($content)
  150. {
  151. return gzencode($content, 9, FORCE_GZIP);
  152. }
  153. /**
  154. * @param string javascript code.
  155. * @param string filename
  156. */
  157. function save_javascript($content, $filename)
  158. {
  159. file_put_contents($filename, $content);
  160. if(supports_gzip_encoding())
  161. file_put_contents($filename.'.gz', gzip_content($content));
  162. }
  163. /**
  164. * @param string comprssed javascript file to be read
  165. * @param string javascript code, null if file is not found.
  166. */
  167. function get_saved_javascript($filename)
  168. {
  169. $fn=$filename;
  170. if(supports_gzip_encoding())
  171. $fn .= '.gz';
  172. if(!is_file($fn))
  173. save_javascript(get_javascript_code(true), $filename);
  174. return file_get_contents($fn);
  175. }
  176. /**
  177. * @return string compressed javascript file name.
  178. */
  179. function compressed_js_filename()
  180. {
  181. $files = get_client_script_files();
  182. if(count($files) > 0)
  183. {
  184. $filename = sprintf('%x',crc32(implode(',',($files))));
  185. return dirname(__FILE__).'/clientscript_'.$filename.'.js';
  186. }
  187. }
  188. /**
  189. * @param boolean true to strip comments from javascript code
  190. * @return string javascript code
  191. */
  192. function get_javascript_code($minify=false)
  193. {
  194. $files = get_client_script_files();
  195. if(count($files) > 0)
  196. {
  197. $content = combine_javascript($files);
  198. if($minify)
  199. return minify_javascript($content, $files);
  200. else
  201. return $content;
  202. }
  203. }
  204. /**
  205. * Prints headers to serve javascript
  206. */
  207. function print_headers()
  208. {
  209. $expiresOffset = is_debug_mode() ? -10000 : 3600 * 24 * 10; //no cache
  210. header("Content-type: text/javascript");
  211. header("Vary: Accept-Encoding"); // Handle proxies
  212. header("Expires: " . @gmdate("D, d M Y H:i:s", @time() + $expiresOffset) . " GMT");
  213. if(($enc = supports_gzip_encoding()) !== null)
  214. header("Content-Encoding: " . $enc);
  215. }
  216. /**
  217. * @return string 'x-gzip' or 'gzip' if php supports gzip and browser supports gzip response, null otherwise.
  218. */
  219. function supports_gzip_encoding()
  220. {
  221. if(isset($_GET['gzip']) && $_GET['gzip']==='false')
  222. return false;
  223. if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
  224. {
  225. $encodings = explode(',', strtolower(preg_replace("/\s+/", "", $_SERVER['HTTP_ACCEPT_ENCODING'])));
  226. $allowsZipEncoding = in_array('gzip', $encodings) || in_array('x-gzip', $encodings) || isset($_SERVER['---------------']);
  227. $hasGzip = function_exists('ob_gzhandler');
  228. $noZipBuffer = !ini_get('zlib.output_compression');
  229. $noZipBufferHandler = ini_get('output_handler') != 'ob_gzhandler';
  230. if ( $allowsZipEncoding && $hasGzip && $noZipBuffer && $noZipBufferHandler)
  231. $enc = in_array('x-gzip', $encodings) ? "x-gzip" : "gzip";
  232. return $enc;
  233. }
  234. }
  235. /**
  236. * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
  237. *
  238. * This is pretty much a direct port of jsmin.c to PHP with just a few
  239. * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
  240. * outputs to stdout, this library accepts a string as input and returns another
  241. * string as output.
  242. *
  243. * PHP 5 or higher is required.
  244. *
  245. * Permission is hereby granted to use this version of the library under the
  246. * same terms as jsmin.c, which has the following license:
  247. *
  248. * --
  249. * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
  250. *
  251. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  252. * this software and associated documentation files (the "Software"), to deal in
  253. * the Software without restriction, including without limitation the rights to
  254. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  255. * of the Software, and to permit persons to whom the Software is furnished to do
  256. * so, subject to the following conditions:
  257. *
  258. * The above copyright notice and this permission notice shall be included in all
  259. * copies or substantial portions of the Software.
  260. *
  261. * The Software shall be used for Good, not Evil.
  262. *
  263. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  264. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  265. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  266. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  267. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  268. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  269. * SOFTWARE.
  270. * --
  271. *
  272. * @package JSMin
  273. * @author Ryan Grove <ryan@wonko.com>
  274. * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
  275. * @copyright 2007 Ryan Grove <ryan@wonko.com> (PHP port)
  276. * @license http://opensource.org/licenses/mit-license.php MIT License
  277. * @version 1.1.0 (2007-06-01)
  278. * @link http://code.google.com/p/jsmin-php/
  279. */
  280. class JSMin {
  281. const ORD_LF = 10;
  282. const ORD_SPACE = 32;
  283. protected $a = '';
  284. protected $b = '';
  285. protected $input = '';
  286. protected $inputIndex = 0;
  287. protected $inputLength = 0;
  288. protected $lookAhead = null;
  289. protected $output = array();
  290. // -- Public Static Methods --------------------------------------------------
  291. public static function minify($js) {
  292. $jsmin = new JSMin($js);
  293. return $jsmin->min();
  294. }
  295. // -- Public Instance Methods ------------------------------------------------
  296. public function __construct($input) {
  297. $this->input = str_replace("\r\n", "\n", $input);
  298. $this->inputLength = strlen($this->input);
  299. }
  300. // -- Protected Instance Methods ---------------------------------------------
  301. protected function action($d) {
  302. switch($d) {
  303. case 1:
  304. $this->output[] = $this->a;
  305. case 2:
  306. $this->a = $this->b;
  307. if ($this->a === "'" || $this->a === '"') {
  308. for (;;) {
  309. $this->output[] = $this->a;
  310. $this->a = $this->get();
  311. if ($this->a === $this->b) {
  312. break;
  313. }
  314. if (ord($this->a) <= self::ORD_LF) {
  315. throw new JSMinException('Unterminated string literal.');
  316. }
  317. if ($this->a === '\\') {
  318. $this->output[] = $this->a;
  319. $this->a = $this->get();
  320. }
  321. }
  322. }
  323. case 3:
  324. $this->b = $this->next();
  325. if ($this->b === '/' && (
  326. $this->a === '(' || $this->a === ',' || $this->a === '=' ||
  327. $this->a === ':' || $this->a === '[' || $this->a === '!' ||
  328. $this->a === '&' || $this->a === '|' || $this->a === '?')) {
  329. $this->output[] = $this->a;
  330. $this->output[] = $this->b;
  331. for (;;) {
  332. $this->a = $this->get();
  333. if ($this->a === '/') {
  334. break;
  335. }
  336. elseif ($this->a === '\\') {
  337. $this->output[] = $this->a;
  338. $this->a = $this->get();
  339. }
  340. elseif (ord($this->a) <= self::ORD_LF) {
  341. throw new JSMinException('Unterminated regular expression '.
  342. 'literal.');
  343. }
  344. $this->output[] = $this->a;
  345. }
  346. $this->b = $this->next();
  347. }
  348. }
  349. }
  350. protected function get() {
  351. $c = $this->lookAhead;
  352. $this->lookAhead = null;
  353. if ($c === null) {
  354. if ($this->inputIndex < $this->inputLength) {
  355. $c = $this->input[$this->inputIndex];
  356. $this->inputIndex += 1;
  357. }
  358. else {
  359. $c = null;
  360. }
  361. }
  362. if ($c === "\r") {
  363. return "\n";
  364. }
  365. if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
  366. return $c;
  367. }
  368. return ' ';
  369. }
  370. protected function isAlphaNum($c) {
  371. return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
  372. }
  373. protected function min() {
  374. $this->a = "\n";
  375. $this->action(3);
  376. while ($this->a !== null) {
  377. switch ($this->a) {
  378. case ' ':
  379. if ($this->isAlphaNum($this->b)) {
  380. $this->action(1);
  381. }
  382. else {
  383. $this->action(2);
  384. }
  385. break;
  386. case "\n":
  387. switch ($this->b) {
  388. case '{':
  389. case '[':
  390. case '(':
  391. case '+':
  392. case '-':
  393. $this->action(1);
  394. break;
  395. case ' ':
  396. $this->action(3);
  397. break;
  398. default:
  399. if ($this->isAlphaNum($this->b)) {
  400. $this->action(1);
  401. }
  402. else {
  403. $this->action(2);
  404. }
  405. }
  406. break;
  407. default:
  408. switch ($this->b) {
  409. case ' ':
  410. if ($this->isAlphaNum($this->a)) {
  411. $this->action(1);
  412. break;
  413. }
  414. $this->action(3);
  415. break;
  416. case "\n":
  417. switch ($this->a) {
  418. case '}':
  419. case ']':
  420. case ')':
  421. case '+':
  422. case '-':
  423. case '"':
  424. case "'":
  425. $this->action(1);
  426. break;
  427. default:
  428. if ($this->isAlphaNum($this->a)) {
  429. $this->action(1);
  430. }
  431. else {
  432. $this->action(3);
  433. }
  434. }
  435. break;
  436. default:
  437. $this->action(1);
  438. break;
  439. }
  440. }
  441. }
  442. return implode('', $this->output);
  443. }
  444. protected function next() {
  445. $c = $this->get();
  446. if ($c === '/') {
  447. switch($this->peek()) {
  448. case '/':
  449. for (;;) {
  450. $c = $this->get();
  451. if (ord($c) <= self::ORD_LF) {
  452. return $c;
  453. }
  454. }
  455. case '*':
  456. $this->get();
  457. for (;;) {
  458. switch($this->get()) {
  459. case '*':
  460. if ($this->peek() === '/') {
  461. $this->get();
  462. return ' ';
  463. }
  464. break;
  465. case null:
  466. throw new JSMinException('Unterminated comment.');
  467. }
  468. }
  469. default:
  470. return $c;
  471. }
  472. }
  473. return $c;
  474. }
  475. protected function peek() {
  476. $this->lookAhead = $this->get();
  477. return $this->lookAhead;
  478. }
  479. }
  480. // -- Exceptions ---------------------------------------------------------------
  481. class JSMinException extends Exception {}
  482. /************** OUTPUT *****************/
  483. if(count(get_script_requests()) > 0)
  484. {
  485. if(is_debug_mode())
  486. {
  487. if(($code = get_javascript_code()) !== null)
  488. {
  489. print_headers();
  490. echo supports_gzip_encoding() ? gzip_content($code) : $code;
  491. }
  492. }
  493. else
  494. {
  495. if(($filename = compressed_js_filename()) !== null)
  496. {
  497. if(!is_file($filename))
  498. save_javascript(get_javascript_code(true), $filename);
  499. print_headers();
  500. echo get_saved_javascript($filename);
  501. }
  502. }
  503. }
  504. ?>