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