Completed
Push — composer-installed ( 5832b4 )
by Ilia
08:49
created

Minify::_combineMinify()   F

Complexity

Conditions 20
Paths 1960

Size

Total Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
nc 1960
nop 0
dl 0
loc 90
rs 0
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 Minify  
4
 * @package Minify
5
 */
6
 
7
/**
8
 * Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
9
 *
10
 * See README for usage instructions (for now).
11
 *
12
 * This library was inspired by {@link mailto:[email protected] jscsscomp by Maxim Martynyuk}
13
 * and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
14
 *
15
 * Requires PHP 5.1.0.
16
 * Tested on PHP 5.1.6.
17
 *
18
 * @package Minify
19
 * @author Ryan Grove <[email protected]>
20
 * @author Stephen Clay <[email protected]>
21
 * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
22
 * @license http://opensource.org/licenses/bsd-license.php  New BSD License
23
 * @link http://code.google.com/p/minify/
24
 */
25
class Minify {
26
    
27
    const VERSION = '2.2.0';
28
    const TYPE_CSS = 'text/css';
29
    const TYPE_HTML = 'text/html';
30
    // there is some debate over the ideal JS Content-Type, but this is the
31
    // Apache default and what Yahoo! uses..
32
    const TYPE_JS = 'application/x-javascript';
33
    const URL_DEBUG = 'http://code.google.com/p/minify/wiki/Debugging';
34
    
35
    /**
36
     * How many hours behind are the file modification times of uploaded files?
37
     * 
38
     * If you upload files from Windows to a non-Windows server, Windows may report
39
     * incorrect mtimes for the files. Immediately after modifying and uploading a 
40
     * file, use the touch command to update the mtime on the server. If the mtime 
41
     * jumps ahead by a number of hours, set this variable to that number. If the mtime 
42
     * moves back, this should not be needed.
43
     *
44
     * @var int $uploaderHoursBehind
45
     */
46
    public static $uploaderHoursBehind = 0;
47
    
48
    /**
49
     * If this string is not empty AND the serve() option 'bubbleCssImports' is
50
     * NOT set, then serve() will check CSS files for @import declarations that
51
     * appear too late in the combined stylesheet. If found, serve() will prepend
52
     * the output with this warning.
53
     *
54
     * @var string $importWarning
55
     */
56
    public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
57
58
    /**
59
     * Has the DOCUMENT_ROOT been set in user code?
60
     * 
61
     * @var bool
62
     */
63
    public static $isDocRootSet = false;
64
65
    /**
66
     * Specify a cache object (with identical interface as Minify_Cache_File) or
67
     * a path to use with Minify_Cache_File.
68
     * 
69
     * If not called, Minify will not use a cache and, for each 200 response, will 
70
     * need to recombine files, minify and encode the output.
71
     *
72
     * @param mixed $cache object with identical interface as Minify_Cache_File or
73
     * a directory path, or null to disable caching. (default = '')
74
     * 
75
     * @param bool $fileLocking (default = true) This only applies if the first
76
     * parameter is a string.
77
     *
78
     * @return null
79
     */
80
    public static function setCache($cache = '', $fileLocking = true)
81
    {
82
        if (is_string($cache)) {
83
            self::$_cache = new Minify_Cache_File($cache, $fileLocking);
84
        } else {
85
            self::$_cache = $cache;
86
        }
87
    }
88
    
89
    /**
90
     * Serve a request for a minified file. 
91
     * 
92
     * Here are the available options and defaults in the base controller:
93
     * 
94
     * 'isPublic' : send "public" instead of "private" in Cache-Control 
95
     * headers, allowing shared caches to cache the output. (default true)
96
     * 
97
     * 'quiet' : set to true to have serve() return an array rather than sending
98
     * any headers/output (default false)
99
     * 
100
     * 'encodeOutput' : set to false to disable content encoding, and not send
101
     * the Vary header (default true)
102
     * 
103
     * 'encodeMethod' : generally you should let this be determined by 
104
     * HTTP_Encoder (leave null), but you can force a particular encoding
105
     * to be returned, by setting this to 'gzip' or '' (no encoding)
106
     * 
107
     * 'encodeLevel' : level of encoding compression (0 to 9, default 9)
108
     * 
109
     * 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
110
     * value to remove. (default 'utf-8')  
111
     * 
112
     * 'maxAge' : set this to the number of seconds the client should use its cache
113
     * before revalidating with the server. This sets Cache-Control: max-age and the
114
     * Expires header. Unlike the old 'setExpires' setting, this setting will NOT
115
     * prevent conditional GETs. Note this has nothing to do with server-side caching.
116
     * 
117
     * 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
118
     * minifier option to enable URI rewriting in CSS files (default true)
119
     * 
120
     * 'bubbleCssImports' : If true, all @import declarations in combined CSS
121
     * files will be move to the top. Note this may alter effective CSS values
122
     * due to a change in order. (default false)
123
     * 
124
     * 'debug' : set to true to minify all sources with the 'Lines' controller, which
125
     * eases the debugging of combined files. This also prevents 304 responses.
126
     * @see Minify_Lines::minify()
127
     *
128
     * 'concatOnly' : set to true to disable minification and simply concatenate the files.
129
     * For JS, no minifier will be used. For CSS, only URI rewriting is still performed.
130
     * 
131
     * 'minifiers' : to override Minify's default choice of minifier function for 
132
     * a particular content-type, specify your callback under the key of the 
133
     * content-type:
134
     * <code>
135
     * // call customCssMinifier($css) for all CSS minification
136
     * $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
137
     * 
138
     * // don't minify Javascript at all
139
     * $options['minifiers'][Minify::TYPE_JS] = '';
140
     * </code>
141
     * 
142
     * 'minifierOptions' : to send options to the minifier function, specify your options
143
     * under the key of the content-type. E.g. To send the CSS minifier an option: 
144
     * <code>
145
     * // give CSS minifier array('optionName' => 'optionValue') as 2nd argument 
146
     * $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
147
     * </code>
148
     * 
149
     * 'contentType' : (optional) this is only needed if your file extension is not 
150
     * js/css/html. The given content-type will be sent regardless of source file
151
     * extension, so this should not be used in a Groups config with other
152
     * Javascript/CSS files.
153
     * 
154
     * Any controller options are documented in that controller's setupSources() method.
155
     * 
156
     * @param mixed $controller instance of subclass of Minify_Controller_Base or string
157
     * name of controller. E.g. 'Files'
158
     * 
159
     * @param array $options controller/serve options
160
     * 
161
     * @return null|array if the 'quiet' option is set to true, an array
162
     * with keys "success" (bool), "statusCode" (int), "content" (string), and
163
     * "headers" (array).
164
     *
165
     * @throws Exception
166
     */
167
    public static function serve($controller, $options = array())
168
    {
169
        if (! self::$isDocRootSet && 0 === stripos(PHP_OS, 'win')) {
170
            self::setDocRoot();
171
        }
172
173
        if (is_string($controller)) {
174
            // make $controller into object
175
            $class = 'Minify_Controller_' . $controller;
176
            $controller = new $class();
177
            /* @var Minify_Controller_Base $controller */
178
        }
179
        
180
        // set up controller sources and mix remaining options with
181
        // controller defaults
182
        $options = $controller->setupSources($options);
183
        $options = $controller->analyzeSources($options);
184
        self::$_options = $controller->mixInDefaultOptions($options);
185
        
186
        // check request validity
187
        if (! $controller->sources) {
188
            // invalid request!
189
            if (! self::$_options['quiet']) {
190
                self::_errorExit(self::$_options['badRequestHeader'], self::URL_DEBUG);
191
            } else {
192
                list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']);
193
                return array(
194
                    'success' => false
195
                    ,'statusCode' => (int)$statusCode
196
                    ,'content' => ''
197
                    ,'headers' => array()
198
                );
199
            }
200
        }
201
        
202
        self::$_controller = $controller;
203
        
204
        if (self::$_options['debug']) {
205
            self::_setupDebug($controller->sources);
206
            self::$_options['maxAge'] = 0;
207
        }
208
        
209
        // determine encoding
210
        if (self::$_options['encodeOutput']) {
211
            $sendVary = true;
212
            if (self::$_options['encodeMethod'] !== null) {
213
                // controller specifically requested this
214
                $contentEncoding = self::$_options['encodeMethod'];
215
            } else {
216
                // sniff request header
217
                // depending on what the client accepts, $contentEncoding may be
218
                // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
219
                // getAcceptedEncoding(false, false) leaves out compress and deflate as options.
220
                list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
221
                $sendVary = ! HTTP_Encoder::isBuggyIe();
222
            }
223
        } else {
224
            self::$_options['encodeMethod'] = ''; // identity (no encoding)
225
        }
226
        
227
        // check client cache
228
        $cgOptions = array(
229
            'lastModifiedTime' => self::$_options['lastModifiedTime']
230
            ,'isPublic' => self::$_options['isPublic']
231
            ,'encoding' => self::$_options['encodeMethod']
232
        );
233
        if (self::$_options['maxAge'] > 0) {
234
            $cgOptions['maxAge'] = self::$_options['maxAge'];
235
        } elseif (self::$_options['debug']) {
236
            $cgOptions['invalidate'] = true;
237
        }
238
        $cg = new HTTP_ConditionalGet($cgOptions);
239
        if ($cg->cacheIsValid) {
240
            // client's cache is valid
241
            if (! self::$_options['quiet']) {
242
                $cg->sendHeaders();
243
                return;
244
            } else {
245
                return array(
246
                    'success' => true
247
                    ,'statusCode' => 304
248
                    ,'content' => ''
249
                    ,'headers' => $cg->getHeaders()
250
                );
251
            }
252
        } else {
253
            // client will need output
254
            $headers = $cg->getHeaders();
255
            unset($cg);
256
        }
257
258
        if (self::$_options['concatOnly']) {
259
            foreach ($controller->sources as $key => $source) {
260
                if (self::$_options['contentType'] === self::TYPE_JS) {
261
                    $source->minifier = "";
262
                } elseif (self::$_options['contentType'] === self::TYPE_CSS) {
263
                    $source->minifier = array('Minify_CSS', 'minify');
264
                    $source->minifyOptions['compress'] = false;
265
                }
266
            }
267
        }
268
269
        if (self::$_options['contentType'] === self::TYPE_CSS && self::$_options['rewriteCssUris']) {
270
            foreach ($controller->sources as $key => $source) {
271
                if ($source->filepath
272
                    && !isset($source->minifyOptions['currentDir'])
273
                    && !isset($source->minifyOptions['prependRelativePath'])
274
                ) {
275
                    $source->minifyOptions['currentDir'] = dirname($source->filepath);
276
                }
277
            }
278
        }
279
        
280
        // check server cache
281
        if (null !== self::$_cache && ! self::$_options['debug']) {
282
            // using cache
283
            // the goal is to use only the cache methods to sniff the length and 
284
            // output the content, as they do not require ever loading the file into
285
            // memory.
286
            $cacheId = self::_getCacheId();
287
            $fullCacheId = (self::$_options['encodeMethod'])
288
                ? $cacheId . '.gz'
289
                : $cacheId;
290
            // check cache for valid entry
291
            $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']); 
292
            if ($cacheIsReady) {
293
                $cacheContentLength = self::$_cache->getSize($fullCacheId);    
294
            } else {
295
                // generate & cache content
296
                try {
297
                    $content = self::_combineMinify();
298
                } catch (Exception $e) {
299
                    self::$_controller->log($e->getMessage());
300
                    if (! self::$_options['quiet']) {
301
                        self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
302
                    }
303
                    throw $e;
304
                }
305
                self::$_cache->store($cacheId, $content);
306
                if (function_exists('gzencode') && self::$_options['encodeMethod']) {
307
                    self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
308
                }
309
            }
310
        } else {
311
            // no cache
312
            $cacheIsReady = false;
313
            try {
314
                $content = self::_combineMinify();
315
            } catch (Exception $e) {
316
                self::$_controller->log($e->getMessage());
317
                if (! self::$_options['quiet']) {
318
                    self::_errorExit(self::$_options['errorHeader'], self::URL_DEBUG);
319
                }
320
                throw $e;
321
            }
322
        }
323
        if (! $cacheIsReady && self::$_options['encodeMethod']) {
324
            // still need to encode
325
            $content = gzencode($content, self::$_options['encodeLevel']);
0 ignored issues
show
Bug introduced by
The variable $content 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...
326
        }
327
        
328
        // add headers
329
        $headers['Content-Length'] = $cacheIsReady
330
            ? $cacheContentLength
0 ignored issues
show
Bug introduced by
The variable $cacheContentLength 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...
331
            : ((function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
332
                ? mb_strlen($content, '8bit')
333
                : strlen($content)
334
            );
335
        $headers['Content-Type'] = self::$_options['contentTypeCharset']
336
            ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
337
            : self::$_options['contentType'];
338
        if (self::$_options['encodeMethod'] !== '') {
339
            $headers['Content-Encoding'] = $contentEncoding;
0 ignored issues
show
Bug introduced by
The variable $contentEncoding 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...
340
        }
341
        if (self::$_options['encodeOutput'] && $sendVary) {
0 ignored issues
show
Bug introduced by
The variable $sendVary 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...
342
            $headers['Vary'] = 'Accept-Encoding';
343
        }
344
345
        if (! self::$_options['quiet']) {
346
            // output headers & content
347
            foreach ($headers as $name => $val) {
348
                header($name . ': ' . $val);
349
            }
350
            if ($cacheIsReady) {
351
                self::$_cache->display($fullCacheId);
0 ignored issues
show
Bug introduced by
The variable $fullCacheId 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...
352
            } else {
353
                echo $content;
354
            }
355
        } else {
356
            return array(
357
                'success' => true
358
                ,'statusCode' => 200
359
                ,'content' => $cacheIsReady
360
                    ? self::$_cache->fetch($fullCacheId)
361
                    : $content
362
                ,'headers' => $headers
363
            );
364
        }
365
    }
366
    
367
    /**
368
     * Return combined minified content for a set of sources
369
     *
370
     * No internal caching will be used and the content will not be HTTP encoded.
371
     * 
372
     * @param array $sources array of filepaths and/or Minify_Source objects
373
     * 
374
     * @param array $options (optional) array of options for serve. By default
375
     * these are already set: quiet = true, encodeMethod = '', lastModifiedTime = 0.
376
     * 
377
     * @return string
378
     */
379
    public static function combine($sources, $options = array())
380
    {
381
        $cache = self::$_cache;
382
        self::$_cache = null;
383
        $options = array_merge(array(
384
            'files' => (array)$sources
385
            ,'quiet' => true
386
            ,'encodeMethod' => ''
387
            ,'lastModifiedTime' => 0
388
        ), $options);
389
        $out = self::serve('Files', $options);
390
        self::$_cache = $cache;
391
        return $out['content'];
392
    }
393
    
394
    /**
395
     * Set $_SERVER['DOCUMENT_ROOT']. On IIS, the value is created from SCRIPT_FILENAME and SCRIPT_NAME.
396
     * 
397
     * @param string $docRoot value to use for DOCUMENT_ROOT
398
     */
399
    public static function setDocRoot($docRoot = '')
400
    {
401
        self::$isDocRootSet = true;
402
        if ($docRoot) {
403
            $_SERVER['DOCUMENT_ROOT'] = $docRoot;
404
        } elseif (isset($_SERVER['SERVER_SOFTWARE'])
405
                  && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')) {
406
            $_SERVER['DOCUMENT_ROOT'] = substr(
407
                $_SERVER['SCRIPT_FILENAME']
408
                ,0
409
                ,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']));
410
            $_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\');
411
        }
412
    }
413
    
414
    /**
415
     * Any Minify_Cache_* object or null (i.e. no server cache is used)
416
     *
417
     * @var Minify_Cache_File
418
     */
419
    private static $_cache = null;
420
    
421
    /**
422
     * Active controller for current request
423
     *
424
     * @var Minify_Controller_Base
425
     */
426
    protected static $_controller = null;
427
    
428
    /**
429
     * Options for current request
430
     *
431
     * @var array
432
     */
433
    protected static $_options = null;
434
435
    /**
436
     * @param string $header
437
     *
438
     * @param string $url
439
     */
440
    protected static function _errorExit($header, $url)
441
    {
442
        $url = htmlspecialchars($url, ENT_QUOTES);
443
        list(,$h1) = explode(' ', $header, 2);
444
        $h1 = htmlspecialchars($h1);
445
        // FastCGI environments require 3rd arg to header() to be set
446
        list(, $code) = explode(' ', $header, 3);
447
        header($header, true, $code);
448
        header('Content-Type: text/html; charset=utf-8');
449
        echo "<h1>$h1</h1>";
450
        echo "<p>Please see <a href='$url'>$url</a>.</p>";
451
        exit;
452
    }
453
454
    /**
455
     * Set up sources to use Minify_Lines
456
     *
457
     * @param Minify_Source[] $sources Minify_Source instances
458
     */
459
    protected static function _setupDebug($sources)
460
    {
461
        foreach ($sources as $source) {
462
            $source->minifier = array('Minify_Lines', 'minify');
463
            $id = $source->getId();
464
            $source->minifyOptions = array(
465
                'id' => (is_file($id) ? basename($id) : $id)
466
            );
467
        }
468
    }
469
    
470
    /**
471
     * Combines sources and minifies the result.
472
     *
473
     * @return string
474
     *
475
     * @throws Exception
476
     */
477
    protected static function _combineMinify()
478
    {
479
        $type = self::$_options['contentType']; // ease readability
480
        
481
        // when combining scripts, make sure all statements separated and
482
        // trailing single line comment is terminated
483
        $implodeSeparator = ($type === self::TYPE_JS)
484
            ? "\n;"
485
            : '';
486
        // allow the user to pass a particular array of options to each
487
        // minifier (designated by type). source objects may still override
488
        // these
489
        $defaultOptions = isset(self::$_options['minifierOptions'][$type])
490
            ? self::$_options['minifierOptions'][$type]
491
            : array();
492
        // if minifier not set, default is no minification. source objects
493
        // may still override this
494
        $defaultMinifier = isset(self::$_options['minifiers'][$type])
495
            ? self::$_options['minifiers'][$type]
496
            : false;
497
498
        // process groups of sources with identical minifiers/options
499
        $content = array();
500
        $i = 0;
501
        $l = count(self::$_controller->sources);
502
        $groupToProcessTogether = array();
503
        $lastMinifier = null;
504
        $lastOptions = null;
505
        do {
506
            // get next source
507
            $source = null;
508
            if ($i < $l) {
509
                $source = self::$_controller->sources[$i];               
510
                /* @var Minify_Source $source */
511
                $sourceContent = $source->getContent();
512
513
                // allow the source to override our minifier and options
514
                $minifier = (null !== $source->minifier)
515
                    ? $source->minifier
516
                    : $defaultMinifier;
517
                $options = (null !== $source->minifyOptions)
518
                    ? array_merge($defaultOptions, $source->minifyOptions)
519
                    : $defaultOptions;
520
            }
521
            // do we need to process our group right now?
522
            if ($i > 0                               // yes, we have at least the first group populated
523
                && (
524
                    ! $source                        // yes, we ran out of sources
525
                    || $type === self::TYPE_CSS      // yes, to process CSS individually (avoiding PCRE bugs/limits)
526
                    || $minifier !== $lastMinifier   // yes, minifier changed
0 ignored issues
show
Bug introduced by
The variable $minifier 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...
527
                    || $options !== $lastOptions)    // yes, options changed
0 ignored issues
show
Bug introduced by
The variable $options 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...
528
                )
529
            {
530
                // minify previous sources with last settings
531
                $imploded = implode($implodeSeparator, $groupToProcessTogether);
532
                $groupToProcessTogether = array();
533
                if ($lastMinifier) {
534
                    try {
535
                        $content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
536
                    } catch (Exception $e) {
537
                        throw new Exception("Exception in minifier: " . $e->getMessage());
538
                    }
539
                } else {
540
                    $content[] = $imploded;
541
                }
542
            }
543
            // add content to the group
544
            if ($source) {
545
                $groupToProcessTogether[] = $sourceContent;
0 ignored issues
show
Bug introduced by
The variable $sourceContent 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...
546
                $lastMinifier = $minifier;
547
                $lastOptions = $options;
548
            }
549
            $i++;
550
        } while ($source);
551
552
        $content = implode($implodeSeparator, $content);
553
        
554
        if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
555
            $content = self::_handleCssImports($content);
556
        }
557
        
558
        // do any post-processing (esp. for editing build URIs)
559
        if (self::$_options['postprocessorRequire']) {
560
            require_once self::$_options['postprocessorRequire'];
561
        }
562
        if (self::$_options['postprocessor']) {
563
            $content = call_user_func(self::$_options['postprocessor'], $content, $type);
564
        }
565
        return $content;
566
    }
567
    
568
    /**
569
     * Make a unique cache id for for this request.
570
     * 
571
     * Any settings that could affect output are taken into consideration  
572
     *
573
     * @param string $prefix
574
     *
575
     * @return string
576
     */
577
    protected static function _getCacheId($prefix = 'minify')
578
    {
579
        $name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', self::$_controller->selectionId);
580
        $name = preg_replace('/\\.+/', '.', $name);
581
        $name = substr($name, 0, 100 - 34 - strlen($prefix));
582
        $md5 = md5(serialize(array(
583
            Minify_Source::getDigest(self::$_controller->sources)
584
            ,self::$_options['minifiers'] 
585
            ,self::$_options['minifierOptions']
586
            ,self::$_options['postprocessor']
587
            ,self::$_options['bubbleCssImports']
588
            ,self::VERSION
589
        )));
590
        return "{$prefix}_{$name}_{$md5}";
591
    }
592
    
593
    /**
594
     * Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top.
595
     *
596
     * @param string $css
597
     *
598
     * @return string
599
     */
600
    protected static function _handleCssImports($css)
601
    {
602
        if (self::$_options['bubbleCssImports']) {
603
            // bubble CSS imports
604
            preg_match_all('/@import.*?;/', $css, $imports);
605
            $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
606
        } else if ('' !== self::$importWarning) {
607
            // remove comments so we don't mistake { in a comment as a block
608
            $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
609
            $lastImportPos = strrpos($noCommentCss, '@import');
610
            $firstBlockPos = strpos($noCommentCss, '{');
611
            if (false !== $lastImportPos
612
                && false !== $firstBlockPos
613
                && $firstBlockPos < $lastImportPos
614
            ) {
615
                // { appears before @import : prepend warning
616
                $css = self::$importWarning . $css;
617
            }
618
        }
619
        return $css;
620
    }
621
}
622