PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/php/phpJSO.php

http://genericgrid.codeplex.com
PHP | 833 lines | 573 code | 71 blank | 189 comment | 83 complexity | aeb4f88c55ec94e111ab9a56ea358c56 MD5 | raw file
  1. <?php
  2. /**
  3. * phpJSO - The Javascript Obfuscator written in PHP. Although
  4. * it effectively obfuscates Javascript code, it is meant to compress
  5. * code to save disk space rather than hide code from end-users.
  6. *
  7. * @started: Mon, May 23, 2005
  8. * @copyright: Copyright (c) 2004-2006 Cortex Creations, All Rights Reserved
  9. * @website: www.cortex-creations.com/phpjso
  10. * @license: Free, zlib/libpng license - see LICENSE
  11. * @version: 0.9
  12. * @subversion: $Id: phpJSO.php 70 2006-10-10 01:35:37Z josh $
  13. */
  14. // See if we're getting called via the command line
  15. if (isset($_SERVER['SHELL']))
  16. {
  17. // If no arguments are provided, display help screen
  18. if (count($_SERVER['argv']) < 3)
  19. {
  20. print("php phpJSO.php <in-file> <out-file>\n\n");
  21. print("Other options:\n");
  22. print("\t-encoding-type={value} Possible values: 1 or 0. The default encoding is 1. 0 turns encoding off.\n");
  23. print("\t-fast-decompress={value} Possible values: 1 or 0. Defaults to 0. 1 is on, 0 is off.\n");
  24. print("\t-collapse-blocks={value} Possible values: 1 or 0. Defaults to 0. 1 is on, 0 is off.\n");
  25. print("\t-collapse-math={value} Possible values: 1 or 0. Defaults to 0. 1 is on, 0 is off.\n");
  26. }
  27. // Check all options, see if they should be on or off
  28. $options = array
  29. (
  30. array('param' => '-encoding-type', 'value' => 1),
  31. array('param' => '-fast-decompress', 'value' => 0),
  32. array('param' => '-collapse-blocks', 'value' => 0),
  33. array('param' => '-collapse-math', 'value' => 0)
  34. );
  35. foreach ($_SERVER['argv'] as $argument)
  36. {
  37. foreach ($options as $k=>$option)
  38. {
  39. if (preg_match('#'.preg_quote($option['param']).'\=(.*?)$#', $argument, $match))
  40. {
  41. $options[$k]['value'] = $match[1];
  42. }
  43. }
  44. }
  45. // Check that in-file exists and out-file is writable
  46. $in_file = $_SERVER['argv'][1];
  47. $out_file = $_SERVER['argv'][2];
  48. if (!file_exists($in_file))
  49. {
  50. die("Make sure that in-file ($in_file) exists.");
  51. }
  52. if (!(touch($out_file) && file_exists($out_file) && is_writable($out_file)))
  53. {
  54. die("Make sure that out-file ($out_file) can be written to.");
  55. }
  56. // Open file
  57. $in_file_code = file_get_contents($in_file);
  58. // Compress it
  59. $messages = array();
  60. $compressed_code = phpJSO_compress($in_file_code, $messages,
  61. $options[0]['value'],
  62. $options[1]['value'],
  63. $options[2]['value'],
  64. $options[3]['value']);
  65. // Save to out file
  66. $out_file_handle = fopen($out_file, 'w');
  67. fwrite($out_file_handle, "/**\n * {your code messages/copyright here}\n *\n * This code was compressed by phpJSO - www.cortex-creations.com.\n**/\n\n".$compressed_code);
  68. fclose($out_file_handle);
  69. // Report stats
  70. $message = '';
  71. if (count($messages))
  72. {
  73. print("Successfully compressed code.\n");
  74. foreach ($messages as $k=>$m)
  75. {
  76. print("\t - $m\n");
  77. }
  78. print("\nThank you for using phpJSO! Check www.cortex-creations.com for news and updates.");
  79. }
  80. }
  81. // Only do HTML output if UNIT_TEST constant is not present
  82. else if (!defined('UNIT_TEST'))
  83. {
  84. // Uncomment to profile using APD
  85. //apd_set_pprof_trace('/Users/joshuagross/Desktop/APD Traces');
  86. $phpJSO_version = '0.9';
  87. // Compress javascript from a submitted form
  88. $compressed_js = 'Compressed code will be placed here';
  89. $code = 'Place your code here.';
  90. $messages = array();
  91. if (isset($_REQUEST['jscode']))
  92. {
  93. // Get JS code
  94. $code = $_REQUEST['jscode'];
  95. // Strip slashes from input?
  96. if (get_magic_quotes_gpc())
  97. {
  98. $code = stripslashes($code);
  99. }
  100. // Compress
  101. $compressed_js = phpJSO_compress($code, $messages,
  102. (isset($_REQUEST['encoding_type']) ? $_REQUEST['encoding_type'] :'1'),
  103. (isset($_REQUEST['fast_decompress']) ? true : false),
  104. (isset($_REQUEST['collapse_blocks']) ? true : false),
  105. (isset($_REQUEST['collapse_math']) ? true : false));
  106. }
  107. $compressed_js = htmlspecialchars($compressed_js);
  108. $code = htmlspecialchars($code);
  109. // Format compression messages, if any
  110. $message = '';
  111. if (count($messages))
  112. {
  113. $message = '<b>Successfully compressed code.</b><br /><ul>';
  114. foreach ($messages as $k=>$m)
  115. {
  116. $message .= nl2br("<li>$m</li>");
  117. }
  118. $message .= '</ul><br /><br />';
  119. }
  120. // Get HTML value of fast_decompress checkbox
  121. $encoding_type = (isset($_REQUEST['encoding_type']) ? $_REQUEST['encoding_type'] : '1');
  122. $fast_decompress_value = (isset($_REQUEST['fast_decompress']) ? 'checked="checked"' : '');
  123. $collapse_blocks_value = (isset($_REQUEST['collapse_blocks']) ? 'checked="checked"' : '');
  124. $collapse_math_value = (isset($_REQUEST['collapse_math']) ? 'checked="checked"' : '');
  125. // Get encoding type select options
  126. $encoding_options = '';
  127. $encoding_options .= '<option value="1" '.($encoding_type == '1' ? 'selected="selected"' : '').'>Numeric encoding: smallest possible</option>';
  128. $encoding_options .= '<option value="off" '.($encoding_type == 'off' ? 'selected="selected"' : '').'>No encoding</option>';
  129. // Show forms, including any compressed JS
  130. print("
  131. <html>
  132. <head>
  133. <title>phpJSO version $phpJSO_version</title>
  134. <style type=\"text/css\">
  135. body {
  136. margin: 0px;
  137. padding: 20px;
  138. background-color: #ffffff;
  139. color: #000000;
  140. font-family: Verdana, Arial, Sans;
  141. font-size: 11px;
  142. }
  143. textarea {
  144. background-color: #dddddd;
  145. width: 100%;
  146. height: 40%;
  147. padding: 5px;
  148. color: #000000;
  149. font-family: Verdana, Arial, Sans;
  150. font-size: 11px;
  151. }
  152. </style>
  153. <script>
  154. </script>
  155. </head>
  156. <body>
  157. <form action=\"".$_SERVER['PHP_SELF']."\" method=\"post\">
  158. $message
  159. <b>Compressed Code:</b><br />
  160. <textarea rows=\"20\" cols=\"30\">$compressed_js</textarea><br /><br />
  161. <b>Place Your Code Here:</b><br />
  162. <textarea rows=\"20\" cols=\"30\" name=\"jscode\">$code</textarea><br /><br />
  163. <b><label for=\"id_encoding_type\">Encoding type: </label></b><select name=\"encoding_type\" id=\"id_encoding_type\">$encoding_options</select><br />
  164. This is the encoding type that will be used by phpJSO; it is simply how you want
  165. phpJSO to compress and encode your code. For VERY large code (6,000 lines or more without comments) either turn
  166. encoding off, or encode about 3,000 lines at a time. This will ensure that browsers
  167. execute the code quickly.
  168. Please note that encoding does NOT change how your code works AT ALL.<br /><br />
  169. <b><input type=\"checkbox\" name=\"fast_decompress\" id=\"id_fast_decompress\" $fast_decompress_value /><label for=\"id_fast_decompress\">Fast decompression</label></b><br />
  170. This option is recommended for large javascript files (above 1,000 lines of code without comments); the larger
  171. a script is, the longer it will take to decompress. you won't notice much of a speed
  172. difference with smaller scripts. note, however, that this option also makes the
  173. compressed code <i>slightly</i> larger. See the \"encoding type\" option; this
  174. option doesn't matter if you turn encoding off.<br /><br />
  175. <b><input type=\"checkbox\" name=\"collapse_blocks\" id=\"id_collapse_blocks\" $collapse_blocks_value /><label for=\"id_collapse_blocks\">Collapse Code Blocks</label></b><br />
  176. This option helps compress code to miniscule sizes. It \"collapses\" code blocks
  177. whenever possible. For example, <i>if(1){alert(1);}</i> becomes <i>if(1)alert(1);</i>.
  178. In short code it may not make a huge difference, but it can in longer code. It also
  179. makes phpJSO slightly slower.<br /><br />
  180. <b><input type=\"checkbox\" name=\"collapse_math\" id=\"id_collapse_math\" $collapse_math_value /><label for=\"id_collapse_math\">Collapse Math Constants</label></b><br />
  181. If you select this option (recommended), phpJSO will change code sections like \"1+1\" to \"2\",
  182. or \"100-(20+30)\" to \"50\". This is a very fast operation and can help reduce code size as
  183. well as speed up running times.<br /><br />
  184. <input type=\"submit\" value=\"Compress Code\" />
  185. </form>
  186. </body>
  187. </html>
  188. ");
  189. }
  190. /**
  191. * Main phpJSO compression function. Pass Javascript code to it, and it will
  192. * return compressed code.
  193. */
  194. function phpJSO_compress ($code, &$messages, $encoding_type, $fast_decompress, $collapse_blocks, $collapse_math_constants)
  195. {
  196. // Start timer
  197. $start_time = phpJSO_microtime_float();
  198. // Array of tokens - alphanumeric
  199. $tokens = array();
  200. // Array of only numeric tokens, that are only inserted to prevent being
  201. // wrongly replaced with another token. For example: the integer 0 will
  202. // be replaced with whatever is at token index 0.
  203. $numeric_tokens = array();
  204. // Save original code length
  205. $original_code_length = strlen($code);
  206. // Remove strings and multi-line comments from code before performing operations
  207. $str_array = array();
  208. phpJSO_strip_strings_and_comments($code, $str_array, substr(md5(time()), 10, 2));
  209. // Strip junk from JS code
  210. phpJSO_strip_junk($code, true);
  211. if ($collapse_blocks)
  212. {
  213. $collapsed_blocks = 0;
  214. $code = phpJSO_collapse_blocks($code, $collapsed_blocks);
  215. $messages[] = 'Block collapse mode on: ' . $collapsed_blocks . ' blocks were collapsed.';
  216. }
  217. phpJSO_strip_junk($code);
  218. // Compress math constants in code?
  219. if ($collapse_math_constants)
  220. {
  221. $collapsed_math_constants = 0;
  222. $code = phpJSO_collapse_math($code, $collapsed_math_constants);
  223. $messages[] = 'Math constant collapse mode on: ' . $collapsed_math_constants . ' math constants were collapsed.';
  224. }
  225. // Add strings back into code
  226. phpJSO_restore_strings($code, $str_array);
  227. // Compressed code
  228. $compressed_code = $code;
  229. // Should we encode?
  230. if ($encoding_type == '1')
  231. {
  232. // BUG FIX: If a modulus is in the code, it will break obfuscation because the browser treats it as escaping of characters
  233. $compressed_code = str_replace('%', '% ', $compressed_code);
  234. // Find all tokens in code
  235. phpJSO_get_tokens($compressed_code, $numeric_tokens, $tokens);
  236. // Insert numeric tokens into token array
  237. phpJSO_merge_token_arrays($tokens, $numeric_tokens);
  238. // Replace all tokens with their token index
  239. phpJSO_replace_tokens($tokens, $compressed_code);
  240. // We have to sort the array because it can end up looking like this:
  241. // (
  242. // [0] => var
  243. // ...
  244. // [5] => opera
  245. // [7] =>
  246. // [6] => domLib_isSafari
  247. // [8] => domLib_isKonq
  248. // )
  249. ksort($tokens);
  250. reset($tokens);
  251. // Insert decompression code
  252. $compressed_code_double_slash = '"'.str_replace(array('\\', '"'), array('\\\\', '\\"'), $compressed_code).'"';
  253. $compressed_code_single_slash = "'".str_replace(array('\\', "'"), array('\\\\', "\\'"), $compressed_code)."'";
  254. $compressed_code = (strlen($compressed_code_double_slash) < strlen($compressed_code_single_slash) ? $compressed_code_double_slash : $compressed_code_single_slash);
  255. if ($fast_decompress)
  256. {
  257. $messages[] = 'Fast decompression mode.';
  258. $compressed_code = "eval(function(a,b,c,d,e){if(!''.replace(/^/,String)){d=function(e){return c[e]&&typeof(c[e])=='string'?c[e]:e};b=1}while(b--)if(c[b]||d)a=a.replace(new RegExp(e+(d?'\\\\w+':b)+e,'g'),d||c[b]);return a}($compressed_code,".count($tokens).",'".implode('|',$tokens)."'.split('|'),0,'\\\\b'))";
  259. }
  260. else
  261. {
  262. $compressed_code = "eval(function(a,b,c,d){while(b--)if(c[b])a=a.replace(new RegExp(d+b+d,'g'),c[b]);return a}($compressed_code,".count($tokens).",'".implode('|',$tokens)."'.split('|'),'\\\\b'))";
  263. }
  264. // Which is smaller: compressed code or uncompressed code?
  265. if (strlen($code) < strlen($compressed_code))
  266. {
  267. $messages[] = 'The uncompressed code (with only comments and whitespace removed)
  268. was smaller than the fully compressed code.';
  269. $compressed_code = $code;
  270. }
  271. }
  272. // End timer
  273. $execution_time = phpJSO_microtime_float() - $start_time;
  274. // Message about how long compression took
  275. $messages[] = "Compressed code in $execution_time seconds.";
  276. // Message reporting compression sizes
  277. $compressed_length = strlen($compressed_code);
  278. $ratio = $compressed_length / $original_code_length;
  279. $messages[] = "Original code length: $original_code_length.
  280. Compressed code length: $compressed_length.
  281. Compression ratio: $ratio.";
  282. return $compressed_code;
  283. }
  284. /**
  285. * Strip strings and comments from code
  286. */
  287. function phpJSO_strip_strings_and_comments (&$str, &$strings, $comment_delim)
  288. {
  289. // Find all occurances of comments and quotes. Then loop through them and parse.
  290. $quotes_and_comments = phpJSO_sort_occurances($str, array('/', '//', '/*', '*/', '"', "'"));
  291. // Loop through occurances of quotes and comments
  292. $in_string = $last_quote_pos = $in_comment = $in_regex = false;
  293. $removed = 0;
  294. $num_strings = count($strings);
  295. $invalid = array();
  296. foreach ($quotes_and_comments as $location => $token)
  297. {
  298. // Parse strings
  299. if ($in_string !== false)
  300. {
  301. if ($token == $in_string)
  302. {
  303. // First, we'll pull out the string and save it, and replace it with a number.
  304. $replacement = '`' . $num_strings . '`';
  305. $string_start_index = $last_quote_pos - $removed;
  306. $string_length = ($location - $last_quote_pos) + 1;
  307. $strings[$num_strings] = substr($str, $string_start_index, $string_length);
  308. ++$num_strings;
  309. // Remove the string completely
  310. $str = substr_replace($str, $replacement, $string_start_index, $string_length);
  311. // Clean up time...
  312. $removed += $string_length - strlen($replacement);
  313. $in_string = $last_quote_pos = false;
  314. }
  315. }
  316. // Parse multi-line comments
  317. else if ($in_comment !== false)
  318. {
  319. // If it's the end of a comment, replace it with a single space
  320. // We replace it with a space in case a comment is between two tokens: test/**/test
  321. if ($token == '*/')
  322. {
  323. $comment_start_index = $in_comment - $removed;
  324. $comment_length = ($location - $in_comment) + 2;
  325. $str = substr_replace($str, ' ', $comment_start_index, $comment_length);
  326. $removed += $comment_length - 1;
  327. $in_comment = false;
  328. }
  329. }
  330. // Parse regex
  331. else if ($in_regex !== false)
  332. {
  333. // Should be end of the regex, unless it's escaped
  334. // If it is the end... don't do anything except stop parsing
  335. // We just don't want strings inside of regex to be removed,
  336. // like: /["']*/ -- VERY bad when mistaken as a string
  337. if ($token == '/')
  338. {
  339. $string_start_index = $in_regex - $removed;
  340. $string_length = ($location - $in_regex) + 1;
  341. $in_regex = false;
  342. }
  343. }
  344. else
  345. {
  346. // Make sure string hasn't been extracted by another operation...
  347. if (substr($str, $location - $removed, strlen($token)) != $token)
  348. {
  349. continue;
  350. }
  351. // This string shouldn't have been escaped...
  352. if ($location && $str[$location - $removed - 1] == '\\')
  353. {
  354. continue;
  355. }
  356. // See what this token is ...
  357. // Start of multi-line comment?
  358. if ($token == '/*')
  359. {
  360. $in_comment = $location;
  361. }
  362. // Start of a string?
  363. else if ($token == '"' || $token == "'")
  364. {
  365. $in_string = $token;
  366. $last_quote_pos = $location;
  367. }
  368. // A single-line comment?
  369. else if ($token == '//')
  370. {
  371. $comment_start_position = $location - $removed;
  372. $newline_pos = strpos($str, "\n", $comment_start_position);
  373. $comment_length = ($newline_pos !== false ? $newline_pos - $comment_start_position : $comment_start_position);
  374. $str = substr_replace($str, '', $comment_start_position, $comment_length);
  375. $removed += $comment_length;
  376. }
  377. // Start of a regex expression?
  378. // Note that the second part of this conditional fixes a bug: if there
  379. // is a regex sequence followed by a comment of the EXACT SAME length,
  380. // it will try to parse the regex sequence a second time...
  381. else if ($token == '/' && (!isset($quotes_and_comments[$location - 1]) || ($quotes_and_comments[$location - 1] != '//' && $quotes_and_comments[$location - 1] != '*/')))
  382. {
  383. // Only start a regex sequence if there was NOT
  384. // an alphanumeric sequence before.
  385. // var regex = /pattern/
  386. // string.match(/pattern/)
  387. if (preg_match('#[(=]#', $str[$location - $removed - 1]))
  388. {
  389. $in_regex = $location;
  390. }
  391. }
  392. }
  393. }
  394. }
  395. /**
  396. * Strips junk from code
  397. */
  398. function phpJSO_strip_junk (&$str, $whitespace_only = false)
  399. {
  400. // Remove unneeded spaces and semicolons
  401. $find = array
  402. (
  403. '/([^a-zA-Z0-9_$]|^)\s+([^a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  404. '/([^a-zA-Z0-9_$]|^)\s+([a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  405. '/([a-zA-Z0-9_$]|^)\s+([^a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  406. '/([^a-zA-Z0-9_$]|^)\s+([^a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  407. '/([^a-zA-Z0-9_$]|^)\s+([a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  408. '/([a-zA-Z0-9_$]|^)\s+([^a-zA-Z0-9_$]|$)/s', // Unneeded spaces between tokens
  409. '/[\r\n]/s', // Unneeded newlines
  410. "/\t+/" // replace tabs with spaces
  411. );
  412. // Unneeded semicolons
  413. if (!$whitespace_only)
  414. {
  415. $find[] = '/;(\}|$)/si';
  416. }
  417. $replace = array
  418. (
  419. '$1$2',
  420. '$1$2',
  421. '$1$2',
  422. '$1$2',
  423. '$1$2',
  424. '$1$2',
  425. '',
  426. ' ',
  427. '$1',
  428. );
  429. $str = preg_replace($find, $replace, $str);
  430. }
  431. /**
  432. * Collapses code blocks.
  433. */
  434. function phpJSO_collapse_blocks ($code, &$collapse_count)
  435. {
  436. // The :parenthetical: is replaced dynamically in the loop below.
  437. // The key values mean this: the first and second values in the array are the indexes
  438. // of the parenthetical subscripts, and the third value is the replace value
  439. // for the regex.
  440. $regex = array
  441. (
  442. // When there is one command inside a block, remove brackets
  443. '#((if|for|while)\(:paren0:\))\{([^;{}]*;)\}#si' => array(3, 0, '$1$5', 5, 0),
  444. '#((if|for|while)\(:paren0:\))\{([^;{}]*)\}(?!;)#si' => array(3, 0, '$1$5;', 5, 0),
  445. '#((if|for|while)\(:paren0:\))\{([^;{}]*)\}(?=;)#si' => array(3, 0, '$1$5', 5, 0),
  446. // Collapse brackets with else and do statements
  447. '#(do|else)\{([^;{}]*)\}#si' => array(0, 0, '$1 $2;', 2, 0),
  448. '#(do|else)\{([^;{}]*;)\}#si' => array(0, 0, '$1 $2', 2, 0),
  449. // Remove brackets when a block is inside a block, EG if(1){if(2){}}
  450. '#((if|for|while)\(:paren0:\))\{((if|for|while|function [a-zA-Z_$][a-zA-Z0-9_$]*)\(:paren1:\))\{([^{}]*)\}\}(?!else)#si' => array(3, 7, '$1$5{$9}', 0, 0),
  451. '#((if|for|while)\(:paren0:\))\{((if|for|while|function [a-zA-Z_$][a-zA-Z0-9_$]*)\(:paren1:\))([^{};]*);?\}(?!else)#si' => array(3, 7, '$1$5$9;', 0, 0),
  452. '#((if|for|while)\(:paren0:\))\{([^;{]*)\{([^{}]*)\};?\}(?!else)#siU' => array(3, 0, '$1$5{$6};$7', 0, 0),
  453. // Remove brackets when a block is inside a block with no parentheticals, EG else{if(2){}}
  454. '#(else|do)\{((if|for|while|function [a-zA-Z_$][a-zA-Z0-9_$]*)\(:paren0:\))\{([^{}]*)\}\}#si' => array(4, 0, '$1 $2{$6}', 0, 0),
  455. '#(else|do)\{((if|for|while|function [a-zA-Z_$][a-zA-Z0-9_$]*)\(:paren0:\))([^{};]*);?\}#si' => array(4, 0, '$1 $2$6;', 0, 0),
  456. '#(else|do)\{([^;{}]*)\{([^{}]*)\};?\}#si' => array(0, 0, '$1 $2{$3};', 0, 0)
  457. );
  458. // Collapse all blocks when possible
  459. while (1)
  460. {
  461. $original_code = $code;
  462. // Loop through all patterns
  463. foreach ($regex as $find => $regex_data)
  464. {
  465. // Match all occurences of pattern
  466. $matches = array();
  467. $find_all = str_replace(':paren0:', '([^{}()]*(\([^{}]*)?)', $find);
  468. $find_all = str_replace(':paren1:', '([^{}()]*(\([^{}]*)?)', $find_all);
  469. preg_match_all($find_all, $code, $matches);
  470. // Loop through all matches, and if the number of opening and closing
  471. // parentheses is even, collapse the block
  472. for ($i = 0; isset($matches[0][$i]); ++$i)
  473. {
  474. // Don't find nested loops in some patterns
  475. if ($regex_data[3] && preg_match('#^if#si', $matches[$regex_data[3]][$i]))
  476. {
  477. continue;
  478. }
  479. // If loops are immediately followed by "else", don't continue
  480. if ($regex_data[4] && strtolower($matches[$regex_data[4]][$i]) == 'else')
  481. {
  482. continue;
  483. }
  484. $complete_match = true;
  485. $find_complete = $find;
  486. for ($j = 0; $j != 2; ++$j)
  487. {
  488. if ($regex_data[$j])
  489. {
  490. $parenthetical = &$matches[$regex_data[$j]][$i];
  491. if (!($parenthetical = phpJSO_is_valid_parenthetical($parenthetical)))
  492. {
  493. $complete_match = false;
  494. }
  495. $find_complete = str_replace(':paren'.$j.':', '((' . preg_quote($parenthetical) . '))', $find_complete);
  496. }
  497. }
  498. if ($complete_match)
  499. {
  500. $code = preg_replace($find_complete, $regex_data[2], $code);
  501. ++$collapse_count;
  502. }
  503. }
  504. }
  505. break;
  506. if ($original_code === $code)
  507. {
  508. break;
  509. }
  510. }
  511. return $code;
  512. }
  513. /**
  514. * Collapse math constants in code.
  515. */
  516. function phpJSO_collapse_math ($code, &$collapsed)
  517. {
  518. preg_match_all('#(^|[^a-zA-Z0-9_\$])(([()]|([\+\-\/\*\%])?(\-)?(0x[0-9a-fA-F]+|[0-9]+(\.[0-9]+)?))+)([^a-zA-Z0-9_\$]|$)#s', $code, $matches);
  519. // Loop through all matches
  520. for ($i = 0; isset($matches[0][$i]); ++$i)
  521. {
  522. $match = $matches[2][$i];
  523. // Make sure it is a valid math block
  524. if (!($match = phpJSO_is_valid_parenthetical($match)))
  525. {
  526. continue;
  527. }
  528. // Must end and begin with parentheses or numbers
  529. if ($match{0} != '(' && !is_numeric($match{0}))
  530. {
  531. continue;
  532. }
  533. $last_index = strlen($match) - 1;
  534. if ($match{$last_index} != ')' && !is_numeric($match{strlen($match) - 1}) && !ctype_alnum($match{$last_index}))
  535. {
  536. continue;
  537. }
  538. // Must be more than just symbols or just numbers
  539. //if (!preg_match('#[0-9]#', $match) || preg_match('#^[0-9]+$#', $match))
  540. //{
  541. // continue;
  542. //}
  543. if (preg_match('#\(\)#', $match))
  544. {
  545. continue;
  546. }
  547. // Convert hex to dec if the dec is smaller
  548. preg_match_all('#0x[0-9a-fA-F]+#', $code, $hex_matches);
  549. foreach ($hex_matches[0] as $hex_match)
  550. {
  551. $dec = hexdec($hex_match);
  552. if (strlen($dec) <= strlen($hex_match))
  553. {
  554. $code = str_replace($hex_match, $dec, $code);
  555. $match = str_replace($hex_match, $dec, $match);
  556. }
  557. }
  558. // Parse it, replace it
  559. $code = @preg_replace('#'.preg_quote($match).'#e', $match, $code);
  560. ++$collapsed;
  561. }
  562. return $code;
  563. }
  564. /**
  565. * Get all the tokens in code and put them in two arrays - one array
  566. * for just numeric tokens, and another array for all the rest.
  567. */
  568. function phpJSO_get_tokens ($code, &$numeric_tokens, &$tokens)
  569. {
  570. preg_match_all('#([a-zA-Z0-9\_\$]+)#s', $code, $match);
  571. $matched_tokens = array_values(array_unique($match[0]));
  572. phpJSO_count_duplicates($duplicates, $match[0]);
  573. foreach ($matched_tokens as $token)
  574. {
  575. // If token is an integer, we do replacements differently
  576. if (preg_match('#^([1-9][0-9]*|0)$#', $token))
  577. {
  578. $numeric_tokens[$token] = 1;
  579. }
  580. // We can place token in the array normally (but it's only worth doing
  581. // a replacement if the token isn't just one character).
  582. // It's also only worth doing a replacement if the token appears more than once in code.
  583. else if (isset($token{1}) && $duplicates[$token] > 1)
  584. {
  585. $tokens[] = $token;
  586. }
  587. }
  588. }
  589. /**
  590. * Merges the two token arrays: numeric tokens and regular tokens.
  591. * Specifically this function will take all the numeric tokens and
  592. * POSSIBLY put them in the token array if that's necessary.
  593. */
  594. function phpJSO_merge_token_arrays (&$tokens, &$numeric_tokens)
  595. {
  596. // Sort numeric token array
  597. ksort($numeric_tokens);
  598. // Loop through all numeric tokens
  599. $num_tokens = count($tokens);
  600. foreach ($numeric_tokens as $int=>$void)
  601. {
  602. if ($num_tokens < $int)
  603. {
  604. // We may not need to consider ANY more numeric tokens, if this
  605. // one is lower than the number of tokens, since the numeric tokens
  606. // are sorted already. This can potentially save a lot of time.
  607. if (strlen(strval($num_tokens)) >= strlen(strval($int)))
  608. {
  609. break;
  610. }
  611. else
  612. {
  613. $tokens[] = $int;
  614. continue;
  615. }
  616. }
  617. phpJSO_insert_token($tokens, '', $int);
  618. ++$num_tokens;
  619. }
  620. }
  621. /**
  622. * Inserts a token into the token array. Shifts all the other tokens
  623. * and puts it somewhere in the middle, based on token_index.
  624. */
  625. function phpJSO_insert_token (&$token_array, $token, $token_index)
  626. {
  627. // Loop through array and shift all indexes up one spot until we reach the
  628. // index we are inserting at
  629. $jump = 1;
  630. $token_index_count = $token_index - 1;
  631. for ($i = count($token_array) - 1; $i > $token_index_count; --$i)
  632. {
  633. if ($token_array[$i] == '')
  634. {
  635. ++$jump;
  636. continue;
  637. }
  638. $token_array[$i+$jump] = $token_array[$i];
  639. $jump = 1;
  640. }
  641. $token_array[$token_index] = $token;
  642. }
  643. /**
  644. * Place stripped strings back into code
  645. */
  646. function phpJSO_restore_strings (&$str, &$strings)
  647. {
  648. //do
  649. //{
  650. $str = preg_replace('#`([0-9]+)`#e', 'isset($strings[\'$1\']) ? $strings[\'$1\'] : \'`$1`\'', $str);
  651. //}
  652. //while (preg_match('#`([0-9]+)`#', $str));
  653. }
  654. /**
  655. * Count duplicate values in an array
  656. */
  657. function phpJSO_count_duplicates (&$dupes, $ary)
  658. {
  659. foreach ($ary as $v)
  660. {
  661. //$dupes[$v] = (isset($dupes[$v]) ? $dupes[$v] : 0) + 1;
  662. if (isset($dupes[$v]))
  663. {
  664. ++$dupes[$v];
  665. }
  666. else
  667. {
  668. $dupes[$v] = 1;
  669. }
  670. }
  671. }
  672. /**
  673. * Replaces tokens in code with the corresponding token index.
  674. */
  675. function phpJSO_replace_tokens (&$tokens, &$code)
  676. {
  677. $tokens_flipped = array_flip($tokens);
  678. unset($tokens_flipped['']);
  679. $find = '#\b('.implode('|', array_flip($tokens_flipped)).')\b#e';
  680. $code = preg_replace($find, '(isset($tokens_flipped[\'$1\']) ? $tokens_flipped[\'$1\'] : \'$1\')', $code);
  681. }
  682. /**
  683. * Check whether a parenthetical is valid or not.
  684. */
  685. function phpJSO_is_valid_parenthetical ($parenthetical)
  686. {
  687. $open_parentheses = 0;
  688. // Get all parentheses in the string
  689. $parentheses = phpJSO_sort_occurances($parenthetical, array('(', ')'));
  690. // Loop through parentheses
  691. foreach ($parentheses as $index => $parenthesis)
  692. {
  693. if ($parenthesis == ')')
  694. {
  695. if (!$open_parentheses)
  696. {
  697. return ($index ? substr($parenthetical, 0, $index) : false);
  698. }
  699. --$open_parentheses;
  700. }
  701. else
  702. {
  703. ++$open_parentheses;
  704. }
  705. }
  706. if ($open_parentheses != 0)
  707. {
  708. return false;
  709. }
  710. return $parenthetical;
  711. }
  712. /**
  713. * Finds all occurances of different strings in the first passed string and sorts
  714. * them by location. Returns array of locations. The key of each array element is the string
  715. * index (location) where the string was found; the value is the actual string, as seen below.
  716. *
  717. * [18] => "
  718. * [34] => "
  719. * [56] => /*
  720. * [100] => '
  721. */
  722. function phpJSO_sort_occurances (&$haystack, $needles)
  723. {
  724. $locations = array();
  725. foreach ($needles as $needle)
  726. {
  727. $pos = -1;
  728. //$needle_length = strlen($needle);
  729. while (($pos = @strpos($haystack, $needle, $pos+1)) !== false)
  730. {
  731. // Don't save location if string length is 1, and the needle is escaped
  732. if ($pos && $haystack[$pos - 1] == '\\' && $needle != '*/')
  733. {
  734. continue;
  735. }
  736. // Save location of needle
  737. $locations[$pos] = $needle;
  738. }
  739. }
  740. ksort($locations);
  741. return $locations;
  742. }
  743. /**
  744. * For timing compression
  745. */
  746. function phpJSO_microtime_float()
  747. {
  748. list($usec, $sec) = explode(" ", microtime());
  749. return ((float)$usec + (float)$sec);
  750. }
  751. ?>