Completed
Push — master ( 2a7a8f...d27882 )
by Lars
01:25
created

domElementAfterMinification()   F

Complexity

Conditions 21
Paths 304

Size

Total Lines 102

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 21.0031

Importance

Changes 0
Metric Value
dl 0
loc 102
ccs 51
cts 52
cp 0.9808
rs 1.6266
c 0
b 0
f 0
cc 21
nc 304
nop 2
crap 21.0031

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 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 49
            return;
56
        }
57
58 34
        $attrs = [];
59 34
        foreach ((array) $attributes as $attrName => $attrValue) {
60
61
            // -------------------------------------------------------------------------
62
            // Remove optional "http:"-prefix from attributes.
63
            // -------------------------------------------------------------------------
64
65 34
            if ($htmlMin->isDoRemoveHttpPrefixFromAttributes()) {
66 5
                $attrValue = $this->removeHttpPrefixHelper(
67 5
                    $attrValue,
68 5
                    $attrName,
69 5
                    'http',
70 5
                    $attributes,
71 5
                    $htmlMin
72
                );
73
            }
74
75 34
            if ($htmlMin->isDoRemoveHttpsPrefixFromAttributes()) {
76 1
                $attrValue = $this->removeHttpPrefixHelper(
77 1
                    $attrValue,
78 1
                    $attrName,
79 1
                    'https',
80 1
                    $attributes,
81 1
                    $htmlMin
82
                );
83
            }
84
85 34
            if ($htmlMin->isDoMakeSameDomainLinksRelative()) {
86 2
                if (!$htmlMin->isLocalDomainSet()) {
87
                    $htmlMin->setLocalDomain();
88
                }
89
90 2
                $localDomain = $htmlMin->getLocalDomain();
91
                /** @noinspection InArrayCanBeUsedInspection */
92
                if (
93
                    (
94 2
                        $attrName === 'href'
95
                        ||
96 1
                        $attrName === 'src'
97
                        ||
98 1
                        $attrName === 'srcset'
99
                        ||
100 2
                        $attrName === 'action'
101
                    )
102
                    &&
103 2
                    !(isset($attributes['rel']) && $attributes['rel'] === 'external')
104
                    &&
105 2
                    !(isset($attributes['target']) && $attributes['target'] === '_blank')
106
                    &&
107 2
                    \stripos($attrValue, $localDomain) !== false
108
                ) {
109 2
                    $localDomainEscaped = \preg_quote($localDomain, '/');
110
111 2
                    $attrValue = (string) \preg_replace("/^(?:(?:https?:)?\/\/)?{$localDomainEscaped}(?!\w)(?:\/?)/i", '/', $attrValue);
112
                }
113
            }
114
115 34
            if ($this->removeAttributeHelper(
116 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...
117 34
                $attrName,
118 34
                $attrValue,
119 34
                $attributes,
120 34
                $htmlMin
121
            )) {
122 6
                $element->{$attrName} = null;
123
124 6
                continue;
125
            }
126
127
            // -------------------------------------------------------------------------
128
            // Sort css-class-names, for better gzip results.
129
            // -------------------------------------------------------------------------
130
131 34
            if ($htmlMin->isDoSortCssClassNames()) {
132 33
                $attrValue = $this->sortCssClassNames($attrName, $attrValue);
133
            }
134
135 34
            if ($htmlMin->isDoSortHtmlAttributes()) {
136 33
                $attrs[$attrName] = $attrValue;
137 33
                $element->{$attrName} = null;
138
            }
139
        }
140
141
        // -------------------------------------------------------------------------
142
        // Sort html-attributes, for better gzip results.
143
        // -------------------------------------------------------------------------
144
145 34
        if ($htmlMin->isDoSortHtmlAttributes()) {
146 33
            \ksort($attrs);
147 33
            foreach ($attrs as $attrName => $attrValue) {
148 33
                $attrValue = HtmlDomParser::replaceToPreserveHtmlEntities($attrValue);
149 33
                $element->setAttribute((string) $attrName, $attrValue, true);
150
            }
151
        }
152 34
    }
153
154
    /**
155
     * Check if the attribute can be removed.
156
     *
157
     * @param string           $tag
158
     * @param string           $attrName
159
     * @param string           $attrValue
160
     * @param array            $allAttr
161
     * @param HtmlMinInterface $htmlMin
162
     *
163
     * @return bool
164
     */
165 34
    private function removeAttributeHelper($tag, $attrName, $attrValue, $allAttr, HtmlMinInterface $htmlMin): bool
