JavaScriptPacker::_bootStrap()   F
last analyzed

Complexity

Conditions 15
Paths 1792

Size

Total Lines 82
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 44
nc 1792
nop 2
dl 0
loc 82
rs 2.0234
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
class Ajde_Resource_Local_Compressor_Js_Packer extends JavaScriptPacker
4
{
5
    public function compress()
6
    {
7
        return $this->pack();
8
    }
9
}
10
11
/* 9 April 2008. version 1.1
12
 *
13
 * This is the php version of the Dean Edwards JavaScript's Packer,
14
 * Based on :
15
 *
16
 * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
17
 * a multi-pattern parser.
18
 * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
19
 * value that is a function
20
 *
21
 * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
22
 *
23
 * License: http://creativecommons.org/licenses/LGPL/2.1/
24
 *
25
 * Ported to PHP by Nicolas Martin.
26
 *
27
 * ----------------------------------------------------------------------
28
 * changelog:
29
 * 1.1 : correct a bug, '\0' packed then unpacked becomes '\'.
30
 * ----------------------------------------------------------------------
31
 *
32
 * examples of usage :
33
 * $myPacker = new JavaScriptPacker($script, 62, true, false);
34
 * $packed = $myPacker->pack();
35
 *
36
 * or
37
 *
38
 * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
39
 * $packed = $myPacker->pack();
40
 *
41
 * or (default values)
42
 *
43
 * $myPacker = new JavaScriptPacker($script);
44
 * $packed = $myPacker->pack();
45
 *
46
 *
47
 * params of the constructor :
48
 * $script:       the JavaScript to pack, string.
49
 * $encoding:     level of encoding, int or string :
50
 *                0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
51
 *                default: 62.
52
 * $fastDecode:   include the fast decoder in the packed result, boolean.
53
 *                default : true.
54
 * $specialChars: if you are flagged your private and local variables
55
 *                in the script, boolean.
56
 *                default: false.
57
 *
58
 * The pack() method return the compressed JavasScript, as a string.
59
 *
60
 * see http://dean.edwards.name/packer/usage/ for more information.
61
 *
62
 * Notes :
63
 * # need PHP 5 . Tested with PHP 5.1.2, 5.1.3, 5.1.4, 5.2.3
64
 *
65
 * # The packed result may be different than with the Dean Edwards
66
 *   version, but with the same length. The reason is that the PHP
67
 *   function usort to sort array don't necessarily preserve the
68
 *   original order of two equal member. The Javascript sort function
69
 *   in fact preserve this order (but that's not require by the
70
 *   ECMAScript standard). So the encoded keywords order can be
71
 *   different in the two results.
72
 *
73
 * # Be careful with the 'High ASCII' Level encoding if you use
74
 *   UTF-8 in your files...
75
 */
76
77
class JavaScriptPacker
78
{
79
    // constants
80
    const IGNORE = '$1';
81
82
    // validate parameters
83
    private $_script = '';
84
    private $_encoding = 62;
85
    private $_fastDecode = true;
86
    private $_specialChars = false;
87
88
    private $LITERAL_ENCODING = [
89
        'None'       => 0,
90
        'Numeric'    => 10,
91
        'Normal'     => 62,
92
        'High ASCII' => 95,
93
    ];
94
95
    public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
96
    {
97
        $this->_script = $_script."\n";
98
        if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) {
99
            $_encoding = $this->LITERAL_ENCODING[$_encoding];
100
        }
101
        $this->_encoding = min((int) $_encoding, 95);
102
        $this->_fastDecode = $_fastDecode;
103
        $this->_specialChars = $_specialChars;
104
    }
105
106
    public function pack()
107
    {
108
        $this->_addParser('_basicCompression');
109
        if ($this->_specialChars) {
110
            $this->_addParser('_encodeSpecialChars');
111
        }
112
        if ($this->_encoding) {
113
            $this->_addParser('_encodeKeywords');
114
        }
115
116
        // go!
117
        return $this->_pack($this->_script);
118
    }
