├── src
├── registration.php
├── etc
│ ├── module.xml
│ └── adminhtml
│ │ └── di.xml
├── Plugin
│ └── AllowSvgInWysiwyg.php
└── Model
│ └── SvgElements.php
├── composer.json
├── README.md
└── LICENSE.txt
/src/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/etc/adminhtml/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hyva-themes/magento2-wysiwyg-svg",
3 | "description": "Allow SVGs and all tailwind classes in CMS block and page content.",
4 | "require": {
5 | "php": "~7.4.0||~8.1.0||~8.2.0||~8.3.0||~8.4.0"
6 | },
7 | "type": "magento2-module",
8 | "license": [
9 | "BSD-3-Clause"
10 | ],
11 | "authors": [{
12 | "name": "Hyvä Themes B.V.",
13 | "email": "info@hyva.io"
14 | }],
15 | "autoload": {
16 | "files": [
17 | "src/registration.php"
18 | ],
19 | "psr-4": {
20 | "Hyva\\WysiwygSvg\\": "src/"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hyvä Themes - SVG support for the Magento CMS Wysiwyg Editor
2 |
3 | [](https://hyva.io/)
4 |
5 | Allow SVG images to be used in CMS blocks and pages via the TinyMCE Wysiwyg Editor.
6 |
7 | ## hyva-themes/magento2-wysiwyg-svg
8 |
9 | ![Supported Magento Versions][ico-compatibility]
10 |
11 | Compatible with Magento 2.4.0 and higher.
12 |
13 | ## Installation
14 |
15 | 1. Install via composer
16 | ```
17 | composer require hyva-themes/magento2-wysiwyg-svg
18 | ```
19 | 2. Enable module
20 | ```
21 | bin/magento setup:upgrade
22 | ```
23 |
24 | ## Usage
25 |
26 | No configuration is required or available.
27 |
28 | To use, switch a CMS Wysiwyg Editor to the HTML view and past the SVG where you want it.
29 | Then switch back to the regular view and save the CMS block or page.
30 |
31 | Note: this module can be used with any Magento theme - there is no hard requirement to use a Hyvä frontend theme.
32 |
33 | ## Credits
34 |
35 | Thanks to Pekka M. for the help figuring out how to customize the editor.
36 |
37 | ## License
38 |
39 | Hyvä Themes - https://hyva.io
40 |
41 | Copyright © Hyvä Themes B.V 2020-present. All rights reserved.
42 |
43 | The BSD-3-Clause License. Please see [License File](LICENSE.txt) for more information.
44 |
45 | [ico-compatibility]: https://img.shields.io/badge/magento-%202.4-brightgreen.svg?logo=magento&longCache=true&style=flat-square
46 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, Hyvä Themes B.V.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/src/Plugin/AllowSvgInWysiwyg.php:
--------------------------------------------------------------------------------
1 | svgElements = $svgElements;
25 | }
26 |
27 | public function afterGetConfig(Config $subject, DataObject $result): DataObject
28 | {
29 | $settings = $result->getData('settings');
30 |
31 | $extendedSettings = isset($settings['extended_valid_elements'])
32 | ? $settings['extended_valid_elements'] . ','
33 | : '';
34 |
35 | // documented setting, but doesn't work on it's own - SVG's are still removed...
36 | $settings['extended_valid_elements'] = $extendedSettings . implode(',', $this->buildExtendedValidElements());
37 |
38 | // non documented setting, but it works with tinymce4 and tinymce5
39 | // @see https://stackoverflow.com/a/48884025
40 | $settings['non_empty_elements'] =
41 | 'td,th,iframe,video,audio,object,script,i,em,span,area,base,basefont,br,' . // default list
42 | 'col,embed,frame,hr,img,input,isindex,link,meta,param,,source,wbr,track,' . // also default list
43 | implode(',', keys($this->svgElements->getSvgElements()));
44 |
45 | $result->setData('settings', $settings);
46 |
47 | return $result;
48 | }
49 |
50 | private function buildExtendedValidElements(): array
51 | {
52 | foreach ($this->svgElements->getSvgElements() as $element => $attributes) {
53 | // Ignore the attributes - too often deprecated attributes are used, and they make the string grow large.
54 | $extend[] = $element . '[*]';
55 | //$extend[] = $element . '[' . ($attributes === [] ? '*' : implode('|', $attributes)) . ']';
56 | }
57 | return $extend;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Model/SvgElements.php:
--------------------------------------------------------------------------------
1 | [
41 | 'id',
42 | 'lang',
43 | 'tabindex',
44 | 'xml:base',
45 | 'xml:lang',
46 | 'xml:space',
47 | ],
48 | self::STYLE => ['class', 'style'],
49 | self::PRESENTATION => [
50 | 'alignment-baseline',
51 | 'baseline-shift',
52 | 'clip',
53 | 'clip-path',
54 | 'clip-rule',
55 | 'color',
56 | 'color-interpolation',
57 | 'color-interpolation-filters',
58 | 'color-profile',
59 | 'color-rendering',
60 | 'cursor',
61 | 'direction',
62 | 'display',
63 | 'dominant-baseline',
64 | 'enable-background',
65 | 'fill',
66 | 'fill-opacity',
67 | 'fill-rule',
68 | 'filter',
69 | 'flood-color',
70 | 'flood-opacity',
71 | 'font-family',
72 | 'font-size',
73 | 'font-size-adjust',
74 | 'font-stretch',
75 | 'font-style',
76 | 'font-variant',
77 | 'font-weight',
78 | 'glyph-orientation-horizontal',
79 | 'glyph-orientation-vertical',
80 | 'image-rendering',
81 | 'kerning',
82 | 'letter-spacing',
83 | 'lighting-color',
84 | 'marker-end',
85 | 'marker-mid',
86 | 'marker-start',
87 | 'mask',
88 | 'opacity',
89 | 'overflow',
90 | 'pointer-events',
91 | 'shape-rendering',
92 | 'stop-color',
93 | 'stop-opacity',
94 | 'stroke',
95 | 'stroke-dasharray',
96 | 'stroke-dashoffset',
97 | 'stroke-linecap',
98 | 'stroke-linejoin',
99 | 'stroke-miterlimit',
100 | 'stroke-opacity',
101 | 'stroke-width',
102 | 'text-anchor',
103 | 'text-decoration',
104 | 'text-rendering',
105 | 'transform',
106 | 'transform-origin',
107 | 'unicode-bidi',
108 | 'vector-effect',
109 | 'visibility',
110 | 'word-spacing',
111 | 'writing-mode',
112 | ],
113 | self::CONDITIONAL_PROCESSING => [
114 | 'requiredExtensions',
115 | 'requiredFeatures',
116 | 'systemLanguage',
117 | ],
118 | self::FILTER_PRIMITIVE => [
119 | 'height',
120 | 'result',
121 | 'width',
122 | 'x',
123 | 'y',
124 | ],
125 | self::TRANSFER_FUNCTION => [
126 | 'type',
127 | 'tableValues',
128 | 'slope',
129 | 'intercept',
130 | 'amplitude',
131 | 'exponent',
132 | 'offset',
133 | ],
134 | self::ANIMATION_TARGET_ELEMENT => ['href'],
135 | self::ANIMATION_ATTRIBUTE_TARGET => ['attributeType', 'attributeName'],
136 | self::ANIMATION_TIMING => [
137 | 'begin',
138 | 'dur',
139 | 'end',
140 | 'min',
141 | 'max',
142 | 'restart',
143 | 'repeatCount',
144 | 'repeatDur',
145 | 'fill',
146 | ],
147 | self::ANIMATION_VALUE => [
148 | 'calcMode',
149 | 'values',
150 | 'keyTimes',
151 | 'keySplines',
152 | 'from',
153 | 'to',
154 | 'by',
155 | 'autoReverse',
156 | 'accelerate',
157 | 'decelerate',
158 | ],
159 | self::ANIMATION_ADDITION => ['additive', 'accumulate'],
160 | self::ANIMATION_EVENT => ['onbegin', 'onend', 'onrepeat'],
161 | self::DOCUMENT_EVENT => ['onabort', 'onerror', 'onresize', 'onscroll', 'onunload'],
162 | self::DOCUMENT_ELEMENT_EVENT => ['oncopy', 'oncut', 'onpaste'],
163 | self::GLOBAL_EVENT => [
164 | 'oncancel',
165 | 'oncanplay',
166 | 'oncanplaythrough',
167 | 'onchange',
168 | 'onclick',
169 | 'onclose',
170 | 'oncuechange',
171 | 'ondblclick',
172 | 'ondrag',
173 | 'ondragend',
174 | 'ondragenter',
175 | 'ondragexit',
176 | 'ondragleave',
177 | 'ondragover',
178 | 'ondragstart',
179 | 'ondrop',
180 | 'ondurationchange',
181 | 'onemptied',
182 | 'onended',
183 | 'onerror',
184 | 'onfocus',
185 | 'oninput',
186 | 'oninvalid',
187 | 'onkeydown',
188 | 'onkeypress',
189 | 'onkeyup',
190 | 'onload',
191 | 'onloadeddata',
192 | 'onloadedmetadata',
193 | 'onloadstart',
194 | 'onmousedown',
195 | 'onmouseenter',
196 | 'onmouseleave',
197 | 'onmousemove',
198 | 'onmouseout',
199 | 'onmouseover',
200 | 'onmouseup',
201 | 'onmousewheel',
202 | 'onpause',
203 | 'onplay',
204 | 'onplaying',
205 | 'onprogress',
206 | 'onratechange',
207 | 'onreset',
208 | 'onresize',
209 | 'onscroll',
210 | 'onseeked',
211 | 'onseeking',
212 | 'onselect',
213 | 'onshow',
214 | 'onstalled',
215 | 'onsubmit',
216 | 'onsuspend',
217 | 'ontimeupdate',
218 | 'ontoggle',
219 | 'onvolumechange',
220 | 'onwaiting',
221 | ],
222 | self::GRAPHICAL_EVENT => ['onactivate', 'onfocusin', 'onfocusout'],
223 | self::ARIA => [
224 | 'aria-activedescendant',
225 | 'aria-atomic',
226 | 'aria-autocomplete',
227 | 'aria-busy',
228 | 'aria-checked',
229 | 'aria-colcount',
230 | 'aria-colindex',
231 | 'aria-colspan',
232 | 'aria-controls',
233 | 'aria-current',
234 | 'aria-describedby',
235 | 'aria-details',
236 | 'aria-disabled',
237 | 'aria-dropeffect',
238 | 'aria-errormessage',
239 | 'aria-expanded',
240 | 'aria-flowto',
241 | 'aria-grabbed',
242 | 'aria-haspopup',
243 | 'aria-hidden',
244 | 'aria-invalid',
245 | 'aria-keyshortcuts',
246 | 'aria-label',
247 | 'aria-labelledby',
248 | 'aria-level',
249 | 'aria-live',
250 | 'aria-modal',
251 | 'aria-multiline',
252 | 'aria-multiselectable',
253 | 'aria-orientation',
254 | 'aria-owns',
255 | 'aria-placeholder',
256 | 'aria-posinset',
257 | 'aria-pressed',
258 | 'aria-readonly',
259 | 'aria-relevant',
260 | 'aria-required',
261 | 'aria-roledescription',
262 | 'aria-rowcount',
263 | 'aria-rowindex',
264 | 'aria-rowspan',
265 | 'aria-selected',
266 | 'aria-setsize',
267 | 'aria-sort',
268 | 'aria-valuemax',
269 | 'aria-valuemin',
270 | 'aria-valuenow',
271 | 'aria-valuetext',
272 | 'role',
273 | ],
274 | ];
275 |
276 | /**
277 | * Return map of SVG elements to array of attributes.
278 | *
279 | * @return string[][]
280 | */
281 | public function getSvgElements(): array
282 | {
283 | $attributeGroups = function (...$groups): array {
284 | return unique(merge(...map(function ($group): array {
285 | return is_array($group)
286 | ? $group
287 | : (self::ATTRIBUTE_GROUPS[$group] ?? []);
288 | }, $groups)));
289 | };
290 |
291 | return [
292 | 'svg' => $attributeGroups(
293 | ['xmlns', 'height', 'preserveAspectRatio', 'viewBox', 'width', 'x', 'y'],
294 | self::CONDITIONAL_PROCESSING,
295 | self::STYLE,
296 | self::CONDITIONAL_PROCESSING,
297 | self::GLOBAL_EVENT,
298 | self::GRAPHICAL_EVENT,
299 | self::DOCUMENT_EVENT,
300 | self::DOCUMENT_ELEMENT_EVENT,
301 | self::PRESENTATION,
302 | self::ARIA
303 | ),
304 | 'a' => $attributeGroups(
305 | ['href', 'hreflang', 'target', 'type'],
306 | self::CORE,
307 | self::STYLE,
308 | self::CONDITIONAL_PROCESSING,
309 | self::GLOBAL_EVENT,
310 | self::DOCUMENT_ELEMENT_EVENT,
311 | self::GRAPHICAL_EVENT,
312 | self::PRESENTATION,
313 | self::ARIA
314 | ),
315 | //'altGlyph', // deprecated
316 | //'altGlyphDef', // deprecated
317 | //'altGlyphItem', // deprecated
318 | 'animate' => $attributeGroups(
319 | self::ANIMATION_TIMING,
320 | self::ANIMATION_VALUE,
321 | self::ANIMATION_ADDITION,
322 | self::ANIMATION_ATTRIBUTE_TARGET,
323 | self::CORE,
324 | self::STYLE,
325 | self::GLOBAL_EVENT,
326 | self::DOCUMENT_ELEMENT_EVENT
327 | ),
328 | 'animateMotion' => $attributeGroups(
329 | ['keyPoints', 'path', 'rotate'],
330 | self::ANIMATION_TIMING,
331 | self::ANIMATION_VALUE,
332 | self::ANIMATION_ADDITION,
333 | self::ANIMATION_ATTRIBUTE_TARGET,
334 | self::ANIMATION_EVENT,
335 | self::CORE,
336 | self::STYLE,
337 | self::GLOBAL_EVENT,
338 | self::DOCUMENT_ELEMENT_EVENT
339 | ),
340 | 'animateTransform' => $attributeGroups(
341 | ['by', 'from', 'to', 'type'],
342 | self::CONDITIONAL_PROCESSING,
343 | self::CORE,
344 | self::ANIMATION_EVENT,
345 | self::ANIMATION_ATTRIBUTE_TARGET,
346 | self::ANIMATION_TIMING,
347 | self::ANIMATION_VALUE,
348 | self::ANIMATION_ADDITION
349 | ),
350 | 'circle' => $attributeGroups(
351 | ['cx', 'cy', 'r', 'pathLength'],
352 | self::CORE,
353 | self::STYLE,
354 | self::CONDITIONAL_PROCESSING,
355 | self::GLOBAL_EVENT,
356 | self::GRAPHICAL_EVENT,
357 | self::PRESENTATION,
358 | self::ARIA
359 | ),
360 | 'clipPath' => $attributeGroups(
361 | ['clipPathUnits'],
362 | self::CORE,
363 | self::STYLE,
364 | self::CONDITIONAL_PROCESSING,
365 | self::PRESENTATION
366 | ),
367 | //'color-profile', // deprecated
368 | //'cursor', // deprecated
369 | 'defs' => $attributeGroups(
370 | self::CORE,
371 | self::STYLE,
372 | self::GLOBAL_EVENT,
373 | self::DOCUMENT_ELEMENT_EVENT,
374 | self::GRAPHICAL_EVENT,
375 | self::PRESENTATION
376 | ),
377 | 'desc' => $attributeGroups(
378 | self::CORE,
379 | self::STYLE,
380 | self::GLOBAL_EVENT,
381 | self::DOCUMENT_ELEMENT_EVENT
382 | ),
383 | 'ellipse' => $attributeGroups(
384 | ['cx', 'cy', 'rx', 'ry', 'pathLength'],
385 | self::CORE,
386 | self::STYLE,
387 | self::CONDITIONAL_PROCESSING,
388 | self::GLOBAL_EVENT,
389 | self::GRAPHICAL_EVENT,
390 | self::PRESENTATION,
391 | self::ARIA
392 | ),
393 | 'feBlend' => $attributeGroups(
394 | ['in', 'in2', 'mode'],
395 | self::CORE,
396 | self::PRESENTATION,
397 | self::FILTER_PRIMITIVE,
398 | self::STYLE
399 | ),
400 | 'feColorMatrix' => $attributeGroups(
401 | ['in', 'type', 'values'],
402 | self::CORE,
403 | self::PRESENTATION,
404 | self::FILTER_PRIMITIVE,
405 | self::STYLE
406 | ),
407 | 'feComponentTransfer' => $attributeGroups(
408 | ['in'],
409 | self::CORE,
410 | self::PRESENTATION,
411 | self::FILTER_PRIMITIVE,
412 | self::STYLE
413 | ),
414 | 'feComposite' => $attributeGroups(
415 | ['in', 'in2', 'operator', 'k1', 'k2', 'k3', 'k4'],
416 | self::CORE,
417 | self::PRESENTATION,
418 | self::FILTER_PRIMITIVE,
419 | self::STYLE
420 | ),
421 | 'feConvolveMatrix' => $attributeGroups(
422 | [
423 | 'in',
424 | 'order',
425 | 'kernelMatrix',
426 | 'divisor',
427 | 'bias',
428 | 'targetX',
429 | 'targetY',
430 | 'edgeMode',
431 | 'kernelUnitLength',
432 | 'preserveAlpha',
433 | ],
434 | self::CORE,
435 | self::PRESENTATION,
436 | self::FILTER_PRIMITIVE,
437 | self::STYLE
438 | ),
439 | 'feDiffuseLighting' => $attributeGroups(
440 | ['in', 'surfaceScale', 'diffuseConstant', 'kernelUnitLength'],
441 | self::CORE,
442 | self::PRESENTATION,
443 | self::FILTER_PRIMITIVE,
444 | self::STYLE
445 | ),
446 | 'feDisplacementMap' => $attributeGroups(
447 | ['in', 'in2', 'scale', 'xChannelSelector', 'yChannelSelector'],
448 | self::CORE,
449 | self::PRESENTATION,
450 | self::FILTER_PRIMITIVE,
451 | self::STYLE
452 | ),
453 | 'feDistantLight' => $attributeGroups(
454 | ['azimuth', 'elevation'],
455 | self::CORE
456 | ),
457 | 'feFlood' => $attributeGroups(
458 | ['flood-color', 'flood-opacity'],
459 | self::CORE,
460 | self::PRESENTATION,
461 | self::FILTER_PRIMITIVE,
462 | self::STYLE
463 | ),
464 | 'feFuncA' => $attributeGroups(
465 | self::CORE,
466 | self::TRANSFER_FUNCTION
467 | ),
468 | 'feFuncB' => $attributeGroups(
469 | self::CORE,
470 | self::TRANSFER_FUNCTION
471 | ),
472 | 'feFuncG' => $attributeGroups(
473 | self::CORE,
474 | self::TRANSFER_FUNCTION
475 | ),
476 | 'feFuncR' => $attributeGroups(
477 | self::CORE,
478 | self::TRANSFER_FUNCTION
479 | ),
480 | 'feGaussianBlur' => $attributeGroups(
481 | ['in', 'stdDeviation', 'edgeMode'],
482 | self::CORE,
483 | self::PRESENTATION,
484 | self::FILTER_PRIMITIVE,
485 | self::STYLE
486 | ),
487 | 'feImage' => $attributeGroups(
488 | ['preserveAspectRatio'],
489 | self::CORE,
490 | self::PRESENTATION,
491 | self::FILTER_PRIMITIVE,
492 | self::STYLE
493 | ),
494 | 'feMerge' => $attributeGroups(
495 | self::CORE,
496 | self::PRESENTATION,
497 | self::FILTER_PRIMITIVE,
498 | self::STYLE
499 | ),
500 | 'feMergeNode' => $attributeGroups(
501 | ['in'],
502 | self::CORE
503 | ),
504 | 'feMorphology' => $attributeGroups(
505 | ['in', 'operator', 'radius'],
506 | self::CORE,
507 | self::PRESENTATION,
508 | self::FILTER_PRIMITIVE,
509 | self::STYLE
510 | ),
511 | 'feOffset' => $attributeGroups(
512 | ['in', 'dx', 'dy'],
513 | self::CORE,
514 | self::PRESENTATION,
515 | self::FILTER_PRIMITIVE,
516 | self::STYLE
517 | ),
518 | 'fePointLight' => $attributeGroups(
519 | ['x', 'y', 'z'],
520 | self::CORE
521 | ),
522 | 'feSpecularLighting' => $attributeGroups(
523 | ['in', 'surfaceScale', 'specularConstant', 'specularExponent', 'kernelUnitLength'],
524 | self::CORE,
525 | self::PRESENTATION,
526 | self::FILTER_PRIMITIVE,
527 | self::STYLE
528 | ),
529 | 'feSpotLight' => $attributeGroups(
530 | ['x', 'y', 'z', 'pointsAtX', 'pointsAtY', 'pointsAtZ', 'specularExponent', 'limitingConeAngle'],
531 | self::CORE
532 | ),
533 | 'feTile' => $attributeGroups(
534 | ['in'],
535 | self::CORE,
536 | self::PRESENTATION,
537 | self::FILTER_PRIMITIVE,
538 | self::STYLE
539 | ),
540 | 'feTurbulence' => $attributeGroups(
541 | ['baseFrequency', 'numOctaves', 'seed', 'stitchTiles', 'type'],
542 | self::CORE,
543 | self::PRESENTATION,
544 | self::FILTER_PRIMITIVE,
545 | self::STYLE
546 | ),
547 | 'filter' => $attributeGroups(
548 | ['x', 'y', 'width', 'height', 'filterRes', 'filterUnits', 'primitiveUnits'],
549 | self::CORE,
550 | self::PRESENTATION,
551 | self::STYLE
552 | ),
553 | //'font', // deprecated
554 | //'font-face', // deprecated
555 | //'font-face-format', // deprecated
556 | //'font-face-name', // deprecated
557 | //'font-face-src', // deprecated
558 | //'font-face-uri', // deprecated
559 | 'foreignObject' => $attributeGroups(
560 | ['height', 'width', 'x', 'y'],
561 | self::CORE,
562 | self::STYLE,
563 | self::CONDITIONAL_PROCESSING,
564 | self::GLOBAL_EVENT,
565 | self::GRAPHICAL_EVENT,
566 | self::DOCUMENT_EVENT,
567 | self::DOCUMENT_ELEMENT_EVENT,
568 | self::PRESENTATION,
569 | self::ARIA
570 | ),
571 | 'g' => $attributeGroups(
572 | self::CORE,
573 | self::STYLE,
574 | self::CONDITIONAL_PROCESSING,
575 | self::GLOBAL_EVENT,
576 | self::GRAPHICAL_EVENT,
577 | self::PRESENTATION,
578 | self::ARIA
579 | ),
580 | //'glyph', // deprecated
581 | //'glyphRef', // deprecated
582 | //'hkern', // deprecated
583 | 'image' => $attributeGroups(
584 | ['x', 'y', 'width', 'height', 'href', 'preserveAspectRatio', 'crossorigin'],
585 | self::CORE,
586 | self::CONDITIONAL_PROCESSING,
587 | self::GRAPHICAL_EVENT,
588 | self::PRESENTATION,
589 | self::STYLE
590 | ),
591 | 'line' => $attributeGroups(
592 | ['x1', 'x2', 'y1', 'y2', 'pathLength'],
593 | self::CORE,
594 | self::STYLE,
595 | self::CONDITIONAL_PROCESSING,
596 | self::GLOBAL_EVENT,
597 | self::GRAPHICAL_EVENT,
598 | self::PRESENTATION,
599 | self::ARIA
600 | ),
601 | 'linearGradient' => $attributeGroups(
602 | ['gradientUnits', 'gradientTransform', 'href', 'spreadMethod', 'x1', 'x2', 'y1', 'y2'],
603 | self::CORE,
604 | self::STYLE,
605 | self::GLOBAL_EVENT,
606 | self::DOCUMENT_ELEMENT_EVENT,
607 | self::PRESENTATION
608 | ),
609 | 'marker' => $attributeGroups(
610 | [
611 | 'markerHeight',
612 | 'markerUnits',
613 | 'markerWidth',
614 | 'orient',
615 | 'preserveAspectRatio',
616 | 'refX',
617 | 'refY',
618 | 'viewBox',
619 | ],
620 | self::CORE,
621 | self::STYLE,
622 | self::CONDITIONAL_PROCESSING,
623 | self::PRESENTATION,
624 | self::ARIA
625 | ),
626 | 'mask' => $attributeGroups(
627 | ['heigth', 'maskContentUnits', 'maskUnits', 'x', 'y', 'width'],
628 | self::CORE,
629 | self::STYLE,
630 | self::CONDITIONAL_PROCESSING,
631 | self::PRESENTATION
632 | ),
633 | 'metadata' => $attributeGroups(
634 | self::CORE,
635 | self::GLOBAL_EVENT
636 | ),
637 | //'missing-glyph', // deprecated
638 | 'mpath' => $attributeGroups(
639 | self::CORE
640 | ),
641 | 'path' => $attributeGroups(
642 | ['d', 'pathLength'],
643 | self::CORE,
644 | self::STYLE,
645 | self::CONDITIONAL_PROCESSING,
646 | self::GLOBAL_EVENT,
647 | self::GRAPHICAL_EVENT,
648 | self::PRESENTATION,
649 | self::ARIA
650 | ),
651 | 'pattern' => $attributeGroups(
652 | [
653 | 'height',
654 | 'href',
655 | 'patternContentUnits',
656 | 'patternTransform',
657 | 'patternUnits',
658 | 'preserveAspectRatio',
659 | 'viewBox',
660 | 'width',
661 | 'x',
662 | 'y',
663 | ],
664 | self::CORE,
665 | self::STYLE,
666 | self::CONDITIONAL_PROCESSING,
667 | self::PRESENTATION
668 | ),
669 | 'polygon' => $attributeGroups(
670 | ['points', 'pathLength'],
671 | self::CORE,
672 | self::STYLE,
673 | self::CONDITIONAL_PROCESSING,
674 | self::GLOBAL_EVENT,
675 | self::GRAPHICAL_EVENT,
676 | self::PRESENTATION,
677 | self::ARIA
678 | ),
679 | 'polyline' => $attributeGroups(
680 | ['points', 'pathLength'],
681 | self::CORE,
682 | self::STYLE,
683 | self::CONDITIONAL_PROCESSING,
684 | self::GLOBAL_EVENT,
685 | self::GRAPHICAL_EVENT,
686 | self::PRESENTATION,
687 | self::ARIA
688 | ),
689 | 'radialGradient' => $attributeGroups(
690 | ['cx', 'cy', 'fr', 'fx', 'fy', 'gradientUnits', 'gradientTransform', 'href', 'r', 'spreadMethod'],
691 | self::CORE,
692 | self::STYLE,
693 | self::GLOBAL_EVENT,
694 | self::DOCUMENT_ELEMENT_EVENT,
695 | self::PRESENTATION
696 | ),
697 | 'rect' => $attributeGroups(
698 | ['x', 'y', 'width', 'height', 'rx', 'ry', 'pathLength'],
699 | self::CORE,
700 | self::STYLE,
701 | self::CONDITIONAL_PROCESSING,
702 | self::GLOBAL_EVENT,
703 | self::GRAPHICAL_EVENT,
704 | self::PRESENTATION,
705 | self::ARIA
706 | ),
707 | // disabled because, easy to hide bad stuff here
708 | //'script' => $attributeGroups(
709 | // ['crossorigin', 'href', 'type'],
710 | // self::CORE,
711 | // self::STYLE,
712 | // self::GLOBAL_EVENT,
713 | // self::DOCUMENT_ELEMENT_EVENT
714 | //),
715 | 'set' => $attributeGroups(
716 | ['to'],
717 | self::ANIMATION_TIMING,
718 | self::ANIMATION_ATTRIBUTE_TARGET,
719 | self::ANIMATION_EVENT,
720 | self::CORE,
721 | self::STYLE,
722 | self::GLOBAL_EVENT,
723 | self::DOCUMENT_ELEMENT_EVENT
724 | ),
725 | 'stop' => $attributeGroups(
726 | ['offset', 'stop-color', 'stop-opacity'],
727 | self::CORE,
728 | self::STYLE,
729 | self::GLOBAL_EVENT,
730 | self::DOCUMENT_ELEMENT_EVENT,
731 | self::PRESENTATION
732 | ),
733 | 'style' => $attributeGroups(
734 | ['type', 'media', 'title'],
735 | self::CORE,
736 | self::STYLE,
737 | self::GLOBAL_EVENT,
738 | self::DOCUMENT_ELEMENT_EVENT
739 | ),
740 | 'switch' => $attributeGroups(
741 | self::CORE,
742 | self::CONDITIONAL_PROCESSING,
743 | self::GRAPHICAL_EVENT,
744 | self::PRESENTATION,
745 | self::STYLE
746 | ),
747 | 'symbol' => $attributeGroups(
748 | ['height', 'preserveAspectRatio', 'refX', 'refY', 'viewBox', 'width', 'x', 'y'],
749 | self::CORE,
750 | self::STYLE,
751 | self::GLOBAL_EVENT,
752 | self::DOCUMENT_ELEMENT_EVENT,
753 | self::GRAPHICAL_EVENT,
754 | self::PRESENTATION,
755 | self::ARIA
756 | ),
757 | 'text' => $attributeGroups(
758 | ['x', 'y', 'dx', 'dy', 'rotate', 'lengthAdjust', 'textLength'],
759 | self::CORE,
760 | self::STYLE,
761 | self::CONDITIONAL_PROCESSING,
762 | self::GLOBAL_EVENT,
763 | self::GRAPHICAL_EVENT,
764 | self::PRESENTATION,
765 | self::ARIA
766 | ),
767 | 'textPath' => $attributeGroups(
768 | ['href', 'lengthAdjust', 'method', 'path', 'side', 'spacing', 'startOffset', 'textLength'],
769 | self::CORE,
770 | self::STYLE,
771 | self::CONDITIONAL_PROCESSING,
772 | self::GLOBAL_EVENT,
773 | self::GRAPHICAL_EVENT,
774 | self::PRESENTATION,
775 | self::ARIA
776 |
777 | ),
778 | 'title' => $attributeGroups(
779 | self::CORE,
780 | self::STYLE,
781 | self::GLOBAL_EVENT,
782 | self::DOCUMENT_ELEMENT_EVENT
783 | ),
784 | //'tref', // deprecated
785 | 'tspan' => $attributeGroups(
786 | ['x', 'y', 'dx', 'dy', 'rotate', 'lengthAdjust', 'textLength'],
787 | self::CORE,
788 | self::STYLE,
789 | self::CONDITIONAL_PROCESSING,
790 | self::GLOBAL_EVENT,
791 | self::GRAPHICAL_EVENT,
792 | self::PRESENTATION,
793 | self::ARIA
794 | ),
795 | 'use' => $attributeGroups(
796 | ['x', 'y', 'width', 'height'],
797 | self::CORE,
798 | self::STYLE,
799 | self::CONDITIONAL_PROCESSING,
800 | self::GLOBAL_EVENT,
801 | self::GRAPHICAL_EVENT,
802 | self::PRESENTATION,
803 | self::ARIA
804 | ),
805 | 'view' => $attributeGroups(
806 | ['viewBox', 'preserveAspectRatio', 'zoomAndPan', 'viewTarget'],
807 | self::CORE,
808 | self::GLOBAL_EVENT,
809 | self::ARIA
810 | ),
811 | //'vkern', // deprecated
812 | ];
813 | }
814 |
815 | /**
816 | * Return di.xml allowedTags configuration for DefaultWYSIWYGValidator
817 | *
818 | * This method is intended to be used during development to generate the XML and copy & paste it into di.xml.
819 | */
820 | public static function getDiConfigAllowedTags(): string
821 | {
822 | $xml = '';
823 | foreach ((new self)->getSvgElements() as $element => $attributes) {
824 | $xml .= ' ' . self::formatItem($element) . "\n";
825 | }
826 | return $xml;
827 | }
828 |
829 | /**
830 | * Return di.xml allowedAttributesByTag configuration for DefaultWYSIWYGValidator
831 | *
832 | * This method is intended to be used during development to generate the XML and copy & paste it into di.xml.
833 | */
834 | public static function getDiConfigAllowedAttributesByTag(): string
835 | {
836 | $xml = '';
837 | foreach ((new self)->getSvgElements() as $element => $attributes) {
838 | $xml .= ' ' . self::formatArrayItem($element) . "\n";
839 | foreach ($attributes as $attribute) {
840 | $xml .= ' ' . self::formatItem($attribute) . "\n";
841 | }
842 | $xml .= ' ' . "\n";
843 | }
844 | return $xml;
845 | }
846 |
847 | private static function formatArrayItem(string $item): string
848 | {
849 | return sprintf('- ', $item);
850 | }
851 |
852 | private static function formatItem(string $item): string
853 | {
854 | return sprintf('
- %s
', $item, strtolower($item));
855 | }
856 | }
857 |
--------------------------------------------------------------------------------