Completed
Pull Request — master (#47)
by Lars
02:14 queued 43s
created

domElementAfterMinification()   F

Complexity

Conditions 21
Paths 124

Size

Total Lines 111

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 21

Importance

Changes 0
Metric Value
dl 0
loc 111
ccs 42
cts 42
cp 1
rs 3.1732
c 0
b 0
f 0
cc 21
nc 124
nop 2
crap 21

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
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 53
    public function domElementBeforeMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
40
    {
41 53
    }
42
43
    /**
44
     * Receive dom elements after the minification.
45
     *
46
     * @param SimpleHtmlDomInterface $element
47
     * @param HtmlMinInterface       $htmlMin
48
     *
49
     * @return void
50
     */
51 53
    public function domElementAfterMinification(SimpleHtmlDomInterface $element, HtmlMinInterface $htmlMin)
52
    {
53 53
        $attributes = $element->getAllAttributes();
54 53
        if ($attributes === null) {
55 51
            return;
56
        }
57
58 36
        $tagName = $element->getNode()->nodeName;
59 36
        $attrs = [];
60 36
        foreach ((array) $attributes as $attrName => $attrValue) {
61
62
            // -------------------------------------------------------------------------
63
            // Remove local domains from attributes.
64
            // -------------------------------------------------------------------------
65
66 36
            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 36
            if ($htmlMin->isDoRemoveHttpPrefixFromAttributes()) {
99 5
                $attrValue = $this->removeUrlSchemeHelper(
100 5
                    $attrValue,
101
                    $attrName,
102 5
                    'http',
103
                    $attributes,
104
                    $tagName,
105
                    $htmlMin
106
                );
107
            }
108
109 36
            if ($htmlMin->isDoRemoveHttpsPrefixFromAttributes()) {
110 1
                $attrValue = $this->removeUrlSchemeHelper(
111 1
                    $attrValue,
112
                    $attrName,
113 1
                    'https',
114
                    $attributes,
115
                    $tagName,
116
                    $htmlMin
117
                );
118
            }
119
120
            // -------------------------------------------------------------------------
121
            // Remove some special attributes.
122
            // -------------------------------------------------------------------------
123
124 36
            if ($this->removeAttributeHelper(
125 36
                $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
                $attrName,
127
                $attrValue,
128
                $attributes,
129
                $htmlMin
130
            )) {
131 8
                $element->{$attrName} = null;
132
133 8
                continue;
134
            }
135
136
            // -------------------------------------------------------------------------
137
            // Sort css-class-names, for better gzip results.
138
            // -------------------------------------------------------------------------
139
140 35
            if ($htmlMin->isDoSortCssClassNames()) {
141 34
                $attrValue = $this->sortCssClassNames($attrName, $attrValue);
142
            }
143
144 35
            if ($htmlMin->isDoSortHtmlAttributes()) {
145 34
                $attrs[$attrName] = $attrValue;
146 34
                $element->{$attrName} = null;
147
            }
148
        }
149
150
        // -------------------------------------------------------------------------
151
        // Sort html-attributes, for better gzip results.
152
        // -------------------------------------------------------------------------
153
154 36
        if ($htmlMin->isDoSortHtmlAttributes()) {
155 35
            \ksort($attrs);
156 35
            foreach ($attrs as $attrName => $attrValue) {
157 34
                $attrValue = HtmlDomParser::replaceToPreserveHtmlEntities($attrValue);
158 34
                $element->setAttribute((string) $attrName, $attrValue, true);
159
            }
160
        }
161 36
    }
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 36
    private function removeAttributeHelper($tag, $attrName, $attrValue, $allAttr, HtmlMinInterface $htmlMin): bool
175
    {
176
        // remove defaults
177 36
        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 36
        if ($htmlMin->isDoRemoveDeprecatedScriptCharsetAttribute()) {
197
            /** @noinspection NestedPositiveIfStatementsInspection */
198 35
            if ($tag === 'script' && $attrName === 'charset' && !isset($allAttr['src'])) {
199
                return true;
200
            }
201
        }
202
203
        // remove deprecated anchor-jump
204 36 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 35
            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 36 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 35
            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 36
        if ($htmlMin->isDoRemoveDeprecatedTypeFromScriptTag()) {
221
            /** @noinspection NestedPositiveIfStatementsInspection */
222 35
            if ($tag === 'script' && $attrName === 'type' && $htmlMin->isXHTML() === false && $htmlMin->isHTML4() === false && isset(self::$executableScriptsMimeTypes[$attrValue])) {
223 4
                return true;
224
            }
225
        }
226
227
        // remove 'value=""' from <input type="text">
228 35 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 34
            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 35
        if ($htmlMin->isDoRemoveEmptyAttributes()) {
237
            /** @noinspection NestedPositiveIfStatementsInspection */
238 34
            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 4
                return true;
240
            }
241
        }
242
243 35
        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 34
    private function sortCssClassNames($attrName, $attrValue): string
303
    {
304 34
        if ($attrName !== 'class' || !$attrValue) {
305 29
            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