119
120
    // apply all parsing routines
121
    private function _pack($script)
122
    {
123
        for ($i = 0; isset($this->_parsers[$i]); $i++) {
124
            $script = call_user_func([&$this, $this->_parsers[$i]], $script);
125
        }
126
127
        return $script;
128
    }
129
130
    // keep a list of parsing functions, they'll be executed all at once
131
    private $_parsers = [];
132
133
    private function _addParser($parser)
134
    {
135
        $this->_parsers[] = $parser;
136
    }
137
138
    // zero encoding - just removal of white space and comments
139
    private function _basicCompression($script)
140
    {
141
        $parser = new ParseMaster();
142
        // make safe
143
        $parser->escapeChar = '\\';
144
        // protect strings
145
        $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
146
        $parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
147
        // remove comments
148
        $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
149
        $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
150
        // protect regular expressions
151
        $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
152
        $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
153
        // remove: ;;; doSomething();
154
        if ($this->_specialChars) {
155
            $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
156
        }
157
        // remove redundant semi-colons
158
        $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
159
        $parser->add('/;+\\s*([};])/', '$2');
160
        // apply the above
161
        $script = $parser->exec($script);
162
163
        // remove white-space
164
        $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
165
        $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
166
        $parser->add('/\\s+/', '');
167
168
        // done
169
        return $parser->exec($script);
170
    }
171
172
    private function _encodeSpecialChars($script)
173
    {
174
        $parser = new ParseMaster();
175
        // replace: $name -> n, $$name -> na
176
        $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
177
            ['fn' => '_replace_name']
0 ignored issues
show
Documentation introduced by
array('fn' => '_replace_name') is of type array<string,string,{"fn":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
178
        );
179
        // replace: _name -> _0, double-underscore (__name) is ignored
180
        $regexp = '/\\b_[A-Za-z\\d]\\w*/';
181
        // build the word list
182
        $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
183
        // quick ref
184
        $encoded = $keywords['encoded'];
185
186
        $parser->add($regexp,
187
            [
0 ignored issues
show
Documentation introduced by
array('fn' => '_replace_...d', 'data' => $encoded) is of type array<string,string|arra...tring","data":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
188
                'fn'   => '_replace_encoded',
189
                'data' => $encoded,
190
            ]
191
        );
192
193
        return $parser->exec($script);
194
    }
195
196
    private function _encodeKeywords($script)
197
    {
198
        // escape high-ascii values already in the script (i.e. in strings)
199
        if ($this->_encoding > 62) {
200
            $script = $this->_escape95($script);
201
        }
202
        // create the parser
203
        $parser = new ParseMaster();
204
        $encode = $this->_getEncoder($this->_encoding);
205
        // for high-ascii, don't encode single character low-ascii
206
        $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
207
        // build the word list
208
        $keywords = $this->_analyze($script, $regexp, $encode);
209
        $encoded = $keywords['encoded'];
210
211
        // encode
212
        $parser->add($regexp,
213
            [
0 ignored issues
show
Documentation introduced by
array('fn' => '_replace_...d', 'data' => $encoded) is of type array<string,string|arra...tring","data":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
                'fn'   => '_replace_encoded',
215
                'data' => $encoded,
216
            ]
217
        );
218
        if (empty($script)) {
219
            return $script;
220
        } else {
221
            //$res = $parser->exec($script);
222
            //$res = $this->_bootStrap($res, $keywords);
223
            //return $res;
224
            return $this->_bootStrap($parser->exec($script), $keywords);
225
        }
226
    }
227
228
    private function _analyze($script, $regexp, $encode)
