Completed
Push — master ( 760841...0bddd7 )
by Lars
01:21
created

removeUrlSchemeHelper()   B

Complexity

Conditions 11
Paths 2

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 11
cts 11
cp 1
rs 7.3166
c 0
b 0
f 0
cc 11
nc 2
nop 6
crap 11

How to fix   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
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * HtmlMinDomObserverOptimizeAttributes: Optimize html attributes. [protected html is still protected]
9
 *
10
 * Sort HTML-Attributes, so that gzip can do better work and remove some default attributes...
11
 */
12
final class HtmlMinDomObserverOptimizeAttributes implements HtmlMinDomObserverInterface
13
{
14
    /**
15
     * // https://mathiasbynens.be/demo/javascript-mime-type
16
     * // https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
17
     *
18
     * @var string[]
19
     *
20
     * @psalm-var array<string, string>
21
     */
22
    private static $executableScriptsMimeTypes = [
23
        'text/javascript'          => '',
24
        'text/ecmascript'          => '',
25
        'text/jscript'             => '',
26
        'application/javascript'   => '',
27
        'application/x-javascript' => '',
28
        'application/ecmascript'   => '',
29
    ];
30
31
    /**
32
     * Receive dom elements before the minification.
33
     *
34
     * @param SimpleHtmlDomInterface $element
35
     * @param HtmlMinInterface       $htmlMin
36
     *
37
     * @return void
38
     */
39 51
    public function domElementBeforeMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
40
    {
41 51
    }
42
43
    /**
44
     * Receive dom elements after the minification.
45
     *
46
     * @param SimpleHtmlDomInterface $element
47
     * @param HtmlMinInterface       $htmlMin
48
     *
49
     * @return void
50
     */
51 51
    public function domElementAfterMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
52
    {
53 51
        $attributes = $element->getAllAttributes();
54 51
        if ($attributes === null) {
55 50
            return;
56
        }
57
58 34
        $tagName = $element->getNode()->nodeName;
59 34
        $attrs = [];
60 34
        foreach ((array) $attributes as $attrName => $attrValue) {
61
62
            // -------------------------------------------------------------------------
63
            // Remove local domains from attributes.
64
            // -------------------------------------------------------------------------
65
66 34
            if ($htmlMin->isDoMakeSameDomainsLinksRelative()) {
67 1
                $localDomains = $htmlMin->getLocalDomains();
68 1
                foreach ($localDomains as $localDomain) {
69
                    /** @noinspection InArrayCanBeUsedInspection */
70
                    if (
71
                        (
72 1
                            $attrName === 'href'
73
                            ||
74 1
                            $attrName === 'src'
75
                            ||
76 1
                            $attrName === 'srcset'
77
                            ||
78 1
                            $attrName === 'action'
79
                        )
80
                        &&
81 1
                        !(isset($attributes['rel']) && $attributes['rel'] === 'external')
82
                        &&
83 1
                        !(isset($attributes['target']) && $attributes['target'] === '_blank')
84
                        &&
85 1
                        \stripos($attrValue, $localDomain) !== false
86
                    ) {
87 1
                        $localDomainEscaped = \preg_quote($localDomain, '/');
88
89 1
                        $attrValue = (string) \preg_replace("/^(?:(?:https?:)?\/\/)?{$localDomainEscaped}(?!\w)(?:\/?)/i", '/', $attrValue);
90
                    }
91
                }
92
            }
93
94
            // -------------------------------------------------------------------------
95
            // Remove optional "http:"-prefix from attributes.
96
            // -------------------------------------------------------------------------
97
98 34
            if ($htmlMin->isDoRemoveHttpPrefixFromAttributes()) {
99 5
                $attrValue = $this->removeUrlSchemeHelper(
100 5
                    $attrValue,
101 5
                    $attrName,
102 5
                    'http',
103 5
                    $attributes,
104 5
                    $tagName,
105 5
                    $htmlMin
106
                );
107
            }
108
109 34
            if ($htmlMin->isDoRemoveHttpsPrefixFromAttributes()) {
110 1
                $attrValue = $this->removeUrlSchemeHelper(
111 1
                    $attrValue,
112 1
                    $attrName,
113 1
                    'https',
114 1
                    $attributes,
115 1
                    $tagName,
116 1
                    $htmlMin
117
                );
118
            }
119
120
            // -------------------------------------------------------------------------
121
            // Remove some special attributes.
122
            // -------------------------------------------------------------------------
123
124 34
            if ($this->removeAttributeHelper(
125 34
                $element->tag,
0 ignored issues
show
Bug introduced by
Accessing tag on the interface voku\helper\SimpleHtmlDomInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
126 34
                $attrName,
127 34
                $attrValue,
128 34
                $attributes,
129 34
                $htmlMin
130
            )) {
131 6
                $element->{$attrName} = null;
132
133 6
                continue;
134
            }
135
136
            // -------------------------------------------------------------------------
137
            // Sort css-class-names, for better gzip results.
138
            // -------------------------------------------------------------------------
139
140 34
            if ($htmlMin->isDoSortCssClassNames()) {
141 33
                $attrValue = $this->sortCssClassNames($attrName, $attrValue);
142
            }
143
144 34
            if ($htmlMin->isDoSortHtmlAttributes()) {
145 33
                $attrs[$attrName] = $attrValue;
146 33
                $element->{$attrName} = null;
147
            }
148
        }
149
150
        // -------------------------------------------------------------------------
151
        // Sort html-attributes, for better gzip results.
152
        // -------------------------------------------------------------------------
153
154 34
        if ($htmlMin->isDoSortHtmlAttributes()) {
155 33
            \ksort($attrs);
156 33
            foreach ($attrs as $attrName => $attrValue) {
157 33
                $attrValue = HtmlDomParser::replaceToPreserveHtmlEntities($attrValue);
158 33
                $element->setAttribute((string) $attrName, $attrValue, true);
159
            }
160
        }
161 34
    }
162
163
    /**
164
     * Check if the attribute can be removed.
165
     *
166
     * @param string           $tag
167
     * @param string           $attrName
168
     * @param string           $attrValue
169
     * @param array            $allAttr
170
     * @param HtmlMinInterface $htmlMin
171
     *
172
     * @return bool
173
     */
174 34
    private function removeAttributeHelper($tag, $attrName, $attrValue, $allAttr, HtmlMinInterface $htmlMin): bool
175
    {
176
        // remove defaults
177 34
        if ($htmlMin->isDoRemoveDefaultAttributes()) {
178 1
            if ($tag === 'script' && $attrName === 'language' && $attrValue === 'javascript') {
179
                return true;
180
            }
181
182 1
            if ($tag === 'form' && $attrName === 'method' && $attrValue === 'get') {
183
                return true;
184
            }
185
186 1
            if ($tag === 'input' && $attrName === 'type' && $attrValue === 'text') {
187
                return true;
188
            }
189
190 1
            if ($tag === 'area' && $attrName === 'shape' && $attrValue === 'rect') {
191
                return true;
192
            }
193
        }
194
195
        // remove deprecated charset-attribute (the browser will use the charset from the HTTP-Header, anyway)
196 34
        if ($htmlMin->isDoRemoveDeprecatedScriptCharsetAttribute()) {
197
            /** @noinspection NestedPositiveIfStatementsInspection */
198 33
            if ($tag === 'script' && $attrName === 'charset' && !isset($allAttr['src'])) {
199
                return true;
200
            }
201
        }
202
203
        // remove deprecated anchor-jump
204 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedAnchorName()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
            /** @noinspection NestedPositiveIfStatementsInspection */
206 33
            if ($tag === 'a' && $attrName === 'name' && isset($allAttr['id']) && $allAttr['id'] === $attrValue) {
207
                return true;
208
            }
209
        }
210
211
        // remove "type=text/css" for css links
212 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedTypeFromStylesheetLink()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213
            /** @noinspection NestedPositiveIfStatementsInspection */
214 33
            if ($tag === 'link' && $attrName === 'type' && $attrValue === 'text/css' && isset($allAttr['rel']) && $allAttr['rel'] === 'stylesheet') {
215 1
                return true;
216
            }
217
        }
