PEEL Shopping
Open source ecommerce : PEEL Shopping
JShrink.php
Go to the documentation of this file.
1 <?php
2 /*
3  * This file is part of the JShrink package.
4  *
5  * (c) Robert Hafner <tedivm@tedivm.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10 
30 class Minifier
31 {
37  protected $input;
38 
45  protected $index = 0;
46 
52  protected $a = '';
53 
59  protected $b = '';
60 
66  protected $c;
67 
73  protected $options;
74 
82  protected static $defaultOptions = array('flaggedComments' => true);
83 
90  protected $locks = array();
91 
101  public static function minify($js, $options = array())
102  {
103  try {
104  ob_start();
105 
106  $jshrink = new Minifier();
107  $js = $jshrink->lock($js);
108  $jshrink->minifyDirectToOutput($js, $options);
109 
110  // Sometimes there's a leading new line, so we trim that out here.
111  $js = ltrim(ob_get_clean());
112  $js = $jshrink->unlock($js);
113  unset($jshrink);
114 
115  return $js;
116 
117  } catch (\Exception $e) {
118 
119  if (isset($jshrink)) {
120  // Since the breakdownScript function probably wasn't finished
121  // we clean it out before discarding it.
122  $jshrink->clean();
123  unset($jshrink);
124  }
125 
126  // without this call things get weird, with partially outputted js.
127  ob_end_clean();
128  throw $e;
129  }
130  }
131 
139  protected function minifyDirectToOutput($js, $options)
140  {
141  $this->initialize($js, $options);
142  $this->loop();
143  $this->clean();
144  }
145 
152  protected function initialize($js, $options)
153  {
154  $this->options = array_merge(static::$defaultOptions, $options);
155  $js = str_replace("\r\n", "\n", $js);
156  $this->input = str_replace("\r", "\n", $js);
157 
158  // We add a newline to the end of the script to make it easier to deal
159  // with comments at the bottom of the script- this prevents the unclosed
160  // comment error that can otherwise occur.
161  $this->input .= PHP_EOL;
162 
163  // Populate "a" with a new line, "b" with the first character, before
164  // entering the loop
165  $this->a = "\n";
166  $this->b = $this->getReal();
167  }
168 
173  protected function loop()
174  {
175  while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
176 
177  switch ($this->a) {
178  // new lines
179  case "\n":
180  // if the next line is something that can't stand alone preserve the newline
181  if (strpos('(-+{[@', $this->b) !== false) {
182  echo $this->a;
183  $this->saveString();
184  break;
185  }
186 
187  // if B is a space we skip the rest of the switch block and go down to the
188  // string/regex check below, resetting $this->b with getReal
189  if($this->b === ' ')
190  break;
191 
192  // otherwise we treat the newline like a space
193 
194  case ' ':
195  if(static::isAlphaNumeric($this->b))
196  echo $this->a;
197 
198  $this->saveString();
199  break;
200 
201  default:
202  switch ($this->b) {
203  case "\n":
204  if (strpos('}])+-"\'', $this->a) !== false) {
205  echo $this->a;
206  $this->saveString();
207  break;
208  } else {
209  if (static::isAlphaNumeric($this->a)) {
210  echo $this->a;
211  $this->saveString();
212  }
213  }
214  break;
215 
216  case ' ':
217  if(!static::isAlphaNumeric($this->a))
218  break;
219 
220  default:
221  // check for some regex that breaks stuff
222  if ($this->a == '/' && ($this->b == '\'' || $this->b == '"')) {
223  $this->saveRegex();
224  continue;
225  }
226 
227  echo $this->a;
228  $this->saveString();
229  break;
230  }
231  }
232 
233  // do reg check of doom
234  $this->b = $this->getReal();
235 
236  if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false))
237  $this->saveRegex();
238  }
239  }
240 
246  protected function clean()
247  {
248  unset($this->input);
249  $this->index = 0;
250  $this->a = $this->b = '';
251  unset($this->c);
252  unset($this->options);
253  }
254 
260  protected function getChar()
261  {
262  // Check to see if we had anything in the look ahead buffer and use that.
263  if (isset($this->c)) {
264  $char = $this->c;
265  unset($this->c);
266 
267  // Otherwise we start pulling from the input.
268  } else {
269  $char = substr($this->input, $this->index, 1);
270 
271  // If the next character doesn't exist return false.
272  if (isset($char) && $char === false) {
273  return false;
274  }
275 
276  // Otherwise increment the pointer and use this char.
277  $this->index++;
278  }
279 
280  // Normalize all whitespace except for the newline character into a
281  // standard space.
282  if($char !== "\n" && ord($char) < 32)
283 
284  return ' ';
285 
286  return $char;
287  }
288 
299  protected function getReal()
300  {
301  $startIndex = $this->index;
302  $char = $this->getChar();
303 
304  // Check to see if we're potentially in a comment
305  if ($char !== '/') {
306  return $char;
307  }
308 
309  $this->c = $this->getChar();
310 
311  if ($this->c == '/') {
312  return $this->processOneLineComments($startIndex);
313 
314  } elseif ($this->c == '*') {
315  return $this->processMultiLineComments($startIndex);
316  }
317 
318  return $char;
319  }
320 
328  protected function processOneLineComments($startIndex)
329  {
330  $thirdCommentString = substr($this->input, $this->index, 1);
331 
332  // kill rest of line
333  $this->getNext("\n");
334 
335  if ($thirdCommentString == '@') {
336  $endPoint = ($this->index) - $startIndex;
337  unset($this->c);
338  $char = "\n" . substr($this->input, $startIndex, $endPoint);
339  } else {
340  // first one is contents of $this->c
341  $this->getChar();
342  $char = $this->getChar();
343  }
344 
345  return $char;
346  }
347 
356  protected function processMultiLineComments($startIndex)
357  {
358  $this->getChar(); // current C
359  $thirdCommentString = $this->getChar();
360 
361  // kill everything up to the next */ if it's there
362  if ($this->getNext('*/')) {
363 
364  $this->getChar(); // get *
365  $this->getChar(); // get /
366  $char = $this->getChar(); // get next real character
367 
368  // Now we reinsert conditional comments and YUI-style licensing comments
369  if (($this->options['flaggedComments'] && $thirdCommentString == '!')
370  || ($thirdCommentString == '@') ) {
371 
372  // If conditional comments or flagged comments are not the first thing in the script
373  // we need to echo a and fill it with a space before moving on.
374  if ($startIndex > 0) {
375  echo $this->a;
376  $this->a = " ";
377 
378  // If the comment started on a new line we let it stay on the new line
379  if ($this->input[($startIndex - 1)] == "\n") {
380  echo "\n";
381  }
382  }
383 
384  $endPoint = ($this->index - 1) - $startIndex;
385  echo substr($this->input, $startIndex, $endPoint);
386 
387  return $char;
388  }
389 
390  } else {
391  $char = false;
392  }
393 
394  if($char === false)
395  throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2));
396 
397  // if we're here c is part of the comment and therefore tossed
398  if(isset($this->c))
399  unset($this->c);
400 
401  return $char;
402  }
403 
412  protected function getNext($string)
413  {
414  // Find the next occurrence of "string" after the current position.
415  $pos = strpos($this->input, $string, $this->index);
416 
417  // If it's not there return false.
418  if($pos === false)
419 
420  return false;
421 
422  // Adjust position of index to jump ahead to the asked for string
423  $this->index = $pos;
424 
425  // Return the first character of that string.
426  return substr($this->input, $this->index, 1);
427  }
428 
435  protected function saveString()
436  {
437  $startpos = $this->index;
438 
439  // saveString is always called after a gets cleared, so we push b into
440  // that spot.
441  $this->a = $this->b;
442 
443  // If this isn't a string we don't need to do anything.
444  if ($this->a != "'" && $this->a != '"') {
445  return;
446  }
447 
448  // String type is the quote used, " or '
449  $stringType = $this->a;
450 
451  // Echo out that starting quote
452  echo $this->a;
453 
454  // Loop until the string is done
455  while (1) {
456 
457  // Grab the very next character and load it into a
458  $this->a = $this->getChar();
459 
460  switch ($this->a) {
461 
462  // If the string opener (single or double quote) is used
463  // output it and break out of the while loop-
464  // The string is finished!
465  case $stringType:
466  break 2;
467 
468  // New lines in strings without line delimiters are bad- actual
469  // new lines will be represented by the string \n and not the actual
470  // character, so those will be treated just fine using the switch
471  // block below.
472  case "\n":
473  throw new \RuntimeException('Unclosed string at position: ' . $startpos );
474  break;
475 
476  // Escaped characters get picked up here. If it's an escaped new line it's not really needed
477  case '\\':
478 
479  // a is a slash. We want to keep it, and the next character,
480  // unless it's a new line. New lines as actual strings will be
481  // preserved, but escaped new lines should be reduced.
482  $this->b = $this->getChar();
483 
484  // If b is a new line we discard a and b and restart the loop.
485  if ($this->b == "\n") {
486  break;
487  }
488 
489  // echo out the escaped character and restart the loop.
490  echo $this->a . $this->b;
491  break;
492 
493 
494  // Since we're not dealing with any special cases we simply
495  // output the character and continue our loop.
496  default:
497  echo $this->a;
498  }
499  }
500  }
501 
508  protected function saveRegex()
509  {
510  echo $this->a . $this->b;
511 
512  while (($this->a = $this->getChar()) !== false) {
513  if($this->a == '/')
514  break;
515 
516  if ($this->a == '\\') {
517  echo $this->a;
518  $this->a = $this->getChar();
519  }
520 
521  if($this->a == "\n")
522  throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index);
523 
524  echo $this->a;
525  }
526  $this->b = $this->getReal();
527  }
528 
535  protected static function isAlphaNumeric($char)
536  {
537  return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/';
538  }
539 
546  protected function lock($js)
547  {
548  /* lock things like <code>"asd" + ++x;</code> */
549  $lock = '"LOCK---' . crc32(time()) . '"';
550 
551  $matches = array();
552  preg_match('/([+-])(\s+)([+-])/', $js, $matches);
553  if (empty($matches)) {
554  return $js;
555  }
556 
557  $this->locks[$lock] = $matches[2];
558 
559  $js = preg_replace('/([+-])\s+([+-])/', "$1{$lock}$2", $js);
560  /* -- */
561 
562  return $js;
563  }
564 
571  protected function unlock($js)
572  {
573  if (!count($this->locks)) {
574  return $js;
575  }
576 
577  foreach ($this->locks as $lock => $replacement) {
578  $js = str_replace($lock, $replacement, $js);
579  }
580 
581  return $js;
582  }
583 
584 }
lock($js)
Replace patterns in the given string and store the replacement.
Definition: JShrink.php:546
getChar()
Returns the next string for processing based off of the current index.
Definition: JShrink.php:260
saveString()
When a javascript string is detected this function crawls for the end of it and saves the whole strin...
Definition: JShrink.php:435
unlock($js)
Replace "locks" with the original characters.
Definition: JShrink.php:571
static isAlphaNumeric($char)
Checks to see if a character is alphanumeric.
Definition: JShrink.php:535
processOneLineComments($startIndex)
Removed one line comments, with the exception of some very specific types of conditional comments...
Definition: JShrink.php:328
if(strlen($date2)== '10') if($type== 'users-by-age'&&a_priv('admin_users', true)) elseif($type== 'forums-count'&&a_priv('admin_content', true)) elseif($type== 'forums-categories'&&a_priv('admin_content', true)) elseif($type== 'users-count'&&a_priv('admin_users', true)) elseif($type== 'product-categories'&&a_priv('admin_products', true)) elseif($type== 'users-by-sex'&&a_priv('admin_users', true)) elseif($type== 'users-by-country'&&a_priv('admin_users', true)) elseif($type== 'sales'&&a_priv('admin_sales', true))
Definition: chart-data.php:160
processMultiLineComments($startIndex)
Skips multiline comments where appropriate, and includes them where needed.
Definition: JShrink.php:356
loop()
The primary action occurs here.
Definition: JShrink.php:173
static minify($js, $options=array())
Takes a string containing javascript and removes unneeded characters in order to shrink the code with...
Definition: JShrink.php:101
getReal()
This function gets the next "real" character.
Definition: JShrink.php:299
saveRegex()
When a regular expression is detected this function crawls for the end of it and saves the whole rege...
Definition: JShrink.php:508
clean()
Resets attributes that do not need to be stored between requests so that the next request is ready to...
Definition: JShrink.php:246
static $defaultOptions
Definition: JShrink.php:82
initialize($js, $options)
Initializes internal variables, normalizes new lines,.
Definition: JShrink.php:152
minifyDirectToOutput($js, $options)
Processes a javascript string and outputs only the required characters, stripping out all unneeded ch...
Definition: JShrink.php:139
getNext($string)
Pushes the index ahead to the next instance of the supplied string.
Definition: JShrink.php:412

This documentation for Open ecommerce PEEL Shopping and PEEL.fr has been generated by Doxygen on Thu Oct 15 2015 14:30:03 - Peel ecommerce is a product of Agence web Advisto SAS. All rights reserved.