229
    {
230
        // analyse
231
        // retreive all words in the script
232
        $all = [];
233
        preg_match_all($regexp, $script, $all);
234
        $_sorted = []; // list of words sorted by frequency
235
        $_encoded = []; // dictionary of word->encoding
236
        $_protected = []; // instances of "protected" words
237
        $all = $all[0]; // simulate the javascript comportement of global match
238
        if (!empty($all)) {
239
            $unsorted = []; // same list, not sorted
240
            $protected = []; // "protected" words (dictionary of word->"word")
241
            $value = []; // dictionary of charCode->encoding (eg. 256->ff)
0 ignored issues
show
Unused Code introduced by
$value is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
242
            $this->_count = []; // word->count
243
            $i = count($all);
244
            $j = 0; //$word = null;
245
            // count the occurrences - used for sorting later
246
            do {
247
                --$i;
248
                $word = '$'.$all[$i];
249
                if (!isset($this->_count[$word])) {
250
                    $this->_count[$word] = 0;
251
                    $unsorted[$j] = $word;
252
                    // make a dictionary of all of the protected words in this script
253
                    //  these are words that might be mistaken for encoding
254
                    //if (is_string($encode) && method_exists($this, $encode))
255
                    $values[$j] = call_user_func([&$this, $encode], $j);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$values was never initialized. Although not strictly required by PHP, it is generally a good practice to add $values = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
256
                    $protected['$'.$values[$j]] = $j++;
0 ignored issues
show
Bug introduced by
The variable $values does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
257
                }
258
                // increment the word counter
259
                $this->_count[$word]++;
260
            } while ($i > 0);
261
            // prepare to sort the word list, first we must protect
262
            //  words that are also used as codes. we assign them a code
263
            //  equivalent to the word itself.
264
            // e.g. if "do" falls within our encoding range
265
            //      then we store keywords["do"] = "do";
266
            // this avoids problems when decoding
267
            $i = count($unsorted);
268
            do {
269
                $word = $unsorted[--$i];
270
                if (isset($protected[$word]) /*!= null*/) {
271
                    $_sorted[$protected[$word]] = substr($word, 1);
272
                    $_protected[$protected[$word]] = true;
273
                    $this->_count[$word] = 0;
274
                }
275
            } while ($i);
276
277
            // sort the words by frequency
278
            // Note: the javascript and php version of sort can be different :
279
            // in php manual, usort :
280
            // " If two members compare as equal,
281
            // their order in the sorted array is undefined."
282
            // so the final packed script is different of the Dean's javascript version
283
            // but equivalent.
284
            // the ECMAscript standard does not guarantee this behaviour,
285
            // and thus not all browsers (e.g. Mozilla versions dating back to at
286
            // least 2003) respect this.
287
            usort($unsorted, [&$this, '_sortWords']);
288
            $j = 0;
289
            // because there are "protected" words in the list
290
            //  we must add the sorted words around them
291
            do {
292
                if (!isset($_sorted[$i])) {
293
                    $_sorted[$i] = substr($unsorted[$j++], 1);
294
                }
295
                $_encoded[$_sorted[$i]] = $values[$i];
296
            } while (++$i < count($unsorted));
297
        }
298
299
        return [
300
            'sorted'    => $_sorted,
301
            'encoded'   => $_encoded,
302
            'protected' => $_protected,
303
        ];
304
    }
305
306
    private $_count = [];
307
308
    private function _sortWords($match1, $match2)
309
    {
310
        return $this->_count[$match2] - $this->_count[$match1];
311
    }
312
313
    // build the boot function used for loading and decoding
314
    private function _bootStrap($packed, $keywords)
