PackPHP.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <?php
  2. /*
  3. * Minify PHP source
  4. * Based on algorithm by gelamu(ät)gmail(dt)com
  5. * http://php.net/manual/fr/function.php-strip-whitespace.php
  6. *
  7. * @package Packer
  8. * @author Vallo Reima
  9. * @copyright (C)2015
  10. */
  11. class PackPHP {
  12. private $obn = null; /* obfuscation object */
  13. private $fle = ''; /* source file name */
  14. private $min = true; /* pack flag */
  15. private static $IW = [ /* Whitespaces left and right from this signs can be ignored */
  16. T_CONCAT_EQUAL, // .=
  17. T_DOUBLE_ARROW, // =>
  18. T_BOOLEAN_AND, // &&
  19. T_BOOLEAN_OR, // ||
  20. T_IS_EQUAL, // ==
  21. T_IS_NOT_EQUAL, // != or <>
  22. T_IS_SMALLER_OR_EQUAL, // <=
  23. T_IS_GREATER_OR_EQUAL, // >=
  24. T_INC, // ++
  25. T_DEC, // --
  26. T_PLUS_EQUAL, // +=
  27. T_MINUS_EQUAL, // -=
  28. T_MUL_EQUAL, // *=
  29. T_DIV_EQUAL, // /=
  30. T_IS_IDENTICAL, // ===
  31. T_IS_NOT_IDENTICAL, // !==
  32. T_DOUBLE_COLON, // ::
  33. T_PAAMAYIM_NEKUDOTAYIM, // ::
  34. T_OBJECT_OPERATOR, // ->
  35. T_DOLLAR_OPEN_CURLY_BRACES, // ${
  36. T_AND_EQUAL, // &=
  37. T_MOD_EQUAL, // %=
  38. T_XOR_EQUAL, // ^=
  39. T_OR_EQUAL, // |=
  40. T_SL, // <<
  41. T_SR, // >>
  42. T_SL_EQUAL, // <<=
  43. T_SR_EQUAL, // >>=
  44. ];
  45. /**
  46. * @param string $source
  47. * @param array $options
  48. * @return mixed -- string - ok
  49. */
  50. public static function minify($source, $options = []) {
  51. $min = new self($options);
  52. return $min->process($source);
  53. }
  54. /**
  55. * save options
  56. * @param array $opts
  57. */
  58. public function __construct($opts) {
  59. if (isset($opts['min'])) {
  60. $this->min = $opts['min'] !== false; // compress yes/no
  61. }
  62. if (isset($opts['obn']) && !empty($opts['fle'])) {
  63. $this->obn = $opts['obn']; //obfuscator call
  64. $this->fle = $opts['fle']; //filename to save
  65. }
  66. }
  67. /**
  68. * compress code and register identifiers
  69. * @param string $php source
  70. * @return array
  71. */
  72. private function process($php) {
  73. $flg = $this->obn ? false : null; //php identifiers' collection
  74. if (mb_stripos(pathinfo($this->fle, PATHINFO_EXTENSION), 'php') !== 0) {//not php file
  75. $php = $this->Fix($php); //04.2017
  76. }
  77. $rlt = $this->Compress($php, $flg);
  78. if (!$this->min) {
  79. $rlt = $php; // return not-packed
  80. }
  81. if ($flg) { // source has php tags
  82. call_user_func($this->obn, -1, $this->fle); // save filename
  83. }
  84. return $rlt;
  85. }
  86. /**
  87. * add a space if missing after the tag
  88. * @param string $src
  89. * @return string
  90. */
  91. private function Fix($src) {
  92. $t = '<?php';
  93. $l = strlen($t);
  94. $s = '';
  95. $n = mb_strlen($src);
  96. $i = 0;
  97. while ($i < $n) {
  98. $k = mb_stripos($src, $t, $i);
  99. if ($k === false) {
  100. $s .= mb_substr($src, $i, $n - $i);
  101. $i = $n;
  102. } else {//tag found
  103. $k += $l;
  104. $s .= mb_substr($src, $i, $k - $i);
  105. $i = $k;
  106. if ($i < $n && mb_substr($src, $i, 1) !== ' ') {
  107. $s .= ' ';//insert missing space
  108. }
  109. }
  110. }
  111. return $s;
  112. }
  113. /**
  114. * compress PHP source code
  115. * @param string $src
  116. * @param mixed $flg -- false - fix open tag
  117. * null - no check
  118. * @return string
  119. */
  120. private function Compress($src, &$flg) {
  121. $tokens = token_get_all($src);
  122. $new = "";
  123. $c = sizeof($tokens);
  124. $iw = false; // ignore whitespace
  125. $ls = ""; // last sign
  126. $ot = null; // open tag
  127. for ($i = 0; $i < $c; $i++) {
  128. $token = $tokens[$i];
  129. if (is_array($token)) {
  130. list($tn, $ts) = $token; // tokens: number, string, line
  131. if ($tn == T_INLINE_HTML) { // token_name($tn)
  132. $new .= $ts;
  133. $iw = false;
  134. } else {
  135. if ($tn == T_OPEN_TAG) {
  136. if (strpos($ts, " ") || strpos($ts, "\n") || strpos($ts, "\t") || strpos($ts, "\r")) {
  137. $ts = rtrim($ts);
  138. }
  139. $ts .= " ";
  140. $new .= $ts;
  141. $ot = T_OPEN_TAG;
  142. $iw = true;
  143. } elseif ($tn == T_OPEN_TAG_WITH_ECHO) {
  144. $new .= $ts;
  145. $ot = T_OPEN_TAG_WITH_ECHO;
  146. $iw = true;
  147. } elseif ($tn == T_CLOSE_TAG) {
  148. if ($ot == T_OPEN_TAG_WITH_ECHO) {
  149. $new = rtrim($new, "; ");
  150. } else {
  151. $ts = " " . $ts;
  152. }
  153. $new .= $ts;
  154. $ot = null;
  155. $iw = false;
  156. } elseif (in_array($tn, self::$IW)) {
  157. $new .= $ts;
  158. $iw = true;
  159. } elseif ($tn == T_CONSTANT_ENCAPSED_STRING || $tn == T_ENCAPSED_AND_WHITESPACE) {
  160. /* if ($ts[0] == '"') {
  161. $ts = addcslashes($ts, "\n\t\r"); //VR
  162. } */
  163. $new .= $ts;
  164. $iw = true;
  165. } elseif ($tn == T_WHITESPACE) {
  166. $nt = @$tokens[$i + 1];
  167. if (!$iw && (!is_string($nt) || $nt == '$') && !in_array($nt[0], self::$IW)) {
  168. $new .= " ";
  169. }
  170. $iw = false;
  171. } elseif ($tn == T_END_HEREDOC) {
  172. $new .= "$ts;\n"; //VR add newline
  173. $iw = true;
  174. for ($j = $i + 1; $j < $c; $j++) {
  175. if (is_string($tokens[$j]) && $tokens[$j] == ";") {
  176. $i = $j;
  177. break;
  178. } else if ($tokens[$j][0] == T_CLOSE_TAG) {
  179. break;
  180. }
  181. }
  182. } elseif ($tn == T_COMMENT || $tn == T_DOC_COMMENT) {
  183. $iw = true;
  184. } else {
  185. if ($tn == T_START_HEREDOC && !mb_strpos($ts, "\n")) { //VR check newline
  186. $ts .= "\n";
  187. }
  188. $new .= $ts;
  189. $iw = false;
  190. if (!is_null($flg)) {//VR identifier registration
  191. call_user_func($this->obn, $i, $tokens); //save possible identifier
  192. $flg = !is_null($ot) || $flg; // mark php content
  193. }
  194. }
  195. }
  196. $ls = "";
  197. } else {
  198. if (($token == ";" || $token == ":") && $ls == $token) {
  199. $new .= $this->Double($tokens, $i); //VR
  200. } else {
  201. $new .= $token;
  202. $ls = $token;
  203. }
  204. $iw = true;
  205. }
  206. }
  207. return $new;
  208. }
  209. /**
  210. * check to omit doubled character
  211. * @param array $tks tokens
  212. * @param int $i current token index
  213. * @return string append to output
  214. */
  215. private function Double($tks, $i) {
  216. $r = '';
  217. if ($tks[$i] == ';') {
  218. $j = $i - 1;
  219. while ($j > 2 && $tks[$j] != '(') { // find condition beginning
  220. $j--;
  221. }
  222. if ($j > 2) {
  223. if (is_array($tks[$j - 1]) && $tks[$j - 1][0] == T_WHITESPACE) {
  224. $j--;
  225. }
  226. if (is_array($tks[$j - 1]) && $tks[$j - 1][0] == T_FOR) {
  227. $r = $tks[$i]; // for (;;)
  228. }
  229. }
  230. }
  231. return $r;
  232. }
  233. }