166
    {
167
        // remove defaults
168 34
        if ($htmlMin->isDoRemoveDefaultAttributes()) {
169 1
            if ($tag === 'script' && $attrName === 'language' && $attrValue === 'javascript') {
170
                return true;
171
            }
172
173 1
            if ($tag === 'form' && $attrName === 'method' && $attrValue === 'get') {
174
                return true;
175
            }
176
177 1
            if ($tag === 'input' && $attrName === 'type' && $attrValue === 'text') {
178
                return true;
179
            }
180
181 1
            if ($tag === 'area' && $attrName === 'shape' && $attrValue === 'rect') {
182
                return true;
183
            }
184
        }
185
186
        // remove deprecated charset-attribute (the browser will use the charset from the HTTP-Header, anyway)
187 34
        if ($htmlMin->isDoRemoveDeprecatedScriptCharsetAttribute()) {
188
            /** @noinspection NestedPositiveIfStatementsInspection */
189 33
            if ($tag === 'script' && $attrName === 'charset' && !isset($allAttr['src'])) {
190
                return true;
191
            }
192
        }
193
194
        // remove deprecated anchor-jump
195 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...
196
            /** @noinspection NestedPositiveIfStatementsInspection */
197 33
            if ($tag === 'a' && $attrName === 'name' && isset($allAttr['id']) && $allAttr['id'] === $attrValue) {
198
                return true;
199
            }
200
        }
201
202
        // remove "type=text/css" for css links
203 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...
204
            /** @noinspection NestedPositiveIfStatementsInspection */
205 33
            if ($tag === 'link' && $attrName === 'type' && $attrValue === 'text/css' && isset($allAttr['rel']) && $allAttr['rel'] === 'stylesheet') {
206 1
                return true;
207
            }
208
        }
209
210
        // remove deprecated script-mime-types
211 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...
212
            /** @noinspection NestedPositiveIfStatementsInspection */
213 33
            if ($tag === 'script' && $attrName === 'type' && isset($allAttr['src'], self::$executableScriptsMimeTypes[$attrValue])) {
214 1
                return true;
215
            }
216
        }
217
218
        // remove 'value=""' from <input type="text">
219 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...
220
            /** @noinspection NestedPositiveIfStatementsInspection */
221 33
            if ($tag === 'input' && $attrName === 'value' && $attrValue === '' && isset($allAttr['type']) && $allAttr['type'] === 'text') {
222 1
                return true;
223
            }
224
        }
225
226
        // remove some empty attributes
227 34
        if ($htmlMin->isDoRemoveEmptyAttributes()) {
228
            /** @noinspection NestedPositiveIfStatementsInspection */
229 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)) {
230 5
                return true;
231
            }
232
        }
233
234 34
        return false;
235
    }
236
237
    /**
238
     * @param string           $attrValue
239
     * @param string           $attrName
240
     * @param string           $scheme
241
     * @param array            $attributes
242
     * @param HtmlMinInterface $htmlMin
243
     *
244
     * @return string
245
     */
246 5
    private function removeHttpPrefixHelper(
247
        string $attrValue,
248
        string $attrName,
249
        string $scheme,
250
        array $attributes,
251
        HtmlMinInterface $htmlMin
252
    ): string {
253
        /** @noinspection InArrayCanBeUsedInspection */
254
        if (
255 5
            !(isset($attributes['rel']) && $attributes['rel'] === 'external')
256
            &&
257 5
            !(isset($attributes['target']) && $attributes['target'] === '_blank')
258
            &&
259
            (
260
                (
261 5
                    $attrName === 'href'
262
                    &&
263 4
                    !$htmlMin->isKeepPrefixOnExternalAttributes()
264
                )
265
                ||
266 5
                $attrName === 'src'
267
                ||
268 5
                $attrName === 'srcset'
269
                ||
270 5
                $attrName === 'action'
271
            )
272
        ) {
273 4
            $attrValue = \str_replace($scheme . '://', '//', $attrValue);
274
        }
275
276 5
        return $attrValue;
277
    }
278
279
    /**
280
     * @param string $attrName
281
     * @param string $attrValue
282
     *
283
     * @return string
284
     */
285 33
    private function sortCssClassNames($attrName, $attrValue): string
286
    {
287 33
        if ($attrName !== 'class' || !$attrValue) {
288 28
            return $attrValue;
289
        }
290
291 18
        $classes = \array_unique(
292 18
            \explode(' ', $attrValue)
293
        );
294 18
        \sort($classes);
295
296 18
        $attrValue = '';
297 18
        foreach ($classes as $class) {
298 18
            if (!$class) {
299 3
                continue;
300
            }
301
302 18
            $attrValue .= \trim($class) . ' ';
303
        }
304
305 18
        return \trim($attrValue);
306
    }
307
}
308