315
    {
316
        $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
317
318
        // $packed: the packed script
319
        $packed = "'".$this->_escape($packed)."'";
320
321
        // $ascii: base for encoding
322
        $ascii = min(count($keywords['sorted']), $this->_encoding);
323
        if ($ascii == 0) {
324
            $ascii = 1;
325
        }
326
327
        // $count: number of words contained in the script
328
        $count = count($keywords['sorted']);
329
330
        // $keywords: list of words contained in the script
331
        foreach ($keywords['protected'] as $i => $value) {
332
            $keywords['sorted'][$i] = '';
333
        }
334
        // convert from a string to an array
335
        ksort($keywords['sorted']);
336
        $keywords = "'".implode('|', $keywords['sorted'])."'.split('|')";
337
338
        $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
339
        $encode = $this->_getJSFunction($encode);
340
        $encode = preg_replace('/_encoding/', '$ascii', $encode);
341
        $encode = preg_replace('/arguments\\.callee/', '$encode', $encode);
342
        $inline = '\\$count'.($ascii > 10 ? '.toString(\\$ascii)' : '');
343
344
        // $decode: code snippet to speed up decoding
345
        if ($this->_fastDecode) {
346
            // create the decoder
347
            $decode = $this->_getJSFunction('_decodeBody');
348
            if ($this->_encoding > 62) {
349
                $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
350
            } // perform the encoding inline for lower ascii values
351
            elseif ($ascii < 36) {
352
                $decode = preg_replace($ENCODE, $inline, $decode);
353
            }
354
            // special case: when $count==0 there are no keywords. I want to keep
355
            //  the basic shape of the unpacking funcion so i'll frig the code...
356
            if ($count == 0) {
357
                $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
358
            }
359
        }
360
361
        // boot function
362
        $unpack = $this->_getJSFunction('_unpack');
363
        if ($this->_fastDecode) {
364
            // insert the decoder
365
            $this->buffer = $decode;
0 ignored issues
show
Bug introduced by
The variable $decode does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
366
            $unpack = preg_replace_callback('/\\{/', [&$this, '_insertFastDecode'], $unpack, 1);
367
        }
368
        $unpack = preg_replace('/"/', "'", $unpack);
369
        if ($this->_encoding > 62) { // high-ascii
370
            // get rid of the word-boundaries for regexp matches
371
            $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
372
        }
373
        if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
374
            // insert the encode function
375
            $this->buffer = $encode;
376
            $unpack = preg_replace_callback('/\\{/', [&$this, '_insertFastEncode'], $unpack, 1);
377
        } else {
378
            // perform the encoding inline
379
            $unpack = preg_replace($ENCODE, $inline, $unpack);
380
        }
381
        // pack the boot function too
382
        $unpackPacker = new self($unpack, 0, false, true);
383
        $unpack = $unpackPacker->pack();
384
385
        // arguments
386
        $params = [$packed, $ascii, $count, $keywords];
387
        if ($this->_fastDecode) {
388
            $params[] = 0;
389
            $params[] = '{}';
390
        }
391
        $params = implode(',', $params);
392
393
        // the whole thing
394
        return 'eval('.$unpack.'('.$params."))\n";
395
    }
396
397
    private $buffer;
398
399
    private function _insertFastDecode($match)
0 ignored issues
show
Unused Code introduced by
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
400
    {
401
        return '{'.$this->buffer.';';
402
    }
403
404
    private function _insertFastEncode($match)
0 ignored issues
show
Unused Code introduced by
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
405
    {
406
        return '{$encode='.$this->buffer.';';
407
    }
408
409
    // mmm.. ..which one do i need ??
410
    private function _getEncoder($ascii)
411
    {
412
        return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
413
            '_encode95' : '_encode62' : '_encode36' : '_encode10';
414
    }
415
416
    // zero encoding
417
    // characters: 0123456789
418
    private function _encode10($charCode)
419
    {
420
        return $charCode;
421
    }
422
423
    // inherent base36 support
424
    // characters: 0123456789abcdefghijklmnopqrstuvwxyz
425
    private function _encode36($charCode)
426
    {
427
        return base_convert($charCode, 10, 36);
428
    }
429
430
    // hitch a ride on base36 and add the upper case alpha characters
431
    // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
432
    private function _encode62($charCode)
433
    {
434
        $res = '';
435
        if ($charCode >= $this->_encoding) {
436
            $res = $this->_encode62((int) ($charCode / $this->_encoding));
437
        }
438
        $charCode = $charCode % $this->_encoding;
439
440
        if ($charCode > 35) {
441
            return $res.chr($charCode + 29);
442
        } else {
443
            return $res.base_convert($charCode, 10, 36);
444
        }
445
    }