218
219
        // remove deprecated script-mime-types
220 34 View Code Duplication
        if ($htmlMin->isDoRemoveDeprecatedTypeFromScriptTag()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            /** @noinspection NestedPositiveIfStatementsInspection */
222 33
            if ($tag === 'script' && $attrName === 'type' && isset($allAttr['src'], self::$executableScriptsMimeTypes[$attrValue])) {
223 1
                return true;
224
            }
225
        }
226
227
        // remove 'value=""' from <input type="text">
228 34 View Code Duplication
        if ($htmlMin->isDoRemoveValueFromEmptyInput()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
            /** @noinspection NestedPositiveIfStatementsInspection */
230 33
            if ($tag === 'input' && $attrName === 'value' && $attrValue === '' && isset($allAttr['type']) && $allAttr['type'] === 'text') {
231 1
                return true;
232
            }
233
        }
234
235
        // remove some empty attributes
236 34
        if ($htmlMin->isDoRemoveEmptyAttributes()) {
237
            /** @noinspection NestedPositiveIfStatementsInspection */
238 33
            if (\trim($attrValue) === '' && \preg_match('/^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(?:down|up|over|move|out)|key(?:press|down|up)))$/', $attrName)) {
239 5
                return true;
240
            }
241
        }
242
243 34
        return false;
