├── 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 | [![Hyvä Themes](https://hyva.io/media/wysiwyg/logo-compact.png)](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 | --------------------------------------------------------------------------------