446
447
    // use high-ascii values
448
    // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
449
    private function _encode95($charCode)
450
    {
451
        $res = '';
452
        if ($charCode >= $this->_encoding) {
453
            $res = $this->_encode95($charCode / $this->_encoding);
454
        }
455
456
        return $res.chr(($charCode % $this->_encoding) + 161);
457
    }
458
459
    private function _safeRegExp($string)
460
    {
461
        return '/'.preg_replace('/\$/', '\\\$', $string).'/';
462
    }
463
464
    private function _encodePrivate($charCode)
465
    {
466
        return '_'.$charCode;
467
    }
468
469
    // protect characters used by the parser
470
    private function _escape($script)
471
    {
472
        return preg_replace('/([\\\\\'])/', '\\\$1', $script);
473
    }
474
475
    // protect high-ascii characters already in the script
476
    private function _escape95($script)
477
    {
478
        return preg_replace_callback(
479
            '/[\\xa1-\\xff]/',
480
            [&$this, '_escape95Bis'],
481
            $script
482
        );
483
    }
484
485
    private function _escape95Bis($match)
486
    {
487
        return '\x'.((string) dechex(ord($match)));
488
    }
489
490
    private function _getJSFunction($aName)
491
    {
492
        if (defined('self::JSFUNCTION'.$aName)) {
493
            return constant('self::JSFUNCTION'.$aName);
494
        } else {
495
            return '';
496
        }
497
    }
498
499
    // JavaScript Functions used.
500
    // Note : In Dean's version, these functions are converted
501
    // with 'String(aFunctionName);'.
502
    // This internal conversion complete the original code, ex :
503
    // 'while (aBool) anAction();' is converted to
504
    // 'while (aBool) { anAction(); }'.
505
    // The JavaScript functions below are corrected.
506
507
    // unpacking function - this is the boot strap function
508
    //  data extracted from this packing routine is passed to
509
    //  this function when decoded in the target
510
    // NOTE ! : without the ';' final.
511
    const JSFUNCTION_unpack =
512
513
        'function($packed, $ascii, $count, $keywords, $encode, $decode) {
514
    while ($count--) {
515
        if ($keywords[$count]) {
516
            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
517
        }
518
    }
519
    return $packed;
520
}';
521
    /*
522
    'function($packed, $ascii, $count, $keywords, $encode, $decode) {
523
        while ($count--)
524
            if ($keywords[$count])
525
                $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
526
        return $packed;
527
    }';
528
    */
529
530
    // code-snippet inserted into the unpacker to speed up decoding
531
    const JSFUNCTION_decodeBody =
532
        //_decode = function() {
533
        // does the browser support String.replace where the
534
        //  replacement value is a function?
535
536
        '    if (!\'\'.replace(/^/, String)) {
537
        // decode all the values we need
538
        while ($count--) {
539
            $decode[$encode($count)] = $keywords[$count] || $encode($count);
540
        }
541
        // global replacement function
542
        $keywords = [function ($encoded) {return $decode[$encoded]}];
543
        // generic match
544
        $encode = function () {return \'\\\\w+\'};
545
        // reset the loop counter -  we are now doing a global replace
546
        $count = 1;
547
    }
548
';
549
    //};
550
    /*
551
    '	if (!\'\'.replace(/^/, String)) {
552
            // decode all the values we need
553
            while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
554
            // global replacement function
555
            $keywords = [function ($encoded) {return $decode[$encoded]}];
556
            // generic match
557
            $encode = function () {return\'\\\\w+\'};
558
            // reset the loop counter -  we are now doing a global replace
559
            $count = 1;
560
        }';
561
    */
562
563
    // zero encoding
564
    // characters: 0123456789
565
    const JSFUNCTION_encode10 =
566
        'function($charCode) {
567
    return $charCode;
568
}'; //;';
569
570
    // inherent base36 support
