Minify_HTML::_removeStyleCB()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 9.3142
cc 3
eloc 12
nc 2
nop 1
1
<?php
2
namespace samsonphp\compressor;
3
4
/**
5
 * Class Minify_HTML
6
 * @package Minify
7
 */
8
9
/**
10
 * Compress HTML
11
 *
12
 * This is a heavy regex-based removal of whitespace, unnecessary comments and
13
 * tokens. IE conditional comments are preserved. There are also options to have
14
 * STYLE and SCRIPT blocks compressed by callback functions.
15
 *
16
 * A test suite is available.
17
 *
18
 * @package Minify
19
 * @author Stephen Clay <[email protected]>
20
 */
21
class Minify_HTML {
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
22
    /**
23
     * @var boolean
24
     */
25
    protected $_jsCleanComments = true;
26
27
    /**
28
     * "Minify" an HTML page
29
     *
30
     * @param string $html
31
     *
32
     * @param array $options
33
     *
34
     * 'cssMinifier' : (optional) callback function to process content of STYLE
35
     * elements.
36
     *
37
     * 'jsMinifier' : (optional) callback function to process content of SCRIPT
38
     * elements. Note: the type attribute is ignored.
39
     *
40
     * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
41
     * unset, minify will sniff for an XHTML doctype.
42
     *
43
     * @return string
44
     */
45
    public static function minify($html, $options = array()) {
46
        $min = new self($html, $options);
47
        return $min->process();
48
    }
49
50
51
    /**
52
     * Create a minifier object
53
     *
54
     * @param string $html
55
     *
56
     * @param array $options
57
     *
58
     * 'cssMinifier' : (optional) callback function to process content of STYLE
59
     * elements.
60
     *
61
     * 'jsMinifier' : (optional) callback function to process content of SCRIPT
62
     * elements. Note: the type attribute is ignored.
63
     *
64
     * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
65
     *
66
     * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
67
     * unset, minify will sniff for an XHTML doctype.
68
     *
69
     * @return null
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
70
     */
71
    public function __construct($html, $options = array())
72
    {
73
        $this->_html = str_replace("\r\n", "\n", trim($html));
0 ignored issues
show
Bug introduced by
The property _html does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74
        if (isset($options['xhtml'])) {
75
            $this->_isXhtml = (bool)$options['xhtml'];
76
        }
77
        if (isset($options['cssMinifier'])) {
78
            $this->_cssMinifier = $options['cssMinifier'];
79
        }
80
        if (isset($options['jsMinifier'])) {
81
            $this->_jsMinifier = $options['jsMinifier'];
82
        }
83
        if (isset($options['jsCleanComments'])) {
84
            $this->_jsCleanComments = (bool)$options['jsCleanComments'];
85
        }
86
    }
87
88
89
    /**
90
     * Minify the markeup given in the constructor
91
     * 
92
     * @return string
93
     */
94
    public function process()
95
    {
96
        if ($this->_isXhtml === null) {
97
            $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
98
        }
99
        
100
        $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
101
        $this->_placeholders = array();
102
        
103
        // replace SCRIPTs (and minify) with placeholders
104
        $this->_html = preg_replace_callback(
105
            '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
106
            ,array($this, '_removeScriptCB')
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
107
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
108
        
109
        // replace STYLEs (and minify) with placeholders
110
        $this->_html = preg_replace_callback(
111
            '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
112
            ,array($this, '_removeStyleCB')
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
113
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
114
        
115
        // remove HTML comments (not containing IE conditional comments).
116
        $this->_html = preg_replace_callback(
117
            '/<!--([\\s\\S]*?)-->/'
118
            ,array($this, '_commentCB')
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
119
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
120
        
121
        // replace PREs with placeholders
122
        $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
123
            ,array($this, '_removePreCB')
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
124
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
125
        
126
        // replace TEXTAREAs with placeholders
127
        $this->_html = preg_replace_callback(
128
            '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
129
            ,array($this, '_removeTextareaCB')
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
130
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
131
        
132
        // trim each line.
133
        // @todo take into account attribute values that span multiple lines.
134
        $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
135
        
136
        // remove ws around block/undisplayed elements
137
        $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
138
            .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
139
            .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
140
            .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
141
            .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
142
        
143
        // remove ws outside of all elements
144
        $this->_html = preg_replace(
145
            '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
146
            ,'>$1$2$3<'
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
147
            ,$this->_html);
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
148
        
149
        // use newlines before 1st attribute in open tags (to limit line lengths)
150
        $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
151
        
152
        // fill placeholders
153
        $this->_html = str_replace(
154
            array_keys($this->_placeholders)
155
            ,array_values($this->_placeholders)
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
156
            ,$this->_html
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
157
        );
158
        // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
159
        $this->_html = str_replace(
160
            array_keys($this->_placeholders)
161
            ,array_values($this->_placeholders)
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
162
            ,$this->_html
0 ignored issues
show
Coding Style introduced by
Space found before comma in function call
Loading history...
163
        );
164
        return $this->_html;
165
    }
166
    
167
    protected function _commentCB($m)
168
    {
169
        return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
170
            ? $m[0]
171
            : '';
172
    }
173
    
174
    protected function _reservePlace($content)
175
    {
176
        $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
177
        $this->_placeholders[$placeholder] = $content;
178
        return $placeholder;
179
    }
180
181
    protected $_isXhtml = null;
182
    protected $_replacementHash = null;
183
    protected $_placeholders = array();
184
    protected $_cssMinifier = null;
185
    protected $_jsMinifier = null;
186
187
    protected function _removePreCB($m)
188
    {
189
        return $this->_reservePlace("<pre{$m[1]}");
190
    }
191
    
192
    protected function _removeTextareaCB($m)
193
    {
194
        return $this->_reservePlace("<textarea{$m[1]}");
195
    }
196
197
    protected function _removeStyleCB($m)
198
    {
199
        $openStyle = "<style{$m[1]}";
200
        $css = $m[2];
201
        // remove HTML comments
202
        $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
203
        
204
        // remove CDATA section markers
205
        $css = $this->_removeCdata($css);
206
        
207
        // minify
208
        $minifier = $this->_cssMinifier
209
            ? $this->_cssMinifier
210
            : 'trim';
211
        $css = call_user_func($minifier, $css);
212
        
213
        return $this->_reservePlace($this->_needsCdata($css)
214
            ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
215
            : "{$openStyle}{$css}</style>"
216
        );
217
    }
218
219
    protected function _removeScriptCB($m)
220
    {
221
        $openScript = "<script{$m[2]}";
222
        $js = $m[3];
223
        
224
        // whitespace surrounding? preserve at least one space
225
        $ws1 = ($m[1] === '') ? '' : ' ';
226
        $ws2 = ($m[4] === '') ? '' : ' ';
227
228
        // remove HTML comments (and ending "//" if present)
229
        if ($this->_jsCleanComments) {
230
            $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
231
        }
232
233
        // remove CDATA section markers
234
        $js = $this->_removeCdata($js);
235
        
236
        // minify
237
        $minifier = $this->_jsMinifier
238
            ? $this->_jsMinifier
239
            : 'trim';
240
        $js = call_user_func($minifier, $js);
241
        
242
        return $this->_reservePlace($this->_needsCdata($js)
243
            ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
244
            : "{$ws1}{$openScript}{$js}</script>{$ws2}"
245
        );
246
    }
247
248
    protected function _removeCdata($str)
249
    {
250
        return (false !== strpos($str, '<![CDATA['))
251
            ? str_replace(array('<![CDATA[', ']]>'), '', $str)
252
            : $str;
253
    }
254
    
255
    protected function _needsCdata($str)
256
    {
257
        return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
258
    }
259
}