244
    }
245
246
    /**
247
     * @param string           $attrValue
248
     * @param string           $attrName
249
     * @param string           $scheme
250
     * @param string[]         $attributes
251
     * @param string           $tagName
252
     * @param HtmlMinInterface $htmlMin
253
     *
254
     * @return string
255
     *
256
     * @noinspection PhpTooManyParametersInspection
257
     */
258 5
    private function removeUrlSchemeHelper(
259
        string $attrValue,
260
        string $attrName,
261
        string $scheme,
262
        array $attributes,
263
        string $tagName,
264
        HtmlMinInterface $htmlMin
265
    ): string {
266
        /** @noinspection InArrayCanBeUsedInspection */
267
        if (
268 5
            !(isset($attributes['rel']) && $attributes['rel'] === 'external')
269
            &&
270 5
            !(isset($attributes['target']) && $attributes['target'] === '_blank')
271
            &&
272
            (
273
                (
274 5
                    $attrName === 'href'
275
                    &&
276
                    (
277 4
                        !$htmlMin->isdoKeepHttpAndHttpsPrefixOnExternalAttributes()
278
                        ||
279 4
                        $tagName === 'link'
280
                    )
281
                )
282
                ||
283 5
                $attrName === 'src'
284
                ||
285 5
                $attrName === 'srcset'
286
                ||
287 5
                $attrName === 'action'
288
            )
289
        ) {
290 4
            $attrValue = \str_replace($scheme . '://', '//', $attrValue);
291
        }
292
293 5
        return $attrValue;
294
    }
295
296
    /**
297
     * @param string $attrName
298
     * @param string $attrValue
299
     *
300
     * @return string
301
     */
302 33
    private function sortCssClassNames($attrName, $attrValue): string
303
    {
304 33
        if ($attrName !== 'class' || !$attrValue) {
305 28
            return $attrValue;
306
        }
307
308 18
        $classes = \array_unique(
309 18
            \explode(' ', $attrValue)
310
        );
311 18
        \sort($classes);
312
313 18
        $attrValue = '';
314 18
        foreach ($classes as $class) {
315 18
            if (!$class) {
316 3
                continue;
317
            }
318
319 18
            $attrValue .= \trim($class) . ' ';
320
        }
321
322 18
        return \trim($attrValue);
323
    }
324
}
325