571
    // characters: 0123456789abcdefghijklmnopqrstuvwxyz
572
    const JSFUNCTION_encode36 =
573
        'function($charCode) {
574
    return $charCode.toString(36);
575
}'; //;';
576
577
    // hitch a ride on base36 and add the upper case alpha characters
578
    // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
579
    const JSFUNCTION_encode62 =
580
        'function($charCode) {
581
    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
582
    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
583
}';
584
585
    // use high-ascii values
586
    // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
587
    const JSFUNCTION_encode95 =
588
        'function($charCode) {
589
    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
590
        String.fromCharCode($charCode % _encoding + 161);
591
}';
592
}
593
594
class ParseMaster
595
{
596
    public $ignoreCase = false;
597
    public $escapeChar = '';
598
599
    // constants
600
    const EXPRESSION = 0;
601
    const REPLACEMENT = 1;
602
    const LENGTH = 2;
603
604
    // used to determine nesting levels
605
    private $GROUPS = '/\\(/'; //g
606
    private $SUB_REPLACE = '/\\$\\d/';
607
    private $INDEXED = '/^\\$\\d+$/';
608
    private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
609
    private $ESCAPE = '/\\\./'; //g
610
    private $QUOTE = '/\'/';
611
    private $DELETED = '/\\x01[^\\x01]*\\x01/'; //g
612
613
    public function add($expression, $replacement = '')
614
    {
615
        // count the number of sub-expressions
616
        //  - add one because each pattern is itself a sub-expression
617
        $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string) $expression), $out);
618
619
        // treat only strings $replacement
620
        if (is_string($replacement)) {
621
            // does the pattern deal with sub-expressions?
622
            if (preg_match($this->SUB_REPLACE, $replacement)) {
623
                // a simple lookup? (e.g. "$2")
624
                if (preg_match($this->INDEXED, $replacement)) {
625
                    // store the index (used for fast retrieval of matched strings)
626
                    $replacement = (int) (substr($replacement, 1)) - 1;
627
                } else { // a complicated lookup (e.g. "Hello $2 $1")
628
                    // build a function to do the lookup
629
                    $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
630
                        ? '"' : "'";
631
                    $replacement = [
632
                        'fn'   => '_backReferences',
633
                        'data' => [
634
                            'replacement' => $replacement,
635
                            'length'      => $length,
636
                            'quote'       => $quote,
637
                        ],
638
                    ];
639
                }
640
            }
641
        }
642
        // pass the modified arguments
643
        if (!empty($expression)) {
644
            $this->_add($expression, $replacement, $length);
645
        } else {
646
            $this->_add('/^$/', $replacement, $length);
647
        }
648
    }
649
650
    public function exec($string)
651
    {
652
        // execute the global replacement
653
        $this->_escaped = [];
654
655
        // simulate the _patterns.toSTring of Dean
656
        $regexp = '/';
657
        foreach ($this->_patterns as $reg) {
658
            $regexp .= '('.substr($reg[self::EXPRESSION], 1, -1).')|';
659
        }
660
        $regexp = substr($regexp, 0, -1).'/';
661
        $regexp .= ($this->ignoreCase) ? 'i' : '';
662
663
        $string = $this->_escape($string, $this->escapeChar);
664
        $string = preg_replace_callback(
665
            $regexp,
666
            [
667
                &$this,
668
                '_replacement',
669
            ],
670
            $string
671
        );
672
        $string = $this->_unescape($string, $this->escapeChar);
673
674
        return preg_replace($this->DELETED, '', $string);
675
    }
676
677
    public function reset()
678
    {
679
        // clear the patterns collection so that this object may be re-used
680
        $this->_patterns = [];
681
    }
682
683
    // private
684
    private $_escaped = [];  // escaped characters
685
    private $_patterns = []; // patterns stored by index
686
687
    // create and add a new pattern to the patterns collection
688
    private function _add()
689
    {
690
        $arguments = func_get_args();
691
        $this->_patterns[] = $arguments;
692
    }
