Completed
Push — master ( 4c5dfc...8b398d )
by Tim
32:19 queued 17:20
created
Classes/Service/CleanHtmlService.php 1 patch
Indentation   +409 added lines, -409 removed lines patch added patch discarded remove patch
@@ -17,413 +17,413 @@
 block discarded – undo
17 17
  */
18 18
 class CleanHtmlService implements SingletonInterface
19 19
 {
20
-    /**
21
-     * Enable Debug comment in footer.
22
-     *
23
-     * @var bool
24
-     */
25
-    protected $debugComment = false;
26
-
27
-    /**
28
-     * Format Type.
29
-     *
30
-     * @var int
31
-     */
32
-    protected $formatType = 0;
33
-
34
-    /**
35
-     * Tab character.
36
-     *
37
-     * @var string
38
-     */
39
-    protected $tab = "\t";
40
-
41
-    /**
42
-     * Newline character.
43
-     *
44
-     * @var string
45
-     */
46
-    protected $newline = "\n";
47
-
48
-    /**
49
-     * Configured extra header comment.
50
-     *
51
-     * @var string
52
-     */
53
-    protected $headerComment = '';
54
-
55
-    /**
56
-     * Empty space char.
57
-     *
58
-     * @var string
59
-     */
60
-    protected $emptySpaceChar = ' ';
61
-
62
-    /**
63
-     * Set variables based on given config.
64
-     */
65
-    public function setVariables(array $config): void
66
-    {
67
-        if (!empty($config)) {
68
-            if ($config['formatHtml'] && is_numeric($config['formatHtml'])) {
69
-                $this->formatType = (int) $config['formatHtml'];
70
-            }
71
-
72
-            if ($config['formatHtml.']['tabSize'] && is_numeric($config['formatHtml.']['tabSize'])) {
73
-                $this->tab = str_pad('', (int) $config['formatHtml.']['tabSize'], ' ');
74
-            }
75
-
76
-            if (isset($config['formatHtml.']['debugComment'])) {
77
-                $this->debugComment = (bool) $config['formatHtml.']['debugComment'];
78
-            }
79
-
80
-            if (isset($config['headerComment'])) {
81
-                $this->headerComment = $config['headerComment'];
82
-            }
83
-
84
-            if (isset($config['dropEmptySpaceChar']) && (bool) $config['dropEmptySpaceChar']) {
85
-                $this->emptySpaceChar = '';
86
-            }
87
-        }
88
-    }
89
-
90
-    /**
91
-     * Clean given HTML with formatter.
92
-     *
93
-     * @param string $html
94
-     * @param array  $config
95
-     *
96
-     * @return string
97
-     */
98
-    public function clean($html, $config = [])
99
-    {
100
-        if (!empty($config)) {
101
-            $this->setVariables($config);
102
-        }
103
-        // convert line-breaks to UNIX
104
-        $this->convNlOs($html);
105
-
106
-        $manipulations = [];
107
-
108
-        if (isset($config['removeGenerator']) && (bool) $config['removeGenerator']) {
109
-            $manipulations['removeGenerator'] = GeneralUtility::makeInstance(RemoveGenerator::class);
110
-        }
111
-
112
-        if (isset($config['removeComments']) && (bool) $config['removeComments']) {
113
-            $manipulations['removeComments'] = GeneralUtility::makeInstance(RemoveComments::class);
114
-        }
115
-
116
-        if (!empty($this->headerComment)) {
117
-            $this->includeHeaderComment($html);
118
-        }
119
-
120
-        foreach ($manipulations as $key => $manipulation) {
121
-            /** @var ManipulationInterface $manipulation */
122
-            $configuration = isset($config[$key.'.']) && \is_array($config[$key.'.']) ? $config[$key.'.'] : [];
123
-            $html = $manipulation->manipulate($html, $configuration);
124
-        }
125
-
126
-        // cleanup HTML5 self-closing elements
127
-        if (!isset($GLOBALS['TSFE']->config['config']['doctype'])
128
-            || 'x' !== substr($GLOBALS['TSFE']->config['config']['doctype'], 0, 1)) {
129
-            $html = preg_replace(
130
-                '/<((?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\s[^>]+?)\s?\/>/',
131
-                '<$1>',
132
-                $html
133
-            );
134
-        }
135
-
136
-        if ($this->formatType > 0) {
137
-            $html = $this->formatHtml($html);
138
-        }
139
-        // remove white space after line ending
140
-        $this->rTrimLines($html);
141
-
142
-        // recover line-breaks
143
-        if (Environment::isWindows()) {
144
-            $html = str_replace($this->newline, "\r\n", $html);
145
-        }
146
-
147
-        return $html;
148
-    }
149
-
150
-    /**
151
-     * Formats the (X)HTML code:
152
-     *  - taps according to the hirarchy of the tags
153
-     *  - removes empty spaces between tags
154
-     *  - removes linebreaks within tags (spares where necessary: pre, textarea, comments, ..)
155
-     *  choose from five options:
156
-     *    0 => off
157
-     *    1 => no line break at all  (code in one line)
158
-     *    2 => minimalistic line breaks (structure defining box-elements)
159
-     *    3 => aesthetic line breaks (important box-elements)
160
-     *    4 => logic line breaks (all box-elements)
161
-     *    5 => max line breaks (all elements).
162
-     *
163
-     * @param string $html
164
-     *
165
-     * @return string
166
-     */
167
-    protected function formatHtml($html)
168
-    {
169
-        // Save original formated pre, textarea, comments, styles and scripts & replace them with markers
170
-        preg_match_all(
171
-            '/(?s)((<!--.*?-->)|(<[ \n\r]*pre[^>]*>.*?<[ \n\r]*\/pre[^>]*>)|(<[ \n\r]*textarea[^>]*>.*?<[ \n\r]*\/textarea[^>]*>)|(<[ \n\r]*style[^>]*>.*?<[ \n\r]*\/style[^>]*>)|(<[ \n\r]*script[^>]*>.*?<[ \n\r]*\/script[^>]*>))/im',
172
-            $html,
173
-            $matches
174
-        );
175
-        $noFormat = $matches[0]; // do not format these block elements
176
-        for ($i = 0; $i < \count($noFormat); ++$i) {
177
-            $html = str_replace($noFormat[$i], "\n<!-- ELEMENT {$i} -->", $html);
178
-        }
179
-
180
-        // define box elements for formatting
181
-        $trueBoxElements = 'address|blockquote|center|dir|div|dl|fieldset|form|h1|h2|h3|h4|h5|h6|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|article|aside|details|figcaption|figure|footer|header|hgroup|menu|nav|section';
182
-        $functionalBoxElements = 'dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|colgroup';
183
-        $usableBoxElements = 'applet|button|del|iframe|ins|map|object|script';
184
-        $imagineBoxElements = 'html|body|head|meta|title|link|script|base|!--';
185
-        $allBoxLikeElements = '(?>'.$trueBoxElements.'|'.$functionalBoxElements.'|'.$usableBoxElements.'|'.$imagineBoxElements.')';
186
-        $esteticBoxLikeElements = '(?>html|head|body|meta name|title|div|table|h1|h2|h3|h4|h5|h6|p|form|pre|center|!--)';
187
-        $structureBoxLikeElements = '(?>html|head|body|div|!--)';
188
-
189
-        // split html into it's elements
190
-        $htmlArrayTemp = preg_split(
191
-            '/(<(?:[^<>]+(?:"[^"]*"|\'[^\']*\')?)+>)/',
192
-            $html,
193
-            -1,
194
-            \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY
195
-        );
196
-
197
-        if (false === $htmlArrayTemp) {
198
-            // Restore saved comments, styles and scripts
199
-            for ($i = 0; $i < \count($noFormat); ++$i) {
200
-                $html = str_replace("<!-- ELEMENT {$i} -->", $noFormat[$i], $html);
201
-            }
202
-
203
-            return $html;
204
-        }
205
-        // remove empty lines
206
-        $htmlArray = [''];
207
-        $index = 1;
208
-        for ($x = 0; $x < \count($htmlArrayTemp); ++$x) {
209
-            $text = trim($htmlArrayTemp[$x]);
210
-            $htmlArray[$index] = '' !== $text ? $htmlArrayTemp[$x] : $this->emptySpaceChar;
211
-            ++$index;
212
-        }
213
-
214
-        // rebuild html
215
-        $html = '';
216
-        $tabs = 0;
217
-        for ($x = 0; $x < \count($htmlArray); ++$x) {
218
-            $htmlArrayBefore = $htmlArray[$x - 1] ?? '';
219
-            $htmlArrayCurrent = $htmlArray[$x] ?? '';
220
-
221
-            // check if the element should stand in a new line
222
-            $newline = false;
223
-            if ('<?xml' == substr($htmlArrayBefore, 0, 5)) {
224
-                $newline = true;
225
-            } elseif (2 == $this->formatType && ( // minimalistic line break
226
-                // this element has a line break before itself
227
-                preg_match(
228
-                    '/<'.$structureBoxLikeElements.'(.*)>/Usi',
229
-                    $htmlArrayCurrent
230
-                ) || preg_match(
231
-                    '/<'.$structureBoxLikeElements.'(.*) \/>/Usi',
232
-                    $htmlArrayCurrent
233
-                ) // one element before is a element that has a line break after
234
-                || preg_match(
235
-                    '/<\/'.$structureBoxLikeElements.'(.*)>/Usi',
236
-                    $htmlArrayBefore
237
-                ) || '<!--' == substr(
238
-                    $htmlArrayBefore,
239
-                    0,
240
-                    4
241
-                ) || preg_match('/<'.$structureBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
242
-            ) {
243
-                $newline = true;
244
-            } elseif (3 == $this->formatType && ( // aestetic line break
245
-                // this element has a line break before itself
246
-                preg_match(
247
-                    '/<'.$esteticBoxLikeElements.'(.*)>/Usi',
248
-                    $htmlArrayCurrent
249
-                ) || preg_match(
250
-                    '/<'.$esteticBoxLikeElements.'(.*) \/>/Usi',
251
-                    $htmlArrayCurrent
252
-                ) // one element before is a element that has a line break after
253
-                || preg_match('/<\/'.$esteticBoxLikeElements.'(.*)>/Usi', $htmlArrayBefore) || '<!--' == substr(
254
-                    $htmlArrayBefore,
255
-                    0,
256
-                    4
257
-                ) || preg_match('/<'.$esteticBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
258
-            ) {
259
-                $newline = true;
260
-            } elseif ($this->formatType >= 4 && ( // logical line break
261
-                // this element has a line break before itself
262
-                preg_match(
263
-                    '/<'.$allBoxLikeElements.'(.*)>/Usi',
264
-                    $htmlArrayCurrent
265
-                ) || preg_match(
266
-                    '/<'.$allBoxLikeElements.'(.*) \/>/Usi',
267
-                    $htmlArrayCurrent
268
-                ) // one element before is a element that has a line break after
269
-                || preg_match('/<\/'.$allBoxLikeElements.'(.*)>/Usi', $htmlArrayBefore) || '<!--' == substr(
270
-                    $htmlArrayBefore,
271
-                    0,
272
-                    4
273
-                ) || preg_match('/<'.$allBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
274
-            ) {
275
-                $newline = true;
276
-            }
277
-
278
-            // count down a tab
279
-            if ('</' == substr($htmlArrayCurrent, 0, 2)) {
280
-                --$tabs;
281
-            }
282
-
283
-            // add tabs and line breaks in front of the current tag
284
-            if ($newline) {
285
-                $html .= $this->newline;
286
-                for ($y = 0; $y < $tabs; ++$y) {
287
-                    $html .= $this->tab;
288
-                }
289
-            }
290
-
291
-            // remove white spaces and line breaks and add current tag to the html-string
292
-            if ('<![CDATA[' == substr($htmlArrayCurrent, 0, 9) // remove multiple white space in CDATA / XML
293
-                || '<?xml' == substr($htmlArrayCurrent, 0, 5)
294
-            ) {
295
-                $html .= $this->killWhiteSpace($htmlArrayCurrent);
296
-            } else { // remove all line breaks
297
-                $html .= $this->killLineBreaks($htmlArrayCurrent);
298
-            }
299
-
300
-            // count up a tab
301
-            if ('<' == substr($htmlArrayCurrent, 0, 1) && '/' != substr($htmlArrayCurrent, 1, 1)) {
302
-                if (' ' !== substr($htmlArrayCurrent, 1, 1)
303
-                    && 'img' !== substr($htmlArrayCurrent, 1, 3)
304
-                    && 'source' !== substr($htmlArrayCurrent, 1, 6)
305
-                    && 'br' !== substr($htmlArrayCurrent, 1, 2)
306
-                    && 'hr' !== substr($htmlArrayCurrent, 1, 2)
307
-                    && 'input' !== substr($htmlArrayCurrent, 1, 5)
308
-                    && 'link' !== substr($htmlArrayCurrent, 1, 4)
309
-                    && 'meta' !== substr($htmlArrayCurrent, 1, 4)
310
-                    && 'col ' !== substr($htmlArrayCurrent, 1, 4)
311
-                    && 'frame' !== substr($htmlArrayCurrent, 1, 5)
312
-                    && 'isindex' !== substr($htmlArrayCurrent, 1, 7)
313
-                    && 'param' !== substr($htmlArrayCurrent, 1, 5)
314
-                    && 'area' !== substr($htmlArrayCurrent, 1, 4)
315
-                    && 'base' !== substr($htmlArrayCurrent, 1, 4)
316
-                    && '<!' !== substr($htmlArrayCurrent, 0, 2)
317
-                    && '<?xml' !== substr($htmlArrayCurrent, 0, 5)
318
-                ) {
319
-                    ++$tabs;
320
-                }
321
-            }
322
-        }
323
-
324
-        // Remove empty lines
325
-        if ($this->formatType > 1) {
326
-            $this->removeEmptyLines($html);
327
-        }
328
-
329
-        // Restore saved comments, styles and scripts
330
-        for ($i = 0; $i < \count($noFormat); ++$i) {
331
-            $html = str_replace("<!-- ELEMENT {$i} -->", $noFormat[$i], $html);
332
-        }
333
-
334
-        // include debug comment at the end
335
-        if (0 != $tabs && true === $this->debugComment) {
336
-            $html .= "<!-- {$tabs} open elements found -->";
337
-        }
338
-
339
-        return $html;
340
-    }
341
-
342
-    /**
343
-     * Remove ALL line breaks and multiple white space.
344
-     *
345
-     * @param string $html
346
-     *
347
-     * @return string
348
-     */
349
-    protected function killLineBreaks($html)
350
-    {
351
-        $html = str_replace($this->newline, '', $html);
352
-
353
-        return preg_replace('/\s\s+/u', ' ', $html);
354
-        // ? return preg_replace('/\n|\s+(\s)/u', '$1', $html);
355
-    }
356
-
357
-    /**
358
-     * Remove multiple white space, keeps line breaks.
359
-     *
360
-     * @param string $html
361
-     *
362
-     * @return string
363
-     */
364
-    protected function killWhiteSpace($html)
365
-    {
366
-        $temp = explode($this->newline, $html);
367
-        for ($i = 0; $i < \count($temp); ++$i) {
368
-            if (!trim($temp[$i])) {
369
-                unset($temp[$i]);
370
-                continue;
371
-            }
372
-
373
-            $temp[$i] = trim($temp[$i]);
374
-            $temp[$i] = preg_replace('/\s\s+/', ' ', $temp[$i]);
375
-        }
376
-
377
-        return implode($this->newline, $temp);
378
-    }
379
-
380
-    /**
381
-     * Remove white space at the end of lines, keeps other white space and line breaks.
382
-     *
383
-     * @param string $html
384
-     *
385
-     * @return string
386
-     */
387
-    protected function rTrimLines(&$html)
388
-    {
389
-        $html = preg_replace('/\s+$/m', '', $html);
390
-    }
391
-
392
-    /**
393
-     * Convert newlines according to the current OS.
394
-     *
395
-     * @param string $html
396
-     *
397
-     * @return string
398
-     */
399
-    protected function convNlOs(&$html)
400
-    {
401
-        $html = preg_replace("(\r\n|\r)", $this->newline, $html);
402
-    }
403
-
404
-    /**
405
-     * Remove empty lines.
406
-     *
407
-     * @param string $html
408
-     */
409
-    protected function removeEmptyLines(&$html): void
410
-    {
411
-        $temp = explode($this->newline, $html);
412
-        $result = [];
413
-        for ($i = 0; $i < \count($temp); ++$i) {
414
-            if ('' == trim($temp[$i])) {
415
-                continue;
416
-            }
417
-            $result[] = $temp[$i];
418
-        }
419
-        $html = implode($this->newline, $result);
420
-    }
421
-
422
-    /**
423
-     * Include configured header comment in HTML content block.
424
-     */
425
-    public function includeHeaderComment(&$html): void
426
-    {
427
-        $html = preg_replace('/^(-->)$/m', "\n\t".$this->headerComment."\n$1", $html);
428
-    }
20
+	/**
21
+	 * Enable Debug comment in footer.
22
+	 *
23
+	 * @var bool
24
+	 */
25
+	protected $debugComment = false;
26
+
27
+	/**
28
+	 * Format Type.
29
+	 *
30
+	 * @var int
31
+	 */
32
+	protected $formatType = 0;
33
+
34
+	/**
35
+	 * Tab character.
36
+	 *
37
+	 * @var string
38
+	 */
39
+	protected $tab = "\t";
40
+
41
+	/**
42
+	 * Newline character.
43
+	 *
44
+	 * @var string
45
+	 */
46
+	protected $newline = "\n";
47
+
48
+	/**
49
+	 * Configured extra header comment.
50
+	 *
51
+	 * @var string
52
+	 */
53
+	protected $headerComment = '';
54
+
55
+	/**
56
+	 * Empty space char.
57
+	 *
58
+	 * @var string
59
+	 */
60
+	protected $emptySpaceChar = ' ';
61
+
62
+	/**
63
+	 * Set variables based on given config.
64
+	 */
65
+	public function setVariables(array $config): void
66
+	{
67
+		if (!empty($config)) {
68
+			if ($config['formatHtml'] && is_numeric($config['formatHtml'])) {
69
+				$this->formatType = (int) $config['formatHtml'];
70
+			}
71
+
72
+			if ($config['formatHtml.']['tabSize'] && is_numeric($config['formatHtml.']['tabSize'])) {
73
+				$this->tab = str_pad('', (int) $config['formatHtml.']['tabSize'], ' ');
74
+			}
75
+
76
+			if (isset($config['formatHtml.']['debugComment'])) {
77
+				$this->debugComment = (bool) $config['formatHtml.']['debugComment'];
78
+			}
79
+
80
+			if (isset($config['headerComment'])) {
81
+				$this->headerComment = $config['headerComment'];
82
+			}
83
+
84
+			if (isset($config['dropEmptySpaceChar']) && (bool) $config['dropEmptySpaceChar']) {
85
+				$this->emptySpaceChar = '';
86
+			}
87
+		}
88
+	}
89
+
90
+	/**
91
+	 * Clean given HTML with formatter.
92
+	 *
93
+	 * @param string $html
94
+	 * @param array  $config
95
+	 *
96
+	 * @return string
97
+	 */
98
+	public function clean($html, $config = [])
99
+	{
100
+		if (!empty($config)) {
101
+			$this->setVariables($config);
102
+		}
103
+		// convert line-breaks to UNIX
104
+		$this->convNlOs($html);
105
+
106
+		$manipulations = [];
107
+
108
+		if (isset($config['removeGenerator']) && (bool) $config['removeGenerator']) {
109
+			$manipulations['removeGenerator'] = GeneralUtility::makeInstance(RemoveGenerator::class);
110
+		}
111
+
112
+		if (isset($config['removeComments']) && (bool) $config['removeComments']) {
113
+			$manipulations['removeComments'] = GeneralUtility::makeInstance(RemoveComments::class);
114
+		}
115
+
116
+		if (!empty($this->headerComment)) {
117
+			$this->includeHeaderComment($html);
118
+		}
119
+
120
+		foreach ($manipulations as $key => $manipulation) {
121
+			/** @var ManipulationInterface $manipulation */
122
+			$configuration = isset($config[$key.'.']) && \is_array($config[$key.'.']) ? $config[$key.'.'] : [];
123
+			$html = $manipulation->manipulate($html, $configuration);
124
+		}
125
+
126
+		// cleanup HTML5 self-closing elements
127
+		if (!isset($GLOBALS['TSFE']->config['config']['doctype'])
128
+			|| 'x' !== substr($GLOBALS['TSFE']->config['config']['doctype'], 0, 1)) {
129
+			$html = preg_replace(
130
+				'/<((?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\s[^>]+?)\s?\/>/',
131
+				'<$1>',
132
+				$html
133
+			);
134
+		}
135
+
136
+		if ($this->formatType > 0) {
137
+			$html = $this->formatHtml($html);
138
+		}
139
+		// remove white space after line ending
140
+		$this->rTrimLines($html);
141
+
142
+		// recover line-breaks
143
+		if (Environment::isWindows()) {
144
+			$html = str_replace($this->newline, "\r\n", $html);
145
+		}
146
+
147
+		return $html;
148
+	}
149
+
150
+	/**
151
+	 * Formats the (X)HTML code:
152
+	 *  - taps according to the hirarchy of the tags
153
+	 *  - removes empty spaces between tags
154
+	 *  - removes linebreaks within tags (spares where necessary: pre, textarea, comments, ..)
155
+	 *  choose from five options:
156
+	 *    0 => off
157
+	 *    1 => no line break at all  (code in one line)
158
+	 *    2 => minimalistic line breaks (structure defining box-elements)
159
+	 *    3 => aesthetic line breaks (important box-elements)
160
+	 *    4 => logic line breaks (all box-elements)
161
+	 *    5 => max line breaks (all elements).
162
+	 *
163
+	 * @param string $html
164
+	 *
165
+	 * @return string
166
+	 */
167
+	protected function formatHtml($html)
168
+	{
169
+		// Save original formated pre, textarea, comments, styles and scripts & replace them with markers
170
+		preg_match_all(
171
+			'/(?s)((<!--.*?-->)|(<[ \n\r]*pre[^>]*>.*?<[ \n\r]*\/pre[^>]*>)|(<[ \n\r]*textarea[^>]*>.*?<[ \n\r]*\/textarea[^>]*>)|(<[ \n\r]*style[^>]*>.*?<[ \n\r]*\/style[^>]*>)|(<[ \n\r]*script[^>]*>.*?<[ \n\r]*\/script[^>]*>))/im',
172
+			$html,
173
+			$matches
174
+		);
175
+		$noFormat = $matches[0]; // do not format these block elements
176
+		for ($i = 0; $i < \count($noFormat); ++$i) {
177
+			$html = str_replace($noFormat[$i], "\n<!-- ELEMENT {$i} -->", $html);
178
+		}
179
+
180
+		// define box elements for formatting
181
+		$trueBoxElements = 'address|blockquote|center|dir|div|dl|fieldset|form|h1|h2|h3|h4|h5|h6|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|article|aside|details|figcaption|figure|footer|header|hgroup|menu|nav|section';
182
+		$functionalBoxElements = 'dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|colgroup';
183
+		$usableBoxElements = 'applet|button|del|iframe|ins|map|object|script';
184
+		$imagineBoxElements = 'html|body|head|meta|title|link|script|base|!--';
185
+		$allBoxLikeElements = '(?>'.$trueBoxElements.'|'.$functionalBoxElements.'|'.$usableBoxElements.'|'.$imagineBoxElements.')';
186
+		$esteticBoxLikeElements = '(?>html|head|body|meta name|title|div|table|h1|h2|h3|h4|h5|h6|p|form|pre|center|!--)';
187
+		$structureBoxLikeElements = '(?>html|head|body|div|!--)';
188
+
189
+		// split html into it's elements
190
+		$htmlArrayTemp = preg_split(
191
+			'/(<(?:[^<>]+(?:"[^"]*"|\'[^\']*\')?)+>)/',
192
+			$html,
193
+			-1,
194
+			\PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY
195
+		);
196
+
197
+		if (false === $htmlArrayTemp) {
198
+			// Restore saved comments, styles and scripts
199
+			for ($i = 0; $i < \count($noFormat); ++$i) {
200
+				$html = str_replace("<!-- ELEMENT {$i} -->", $noFormat[$i], $html);
201
+			}
202
+
203
+			return $html;
204
+		}
205
+		// remove empty lines
206
+		$htmlArray = [''];
207
+		$index = 1;
208
+		for ($x = 0; $x < \count($htmlArrayTemp); ++$x) {
209
+			$text = trim($htmlArrayTemp[$x]);
210
+			$htmlArray[$index] = '' !== $text ? $htmlArrayTemp[$x] : $this->emptySpaceChar;
211
+			++$index;
212
+		}
213
+
214
+		// rebuild html
215
+		$html = '';
216
+		$tabs = 0;
217
+		for ($x = 0; $x < \count($htmlArray); ++$x) {
218
+			$htmlArrayBefore = $htmlArray[$x - 1] ?? '';
219
+			$htmlArrayCurrent = $htmlArray[$x] ?? '';
220
+
221
+			// check if the element should stand in a new line
222
+			$newline = false;
223
+			if ('<?xml' == substr($htmlArrayBefore, 0, 5)) {
224
+				$newline = true;
225
+			} elseif (2 == $this->formatType && ( // minimalistic line break
226
+				// this element has a line break before itself
227
+				preg_match(
228
+					'/<'.$structureBoxLikeElements.'(.*)>/Usi',
229
+					$htmlArrayCurrent
230
+				) || preg_match(
231
+					'/<'.$structureBoxLikeElements.'(.*) \/>/Usi',
232
+					$htmlArrayCurrent
233
+				) // one element before is a element that has a line break after
234
+				|| preg_match(
235
+					'/<\/'.$structureBoxLikeElements.'(.*)>/Usi',
236
+					$htmlArrayBefore
237
+				) || '<!--' == substr(
238
+					$htmlArrayBefore,
239
+					0,
240
+					4
241
+				) || preg_match('/<'.$structureBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
242
+			) {
243
+				$newline = true;
244
+			} elseif (3 == $this->formatType && ( // aestetic line break
245
+				// this element has a line break before itself
246
+				preg_match(
247
+					'/<'.$esteticBoxLikeElements.'(.*)>/Usi',
248
+					$htmlArrayCurrent
249
+				) || preg_match(
250
+					'/<'.$esteticBoxLikeElements.'(.*) \/>/Usi',
251
+					$htmlArrayCurrent
252
+				) // one element before is a element that has a line break after
253
+				|| preg_match('/<\/'.$esteticBoxLikeElements.'(.*)>/Usi', $htmlArrayBefore) || '<!--' == substr(
254
+					$htmlArrayBefore,
255
+					0,
256
+					4
257
+				) || preg_match('/<'.$esteticBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
258
+			) {
259
+				$newline = true;
260
+			} elseif ($this->formatType >= 4 && ( // logical line break
261
+				// this element has a line break before itself
262
+				preg_match(
263
+					'/<'.$allBoxLikeElements.'(.*)>/Usi',
264
+					$htmlArrayCurrent
265
+				) || preg_match(
266
+					'/<'.$allBoxLikeElements.'(.*) \/>/Usi',
267
+					$htmlArrayCurrent
268
+				) // one element before is a element that has a line break after
269
+				|| preg_match('/<\/'.$allBoxLikeElements.'(.*)>/Usi', $htmlArrayBefore) || '<!--' == substr(
270
+					$htmlArrayBefore,
271
+					0,
272
+					4
273
+				) || preg_match('/<'.$allBoxLikeElements.'(.*) \/>/Usi', $htmlArrayBefore))
274
+			) {
275
+				$newline = true;
276
+			}
277
+
278
+			// count down a tab
279
+			if ('</' == substr($htmlArrayCurrent, 0, 2)) {
280
+				--$tabs;
281
+			}
282
+
283
+			// add tabs and line breaks in front of the current tag
284
+			if ($newline) {
285
+				$html .= $this->newline;
286
+				for ($y = 0; $y < $tabs; ++$y) {
287
+					$html .= $this->tab;
288
+				}
289
+			}
290
+
291
+			// remove white spaces and line breaks and add current tag to the html-string
292
+			if ('<![CDATA[' == substr($htmlArrayCurrent, 0, 9) // remove multiple white space in CDATA / XML
293
+				|| '<?xml' == substr($htmlArrayCurrent, 0, 5)
294
+			) {
295
+				$html .= $this->killWhiteSpace($htmlArrayCurrent);
296
+			} else { // remove all line breaks
297
+				$html .= $this->killLineBreaks($htmlArrayCurrent);
298
+			}
299
+
300
+			// count up a tab
301
+			if ('<' == substr($htmlArrayCurrent, 0, 1) && '/' != substr($htmlArrayCurrent, 1, 1)) {
302
+				if (' ' !== substr($htmlArrayCurrent, 1, 1)
303
+					&& 'img' !== substr($htmlArrayCurrent, 1, 3)
304
+					&& 'source' !== substr($htmlArrayCurrent, 1, 6)
305
+					&& 'br' !== substr($htmlArrayCurrent, 1, 2)
306
+					&& 'hr' !== substr($htmlArrayCurrent, 1, 2)
307
+					&& 'input' !== substr($htmlArrayCurrent, 1, 5)
308
+					&& 'link' !== substr($htmlArrayCurrent, 1, 4)
309
+					&& 'meta' !== substr($htmlArrayCurrent, 1, 4)
310
+					&& 'col ' !== substr($htmlArrayCurrent, 1, 4)
311
+					&& 'frame' !== substr($htmlArrayCurrent, 1, 5)
312
+					&& 'isindex' !== substr($htmlArrayCurrent, 1, 7)
313
+					&& 'param' !== substr($htmlArrayCurrent, 1, 5)
314
+					&& 'area' !== substr($htmlArrayCurrent, 1, 4)
315
+					&& 'base' !== substr($htmlArrayCurrent, 1, 4)
316
+					&& '<!' !== substr($htmlArrayCurrent, 0, 2)
317
+					&& '<?xml' !== substr($htmlArrayCurrent, 0, 5)
318
+				) {
319
+					++$tabs;
320
+				}
321
+			}
322
+		}
323
+
324
+		// Remove empty lines
325
+		if ($this->formatType > 1) {
326
+			$this->removeEmptyLines($html);
327
+		}
328
+
329
+		// Restore saved comments, styles and scripts
330
+		for ($i = 0; $i < \count($noFormat); ++$i) {
331
+			$html = str_replace("<!-- ELEMENT {$i} -->", $noFormat[$i], $html);
332
+		}
333
+
334
+		// include debug comment at the end
335
+		if (0 != $tabs && true === $this->debugComment) {
336
+			$html .= "<!-- {$tabs} open elements found -->";
337
+		}
338
+
339
+		return $html;
340
+	}
341
+
342
+	/**
343
+	 * Remove ALL line breaks and multiple white space.
344
+	 *
345
+	 * @param string $html
346
+	 *
347
+	 * @return string
348
+	 */
349
+	protected function killLineBreaks($html)
350
+	{
351
+		$html = str_replace($this->newline, '', $html);
352
+
353
+		return preg_replace('/\s\s+/u', ' ', $html);
354
+		// ? return preg_replace('/\n|\s+(\s)/u', '$1', $html);
355
+	}
356
+
357
+	/**
358
+	 * Remove multiple white space, keeps line breaks.
359
+	 *
360
+	 * @param string $html
361
+	 *
362
+	 * @return string
363
+	 */
364
+	protected function killWhiteSpace($html)
365
+	{
366
+		$temp = explode($this->newline, $html);
367
+		for ($i = 0; $i < \count($temp); ++$i) {
368
+			if (!trim($temp[$i])) {
369
+				unset($temp[$i]);
370
+				continue;
371
+			}
372
+
373
+			$temp[$i] = trim($temp[$i]);
374
+			$temp[$i] = preg_replace('/\s\s+/', ' ', $temp[$i]);
375
+		}
376
+
377
+		return implode($this->newline, $temp);
378
+	}
379
+
380
+	/**
381
+	 * Remove white space at the end of lines, keeps other white space and line breaks.
382
+	 *
383
+	 * @param string $html
384
+	 *
385
+	 * @return string
386
+	 */
387
+	protected function rTrimLines(&$html)
388
+	{
389
+		$html = preg_replace('/\s+$/m', '', $html);
390
+	}
391
+
392
+	/**
393
+	 * Convert newlines according to the current OS.
394
+	 *
395
+	 * @param string $html
396
+	 *
397
+	 * @return string
398
+	 */
399
+	protected function convNlOs(&$html)
400
+	{
401
+		$html = preg_replace("(\r\n|\r)", $this->newline, $html);
402
+	}
403
+
404
+	/**
405
+	 * Remove empty lines.
406
+	 *
407
+	 * @param string $html
408
+	 */
409
+	protected function removeEmptyLines(&$html): void
410
+	{
411
+		$temp = explode($this->newline, $html);
412
+		$result = [];
413
+		for ($i = 0; $i < \count($temp); ++$i) {
414
+			if ('' == trim($temp[$i])) {
415
+				continue;
416
+			}
417
+			$result[] = $temp[$i];
418
+		}
419
+		$html = implode($this->newline, $result);
420
+	}
421
+
422
+	/**
423
+	 * Include configured header comment in HTML content block.
424
+	 */
425
+	public function includeHeaderComment(&$html): void
426
+	{
427
+		$html = preg_replace('/^(-->)$/m', "\n\t".$this->headerComment."\n$1", $html);
428
+	}
429 429
 }
Please login to merge, or discard this patch.
Classes/Service/SvgStoreService.php 2 patches
Indentation   +193 added lines, -193 removed lines patch added patch discarded remove patch
@@ -13,197 +13,197 @@
 block discarded – undo
13 13
  */
14 14
 class SvgStoreService implements \TYPO3\CMS\Core\SingletonInterface
15 15
 {
16
-    /**
17
-     * SVG-Sprite storage directory.
18
-     *
19
-     * @var string
20
-     */
21
-    protected $outputDir = '/typo3temp/assets/svg/';
22
-
23
-    public function __construct()
24
-    {
25
-        // $this->styl = []; # https://stackoverflow.com/questions/39583880/external-svg-fails-to-apply-internal-css
26
-        // $this->defs = []; # https://bugs.chromium.org/p/chromium/issues/detail?id=751733#c14
27
-        $this->svgs = [];
28
-
29
-        $this->sitePath = \TYPO3\CMS\Core\Core\Environment::getPublicPath(); // [^/]$
30
-        $this->svgCache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('svgstore');
31
-    }
32
-
33
-    public function process(string $html): string
34
-    {
35
-        $this->spritePath = $this->svgCache->get('spritePath') ?: '';
36
-        $this->svgFileArr = $this->svgCache->get('svgFileArr') ?: [];
37
-
38
-        if (empty($this->spritePath) && !$this->populateCache()) {
39
-            throw new \Exception('could not write file: '.$this->sitePath.$this->spritePath);
40
-        }
41
-
42
-        if (!file_exists($this->sitePath.$this->spritePath)) {
43
-            throw new \Exception('file does not exists: '.$this->sitePath.$this->spritePath);
44
-        }
45
-
46
-        if ($GLOBALS['TSFE']->config['config']['disableAllHeaderCode'] ?? false) {
47
-            $dom = ['head' => '', 'body' => $html];
48
-        } elseif (!preg_match('/(?<head>.+?<\/head>)(?<body>.+)/s', $html, $dom) && 5 == \count($dom)) {
49
-            return $html;
50
-        }
51
-
52
-        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes
53
-        $dom['body'] = preg_replace_callback('/<img(?<pre>[^>]*)src="(?<src>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?!\s*<\/picture>)/s', function (array $match): string { // ^[/]
54
-            if (!isset($this->svgFileArr[$match['src']])) { // check usage
55
-                return $match[0];
56
-            }
57
-            $attr = preg_replace('/\s(?:alt|ismap|loading|title|sizes|srcset|usemap|crossorigin|decoding|referrerpolicy)="[^"]*"/', '', $match['pre'].$match['post']); // cleanup
58
-
59
-            return sprintf('<svg %s %s><use href="%s#%s"/></svg>', $this->svgFileArr[$match['src']]['attr'], trim($attr), $this->spritePath, $this->convertFilePath($match['src']));
60
-        }, $dom['body']);
61
-
62
-        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#attributes
63
-        $dom['body'] = preg_replace_callback('/<object(?<pre>[^>]*)data="(?<data>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?:<\/object>)/s', function (array $match): string { // ^[/]
64
-            if (!isset($this->svgFileArr[$match['data']])) { // check usage
65
-                return $match[0];
66
-            }
67
-            $attr = preg_replace('/\s(?:form|name|type|usemap)="[^"]*"/', '', $match['pre'].$match['post']); // cleanup
68
-
69
-            return sprintf('<svg %s %s><use href="%s#%s"/></svg>', $this->svgFileArr[$match['data']]['attr'], trim($attr), $this->spritePath, $this->convertFilePath($match['data']));
70
-        }, $dom['body']);
71
-
72
-        return $dom['head'].$dom['body'];
73
-    }
74
-
75
-    private function convertFilePath(string $path): string
76
-    {
77
-        return preg_replace('/.svg$|[^\w\-]/', '', str_replace('/', '-', ltrim($path, '/'))); // ^[^/]
78
-    }
79
-
80
-    private function addFileToSpriteArr(string $hash, string $path): ?array
81
-    {
82
-        if (!file_exists($this->sitePath.$path)) {
83
-            return null;
84
-        }
85
-
86
-        if (preg_match('/(?:;base64|i:a?i?pgf)/', $svg = file_get_contents($this->sitePath.$path))) { // noop!
87
-            return null;
88
-        }
89
-
90
-        if (preg_match('/<(?:style|defs)|url\(/', $svg)) {
91
-            return null; // check links @ __construct
92
-        }
93
-
94
-        // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
95
-        $svg = preg_replace('/^.*?<svg|\s*(<\/svg>)(?!.*\1).*$|xlink:|\s(?:(?:version|xmlns)|(?:[a-z\-]+\:[a-z\-]+))="[^"]*"/s', '', $svg); // cleanup
96
-
97
-        // $svg = preg_replace('/(?<=(?:id|class)=")/', $hash.'__', $svg); // extend  IDs
98
-        // $svg = preg_replace('/(?<=href="|url\()#/', $hash.'__', $svg); // recover IDs
99
-
100
-        // $svg = preg_replace_callback('/<style[^>]*>(?<styl>.+?)<\/style>|<defs[^>]*>(?<defs>.+?)<\/defs>/s', function(array $match) use($hash): string {
101
-        //
102
-        //    if(isset($match['styl']))
103
-        //    {
104
-        //        $this->styl[] = preg_replace('/\s*(\.|#){1}(.+?)\s*\{/', '$1'.$hash.'__$2{', $match['styl']); // patch CSS # https://mathiasbynens.be/notes/css-escapes
105
-        //    }
106
-        //    if(isset($match['defs']))
107
-        //    {
108
-        //        $this->defs[] = trim($match['defs']);
109
-        //    }
110
-        //    return '';
111
-        // }, $svg);
112
-
113
-        // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg#attributes
114
-        $svg = preg_replace_callback('/([^>]*)\s*(?=>)/s', function (array $match) use (&$attr): string {
115
-            if (false === preg_match_all('/(?!\s)(?<attr>[\w\-]+)="\s*(?<value>[^"]+)\s*"/', $match[1], $matches)) {
116
-                return $match[0];
117
-            }
118
-            foreach ($matches['attr'] as $index => $attribute) {
119
-                switch ($attribute) {
120
-                    case 'id':
121
-                    case 'width':
122
-                    case 'height':
123
-                        unset($matches[0][$index]);
124
-                        break;
125
-
126
-                    case 'viewBox':
127
-                        if (false !== preg_match('/\S+\s\S+\s\+?(?<width>[\d\.]+)\s\+?(?<height>[\d\.]+)/', $matches['value'][$index], $match)) {
128
-                            $attr[] = sprintf('%s="0 0 %s %s"', $attribute, $match['width'], $match['height']); // save!
129
-                        }
130
-                }
131
-            }
132
-
133
-            return implode(' ', $matches[0]);
134
-        }, $svg, 1);
135
-
136
-        if ($attr) { // TODO; beautify
137
-            $this->svgs[] = sprintf('id="%s" %s', $this->convertFilePath($path), $svg); // prepend ID
138
-
139
-            return ['attr' => implode(' ', $attr), 'hash' => $hash];
140
-        }
141
-
142
-        return null;
143
-    }
144
-
145
-    private function populateCache(): bool
146
-    {
147
-        $storageArr = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class)->findAll();
148
-        foreach ($storageArr as $storage) {
149
-            if ('relative' == $storage->getConfiguration()['pathType']) {
150
-                $storageArr[$storage->getUid()] = rtrim($storage->getConfiguration()['basePath'], '/'); // [^/]$
151
-            }
152
-        }
153
-        unset($storageArr[0]); // keep!
154
-
155
-        $fileArr = GeneralUtility::makeInstance(\HTML\Sourceopt\Resource\SvgFileRepository::class)->findAllByStorageUids(array_keys($storageArr));
156
-        foreach ($fileArr as $file) {
157
-            $file['path'] = '/'.$storageArr[$file['storage']].$file['identifier']; // ^[/]
158
-            $file['defs'] = $this->addFileToSpriteArr($file['sha1'], $file['path']);
159
-
160
-            if (null !== $file['defs']) {
161
-                $this->svgFileArr[$file['path']] = $file['defs'];
162
-            }
163
-        }
164
-        unset($storageArr, $storage, $fileArr, $file); // save MEM
165
-
166
-        $svg = preg_replace_callback(
167
-            '/<use(?<pre>.*?)(?:xlink:)?href="(?<href>\/.+?\.svg)#[^"]+"(?<post>.*?)[\s\/]*>(?:<\/use>)?/s',
168
-            function (array $match): string {
169
-                if (!isset($this->svgFileArr[$match['href']])) { // check usage
170
-                    return $match[0];
171
-                }
172
-
173
-                return sprintf('<use%s href="#%s"/>', $match['pre'].$match['post'], $this->convertFilePath($match['href']));
174
-            },
175
-            '<svg xmlns="http://www.w3.org/2000/svg">'
176
-            // ."\n<style>\n".implode("\n", $this->styl)."\n</style>"
177
-            // ."\n<defs>\n".implode("\n", $this->defs)."\n</defs>"
178
-            ."\n<symbol ".implode("</symbol>\n<symbol ", $this->svgs)."</symbol>\n"
179
-            .'</svg>'
180
-        );
181
-
182
-        // unset($this->styl); // save MEM
183
-        // unset($this->defs); // save MEM
184
-        unset($this->svgs); // save MEM
185
-
186
-        if ($GLOBALS['TSFE']->config['config']['sourceopt.']['formatHtml'] ?? false) {
187
-            $svg = preg_replace('/(?<=>)\s+(?=<)/', '', $svg); // remove emptiness
188
-            $svg = preg_replace('/[\t\v]/', ' ', $svg); // prepare shrinkage
189
-            $svg = preg_replace('/\s{2,}/', ' ', $svg); // shrink whitespace
190
-        }
191
-
192
-        $svg = preg_replace('/<([a-z]+)\s*(\/|>\s*<\/\1)>\s*|\s+(?=\/>)/i', '', $svg); // remove emtpy TAGs & shorten endings
193
-        $svg = preg_replace('/<((circle|ellipse|line|path|polygon|polyline|rect|stop|use)\s[^>]+?)\s*>\s*<\/\2>/', '<$1/>', $svg); // shorten/minify TAG syntax
194
-
195
-        if (!is_dir($this->sitePath.$this->outputDir)) {
196
-            GeneralUtility::mkdir_deep($this->sitePath.$this->outputDir);
197
-        }
198
-
199
-        $this->spritePath = $this->outputDir.hash('sha1', serialize($this->svgFileArr)).'.svg';
200
-        if (false === file_put_contents($this->sitePath.$this->spritePath, $svg)) {
201
-            return false;
202
-        }
203
-
204
-        $this->svgCache->set('spritePath', $this->spritePath);
205
-        $this->svgCache->set('svgFileArr', $this->svgFileArr);
206
-
207
-        return true;
208
-    }
16
+	/**
17
+	 * SVG-Sprite storage directory.
18
+	 *
19
+	 * @var string
20
+	 */
21
+	protected $outputDir = '/typo3temp/assets/svg/';
22
+
23
+	public function __construct()
24
+	{
25
+		// $this->styl = []; # https://stackoverflow.com/questions/39583880/external-svg-fails-to-apply-internal-css
26
+		// $this->defs = []; # https://bugs.chromium.org/p/chromium/issues/detail?id=751733#c14
27
+		$this->svgs = [];
28
+
29
+		$this->sitePath = \TYPO3\CMS\Core\Core\Environment::getPublicPath(); // [^/]$
30
+		$this->svgCache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('svgstore');
31
+	}
32
+
33
+	public function process(string $html): string
34
+	{
35
+		$this->spritePath = $this->svgCache->get('spritePath') ?: '';
36
+		$this->svgFileArr = $this->svgCache->get('svgFileArr') ?: [];
37
+
38
+		if (empty($this->spritePath) && !$this->populateCache()) {
39
+			throw new \Exception('could not write file: '.$this->sitePath.$this->spritePath);
40
+		}
41
+
42
+		if (!file_exists($this->sitePath.$this->spritePath)) {
43
+			throw new \Exception('file does not exists: '.$this->sitePath.$this->spritePath);
44
+		}
45
+
46
+		if ($GLOBALS['TSFE']->config['config']['disableAllHeaderCode'] ?? false) {
47
+			$dom = ['head' => '', 'body' => $html];
48
+		} elseif (!preg_match('/(?<head>.+?<\/head>)(?<body>.+)/s', $html, $dom) && 5 == \count($dom)) {
49
+			return $html;
50
+		}
51
+
52
+		// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes
53
+		$dom['body'] = preg_replace_callback('/<img(?<pre>[^>]*)src="(?<src>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?!\s*<\/picture>)/s', function (array $match): string { // ^[/]
54
+			if (!isset($this->svgFileArr[$match['src']])) { // check usage
55
+				return $match[0];
56
+			}
57
+			$attr = preg_replace('/\s(?:alt|ismap|loading|title|sizes|srcset|usemap|crossorigin|decoding|referrerpolicy)="[^"]*"/', '', $match['pre'].$match['post']); // cleanup
58
+
59
+			return sprintf('<svg %s %s><use href="%s#%s"/></svg>', $this->svgFileArr[$match['src']]['attr'], trim($attr), $this->spritePath, $this->convertFilePath($match['src']));
60
+		}, $dom['body']);
61
+
62
+		// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#attributes
63
+		$dom['body'] = preg_replace_callback('/<object(?<pre>[^>]*)data="(?<data>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?:<\/object>)/s', function (array $match): string { // ^[/]
64
+			if (!isset($this->svgFileArr[$match['data']])) { // check usage
65
+				return $match[0];
66
+			}
67
+			$attr = preg_replace('/\s(?:form|name|type|usemap)="[^"]*"/', '', $match['pre'].$match['post']); // cleanup
68
+
69
+			return sprintf('<svg %s %s><use href="%s#%s"/></svg>', $this->svgFileArr[$match['data']]['attr'], trim($attr), $this->spritePath, $this->convertFilePath($match['data']));
70
+		}, $dom['body']);
71
+
72
+		return $dom['head'].$dom['body'];
73
+	}
74
+
75
+	private function convertFilePath(string $path): string
76
+	{
77
+		return preg_replace('/.svg$|[^\w\-]/', '', str_replace('/', '-', ltrim($path, '/'))); // ^[^/]
78
+	}
79
+
80
+	private function addFileToSpriteArr(string $hash, string $path): ?array
81
+	{
82
+		if (!file_exists($this->sitePath.$path)) {
83
+			return null;
84
+		}
85
+
86
+		if (preg_match('/(?:;base64|i:a?i?pgf)/', $svg = file_get_contents($this->sitePath.$path))) { // noop!
87
+			return null;
88
+		}
89
+
90
+		if (preg_match('/<(?:style|defs)|url\(/', $svg)) {
91
+			return null; // check links @ __construct
92
+		}
93
+
94
+		// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
95
+		$svg = preg_replace('/^.*?<svg|\s*(<\/svg>)(?!.*\1).*$|xlink:|\s(?:(?:version|xmlns)|(?:[a-z\-]+\:[a-z\-]+))="[^"]*"/s', '', $svg); // cleanup
96
+
97
+		// $svg = preg_replace('/(?<=(?:id|class)=")/', $hash.'__', $svg); // extend  IDs
98
+		// $svg = preg_replace('/(?<=href="|url\()#/', $hash.'__', $svg); // recover IDs
99
+
100
+		// $svg = preg_replace_callback('/<style[^>]*>(?<styl>.+?)<\/style>|<defs[^>]*>(?<defs>.+?)<\/defs>/s', function(array $match) use($hash): string {
101
+		//
102
+		//    if(isset($match['styl']))
103
+		//    {
104
+		//        $this->styl[] = preg_replace('/\s*(\.|#){1}(.+?)\s*\{/', '$1'.$hash.'__$2{', $match['styl']); // patch CSS # https://mathiasbynens.be/notes/css-escapes
105
+		//    }
106
+		//    if(isset($match['defs']))
107
+		//    {
108
+		//        $this->defs[] = trim($match['defs']);
109
+		//    }
110
+		//    return '';
111
+		// }, $svg);
112
+
113
+		// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg#attributes
114
+		$svg = preg_replace_callback('/([^>]*)\s*(?=>)/s', function (array $match) use (&$attr): string {
115
+			if (false === preg_match_all('/(?!\s)(?<attr>[\w\-]+)="\s*(?<value>[^"]+)\s*"/', $match[1], $matches)) {
116
+				return $match[0];
117
+			}
118
+			foreach ($matches['attr'] as $index => $attribute) {
119
+				switch ($attribute) {
120
+					case 'id':
121
+					case 'width':
122
+					case 'height':
123
+						unset($matches[0][$index]);
124
+						break;
125
+
126
+					case 'viewBox':
127
+						if (false !== preg_match('/\S+\s\S+\s\+?(?<width>[\d\.]+)\s\+?(?<height>[\d\.]+)/', $matches['value'][$index], $match)) {
128
+							$attr[] = sprintf('%s="0 0 %s %s"', $attribute, $match['width'], $match['height']); // save!
129
+						}
130
+				}
131
+			}
132
+
133
+			return implode(' ', $matches[0]);
134
+		}, $svg, 1);
135
+
136
+		if ($attr) { // TODO; beautify
137
+			$this->svgs[] = sprintf('id="%s" %s', $this->convertFilePath($path), $svg); // prepend ID
138
+
139
+			return ['attr' => implode(' ', $attr), 'hash' => $hash];
140
+		}
141
+
142
+		return null;
143
+	}
144
+
145
+	private function populateCache(): bool
146
+	{
147
+		$storageArr = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class)->findAll();
148
+		foreach ($storageArr as $storage) {
149
+			if ('relative' == $storage->getConfiguration()['pathType']) {
150
+				$storageArr[$storage->getUid()] = rtrim($storage->getConfiguration()['basePath'], '/'); // [^/]$
151
+			}
152
+		}
153
+		unset($storageArr[0]); // keep!
154
+
155
+		$fileArr = GeneralUtility::makeInstance(\HTML\Sourceopt\Resource\SvgFileRepository::class)->findAllByStorageUids(array_keys($storageArr));
156
+		foreach ($fileArr as $file) {
157
+			$file['path'] = '/'.$storageArr[$file['storage']].$file['identifier']; // ^[/]
158
+			$file['defs'] = $this->addFileToSpriteArr($file['sha1'], $file['path']);
159
+
160
+			if (null !== $file['defs']) {
161
+				$this->svgFileArr[$file['path']] = $file['defs'];
162
+			}
163
+		}
164
+		unset($storageArr, $storage, $fileArr, $file); // save MEM
165
+
166
+		$svg = preg_replace_callback(
167
+			'/<use(?<pre>.*?)(?:xlink:)?href="(?<href>\/.+?\.svg)#[^"]+"(?<post>.*?)[\s\/]*>(?:<\/use>)?/s',
168
+			function (array $match): string {
169
+				if (!isset($this->svgFileArr[$match['href']])) { // check usage
170
+					return $match[0];
171
+				}
172
+
173
+				return sprintf('<use%s href="#%s"/>', $match['pre'].$match['post'], $this->convertFilePath($match['href']));
174
+			},
175
+			'<svg xmlns="http://www.w3.org/2000/svg">'
176
+			// ."\n<style>\n".implode("\n", $this->styl)."\n</style>"
177
+			// ."\n<defs>\n".implode("\n", $this->defs)."\n</defs>"
178
+			."\n<symbol ".implode("</symbol>\n<symbol ", $this->svgs)."</symbol>\n"
179
+			.'</svg>'
180
+		);
181
+
182
+		// unset($this->styl); // save MEM
183
+		// unset($this->defs); // save MEM
184
+		unset($this->svgs); // save MEM
185
+
186
+		if ($GLOBALS['TSFE']->config['config']['sourceopt.']['formatHtml'] ?? false) {
187
+			$svg = preg_replace('/(?<=>)\s+(?=<)/', '', $svg); // remove emptiness
188
+			$svg = preg_replace('/[\t\v]/', ' ', $svg); // prepare shrinkage
189
+			$svg = preg_replace('/\s{2,}/', ' ', $svg); // shrink whitespace
190
+		}
191
+
192
+		$svg = preg_replace('/<([a-z]+)\s*(\/|>\s*<\/\1)>\s*|\s+(?=\/>)/i', '', $svg); // remove emtpy TAGs & shorten endings
193
+		$svg = preg_replace('/<((circle|ellipse|line|path|polygon|polyline|rect|stop|use)\s[^>]+?)\s*>\s*<\/\2>/', '<$1/>', $svg); // shorten/minify TAG syntax
194
+
195
+		if (!is_dir($this->sitePath.$this->outputDir)) {
196
+			GeneralUtility::mkdir_deep($this->sitePath.$this->outputDir);
197
+		}
198
+
199
+		$this->spritePath = $this->outputDir.hash('sha1', serialize($this->svgFileArr)).'.svg';
200
+		if (false === file_put_contents($this->sitePath.$this->spritePath, $svg)) {
201
+			return false;
202
+		}
203
+
204
+		$this->svgCache->set('spritePath', $this->spritePath);
205
+		$this->svgCache->set('svgFileArr', $this->svgFileArr);
206
+
207
+		return true;
208
+	}
209 209
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -50,7 +50,7 @@  discard block
 block discarded – undo
50 50
         }
51 51
 
52 52
         // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes
53
-        $dom['body'] = preg_replace_callback('/<img(?<pre>[^>]*)src="(?<src>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?!\s*<\/picture>)/s', function (array $match): string { // ^[/]
53
+        $dom['body'] = preg_replace_callback('/<img(?<pre>[^>]*)src="(?<src>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?!\s*<\/picture>)/s', function(array $match): string { // ^[/]
54 54
             if (!isset($this->svgFileArr[$match['src']])) { // check usage
55 55
                 return $match[0];
56 56
             }
@@ -60,7 +60,7 @@  discard block
 block discarded – undo
60 60
         }, $dom['body']);
61 61
 
62 62
         // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object#attributes
63
-        $dom['body'] = preg_replace_callback('/<object(?<pre>[^>]*)data="(?<data>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?:<\/object>)/s', function (array $match): string { // ^[/]
63
+        $dom['body'] = preg_replace_callback('/<object(?<pre>[^>]*)data="(?<data>\/[^"]+\.svg)"(?<post>[^>]*?)[\s\/]*>(?:<\/object>)/s', function(array $match): string { // ^[/]
64 64
             if (!isset($this->svgFileArr[$match['data']])) { // check usage
65 65
                 return $match[0];
66 66
             }
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
         // }, $svg);
112 112
 
113 113
         // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg#attributes
114
-        $svg = preg_replace_callback('/([^>]*)\s*(?=>)/s', function (array $match) use (&$attr): string {
114
+        $svg = preg_replace_callback('/([^>]*)\s*(?=>)/s', function(array $match) use (&$attr): string {
115 115
             if (false === preg_match_all('/(?!\s)(?<attr>[\w\-]+)="\s*(?<value>[^"]+)\s*"/', $match[1], $matches)) {
116 116
                 return $match[0];
117 117
             }
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
 
166 166
         $svg = preg_replace_callback(
167 167
             '/<use(?<pre>.*?)(?:xlink:)?href="(?<href>\/.+?\.svg)#[^"]+"(?<post>.*?)[\s\/]*>(?:<\/use>)?/s',
168
-            function (array $match): string {
168
+            function(array $match): string {
169 169
                 if (!isset($this->svgFileArr[$match['href']])) { // check usage
170 170
                     return $match[0];
171 171
                 }
Please login to merge, or discard this patch.
Classes/Resource/SvgFileRepository.php 1 patch
Indentation   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -13,43 +13,43 @@
 block discarded – undo
13 13
  */
14 14
 class SvgFileRepository extends \TYPO3\CMS\Core\Resource\FileRepository
15 15
 {
16
-    /**
17
-     * Retrieves all used SVGs within given storage-array.
18
-     */
19
-    public function findAllByStorageUids(array $storageUids): array
20
-    {
21
-        return
22
-            ($queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)->getQueryBuilderForTable($this->table))
23
-                ->select('sys_file.storage', 'sys_file.identifier', 'sys_file.sha1')
24
-                ->from($this->table)
25
-                ->innerJoin(
26
-                    'sys_file',
27
-                    'sys_file_reference',
28
-                    'sys_file_reference',
29
-                    $queryBuilder->expr()->eq(
30
-                        'sys_file_reference.uid_local',
31
-                        $queryBuilder->quoteIdentifier('sys_file.uid')
32
-                    )
33
-                )
34
-                ->where(
35
-                    $queryBuilder->expr()->in(
36
-                        'sys_file.storage',
37
-                        $queryBuilder->createNamedParameter($storageUids, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
38
-                    ),
39
-                    $queryBuilder->expr()->lt(
40
-                        'sys_file.size',
41
-                        $queryBuilder->createNamedParameter((int) $GLOBALS['TSFE']->config['config']['svgstore.']['fileSize'], \PDO::PARAM_INT)
42
-                    ),
43
-                    $queryBuilder->expr()->eq(
44
-                        'sys_file.mime_type',
45
-                        $queryBuilder->createNamedParameter('image/svg+xml', \PDO::PARAM_STR)
46
-                    )
47
-                )
48
-                ->groupBy('sys_file.uid', 'sys_file.storage', 'sys_file.identifier', 'sys_file.sha1')
49
-                ->orderBy('sys_file.storage')
50
-                ->addOrderBy('sys_file.identifier')
51
-                ->execute()
52
-                ->fetchAll() // TODO; use stdClass
53
-        ;
54
-    }
16
+	/**
17
+	 * Retrieves all used SVGs within given storage-array.
18
+	 */
19
+	public function findAllByStorageUids(array $storageUids): array
20
+	{
21
+		return
22
+			($queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)->getQueryBuilderForTable($this->table))
23
+				->select('sys_file.storage', 'sys_file.identifier', 'sys_file.sha1')
24
+				->from($this->table)
25
+				->innerJoin(
26
+					'sys_file',
27
+					'sys_file_reference',
28
+					'sys_file_reference',
29
+					$queryBuilder->expr()->eq(
30
+						'sys_file_reference.uid_local',
31
+						$queryBuilder->quoteIdentifier('sys_file.uid')
32
+					)
33
+				)
34
+				->where(
35
+					$queryBuilder->expr()->in(
36
+						'sys_file.storage',
37
+						$queryBuilder->createNamedParameter($storageUids, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
38
+					),
39
+					$queryBuilder->expr()->lt(
40
+						'sys_file.size',
41
+						$queryBuilder->createNamedParameter((int) $GLOBALS['TSFE']->config['config']['svgstore.']['fileSize'], \PDO::PARAM_INT)
42
+					),
43
+					$queryBuilder->expr()->eq(
44
+						'sys_file.mime_type',
45
+						$queryBuilder->createNamedParameter('image/svg+xml', \PDO::PARAM_STR)
46
+					)
47
+				)
48
+				->groupBy('sys_file.uid', 'sys_file.storage', 'sys_file.identifier', 'sys_file.sha1')
49
+				->orderBy('sys_file.storage')
50
+				->addOrderBy('sys_file.identifier')
51
+				->execute()
52
+				->fetchAll() // TODO; use stdClass
53
+		;
54
+	}
55 55
 }
Please login to merge, or discard this patch.