693
694
    // this is the global replace function (it's quite complicated)
695
    private function _replacement($arguments)
696
    {
697
        if (empty($arguments)) {
698
            return '';
699
        }
700
701
        $i = 1;
702
        $j = 0;
703
        // loop through the patterns
704
        while (isset($this->_patterns[$j])) {
705
            $pattern = $this->_patterns[$j++];
706
            // do we have a result?
707
            if (isset($arguments[$i]) && ($arguments[$i] != '')) {
708
                $replacement = $pattern[self::REPLACEMENT];
709
710
                if (is_array($replacement) && isset($replacement['fn'])) {
711
                    if (isset($replacement['data'])) {
712
                        $this->buffer = $replacement['data'];
713
                    }
714
715
                    return call_user_func([&$this, $replacement['fn']], $arguments, $i);
716
                } elseif (is_int($replacement)) {
717
                    return $arguments[$replacement + $i];
718
                }
719
                $delete = ($this->escapeChar == '' ||
720
                    strpos($arguments[$i], $this->escapeChar) === false)
721
                    ? '' : "\x01".$arguments[$i]."\x01";
722
723
                return $delete.$replacement;
724
                // skip over references to sub-expressions
725
            } else {
726
                $i += $pattern[self::LENGTH];
727
            }
728
        }
729
    }
730
731
    private function _backReferences($match, $offset)
732
    {
733
        $replacement = $this->buffer['replacement'];
734
        $quote = $this->buffer['quote'];
0 ignored issues
show
Unused Code introduced by
$quote is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
735
        $i = $this->buffer['length'];
736
        while ($i) {
737
            $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
738
        }
739
740
        return $replacement;
741
    }
742
743
    private function _replace_name($match, $offset)
744
    {
745
        $length = strlen($match[$offset + 2]);
746
        $start = $length - max($length - strlen($match[$offset + 3]), 0);
747
748
        return substr($match[$offset + 1], $start, $length).$match[$offset + 4];
749
    }
750
751
    private function _replace_encoded($match, $offset)
752
    {
753
        return $this->buffer[$match[$offset]];
754
    }
755
756
    // php : we cannot pass additional data to preg_replace_callback,
757
    // and we cannot use &$this in create_function, so let's go to lower level
758
    private $buffer;
759
760
    // encode escaped characters
761
    private function _escape($string, $escapeChar)
762
    {
763
        if ($escapeChar) {
764
            $this->buffer = $escapeChar;
765
766
            return preg_replace_callback(
767
                '/\\'.$escapeChar.'(.)'.'/',
768
                [&$this, '_escapeBis'],
769
                $string
770
            );
771
        } else {
772
            return $string;
773
        }
774
    }
775
776
    private function _escapeBis($match)
777
    {
778
        $this->_escaped[] = $match[1];
779
780
        return $this->buffer;
781
    }
782
783
    // decode escaped characters
784
    private function _unescape($string, $escapeChar)
785
    {
786
        if ($escapeChar) {
787
            $regexp = '/'.'\\'.$escapeChar.'/';
788
            $this->buffer = ['escapeChar' => $escapeChar, 'i' => 0];
789
790
            return preg_replace_callback(
791
                $regexp,
792
                [&$this, '_unescapeBis'],
793
                $string
794
            );
795
        } else {
796
            return $string;
797
        }
798
    }
799
800
    private function _unescapeBis()
801
    {
802
        if (isset($this->_escaped[$this->buffer['i']])
803
            && $this->_escaped[$this->buffer['i']] != ''
804
        ) {
805
            $temp = $this->_escaped[$this->buffer['i']];
806
        } else {
807
            $temp = '';
808
        }
809
        $this->buffer['i']++;
810
811
        return $this->buffer['escapeChar'].$temp;
812
    }
813
814
    private function _internalEscape($string)
815
    {
816
        return preg_replace($this->ESCAPE, '', $string);
817
    }
818
}
819