| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * kses 0.2.2 - HTML/XHTML filter that only allows some elements and attributes 4 * Copyright (C) 2002, 2003, 2005 Ulf Harnhammar 5 * 6 * This program is free software and open source software; you can redistribute 7 * it and/or modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of the License, 9 * or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 * more details. 15 * 16 * You should have received a copy of the GNU General Public License along 17 * with this program; if not, write to the Free Software Foundation, Inc., 18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 * http://www.gnu.org/licenses/gpl.html 20 * 21 * [kses strips evil scripts!] 22 * 23 * Added wp_ prefix to avoid conflicts with existing kses users 24 * 25 * @version 0.2.2 26 * @copyright (C) 2002, 2003, 2005 27 * @author Ulf Harnhammar <http://advogato.org/person/metaur/> 28 * 29 * @package External 30 * @subpackage KSES 31 */ 32 33 /** 34 * Specifies the default allowable HTML tags. 35 * 36 * Using `CUSTOM_TAGS` is not recommended and should be considered deprecated. The 37 * {@see 'wp_kses_allowed_html'} filter is more powerful and supplies context. 38 * 39 * When using this constant, make sure to set all of these globals to arrays: 40 * 41 * - `$allowedposttags` 42 * - `$allowedtags` 43 * - `$allowedentitynames` 44 * - `$allowedxmlentitynames` 45 * 46 * @see wp_kses_allowed_html() 47 * @since 1.2.0 48 * 49 * @var array[]|false Array of default allowable HTML tags, or false to use the defaults. 50 */ 51 if ( ! defined( 'CUSTOM_TAGS' ) ) { 52 define( 'CUSTOM_TAGS', false ); 53 } 54 55 // Ensure that these variables are added to the global namespace 56 // (e.g. if using namespaces / autoload in the current PHP environment). 57 global $allowedposttags, $allowedtags, $allowedentitynames, $allowedxmlentitynames; 58 59 if ( ! CUSTOM_TAGS ) { 60 /** 61 * KSES global for default allowable HTML tags. 62 * 63 * Can be overridden with the `CUSTOM_TAGS` constant. 64 * 65 * @var array[] $allowedposttags Array of default allowable HTML tags. 66 * @since 2.0.0 67 */ 68 $allowedposttags = array( 69 'address' => array(), 70 'a' => array( 71 'href' => true, 72 'rel' => true, 73 'rev' => true, 74 'name' => true, 75 'target' => true, 76 'download' => array( 77 'valueless' => 'y', 78 ), 79 ), 80 'abbr' => array(), 81 'acronym' => array(), 82 'area' => array( 83 'alt' => true, 84 'coords' => true, 85 'href' => true, 86 'nohref' => true, 87 'shape' => true, 88 'target' => true, 89 ), 90 'article' => array( 91 'align' => true, 92 ), 93 'aside' => array( 94 'align' => true, 95 ), 96 'audio' => array( 97 'autoplay' => true, 98 'controls' => true, 99 'loop' => true, 100 'muted' => true, 101 'preload' => true, 102 'src' => true, 103 ), 104 'b' => array(), 105 'bdo' => array(), 106 'big' => array(), 107 'blockquote' => array( 108 'cite' => true, 109 ), 110 'br' => array(), 111 'button' => array( 112 'command' => true, 113 'commandfor' => true, 114 'disabled' => true, 115 'name' => true, 116 'type' => true, 117 'value' => true, 118 'popovertarget' => true, 119 'popovertargetaction' => true, 120 'aria-haspopup' => true, 121 ), 122 'caption' => array( 123 'align' => true, 124 ), 125 'cite' => array(), 126 'code' => array(), 127 'col' => array( 128 'align' => true, 129 'char' => true, 130 'charoff' => true, 131 'span' => true, 132 'valign' => true, 133 'width' => true, 134 ), 135 'colgroup' => array( 136 'align' => true, 137 'char' => true, 138 'charoff' => true, 139 'span' => true, 140 'valign' => true, 141 'width' => true, 142 ), 143 'data' => array( 144 'value' => true, 145 ), 146 'del' => array( 147 'datetime' => true, 148 ), 149 'dd' => array(), 150 'dfn' => array(), 151 'details' => array( 152 'align' => true, 153 'open' => true, 154 'name' => true, 155 ), 156 'div' => array( 157 'align' => true, 158 'popover' => true, 159 ), 160 'dialog' => array( 161 'closedby' => true, 162 'open' => true, 163 'popover' => true, 164 ), 165 'dl' => array(), 166 'dt' => array(), 167 'em' => array(), 168 'fieldset' => array(), 169 'figure' => array( 170 'align' => true, 171 ), 172 'figcaption' => array( 173 'align' => true, 174 ), 175 'font' => array( 176 'color' => true, 177 'face' => true, 178 'size' => true, 179 ), 180 'footer' => array( 181 'align' => true, 182 ), 183 'h1' => array( 184 'align' => true, 185 ), 186 'h2' => array( 187 'align' => true, 188 ), 189 'h3' => array( 190 'align' => true, 191 ), 192 'h4' => array( 193 'align' => true, 194 ), 195 'h5' => array( 196 'align' => true, 197 ), 198 'h6' => array( 199 'align' => true, 200 ), 201 'header' => array( 202 'align' => true, 203 ), 204 'hgroup' => array( 205 'align' => true, 206 ), 207 'hr' => array( 208 'align' => true, 209 'noshade' => true, 210 'size' => true, 211 'width' => true, 212 ), 213 'i' => array(), 214 'img' => array( 215 'alt' => true, 216 'align' => true, 217 'border' => true, 218 'height' => true, 219 'hspace' => true, 220 'loading' => true, 221 'longdesc' => true, 222 'vspace' => true, 223 'src' => true, 224 'usemap' => true, 225 'width' => true, 226 ), 227 'ins' => array( 228 'datetime' => true, 229 'cite' => true, 230 ), 231 'kbd' => array(), 232 'label' => array( 233 'for' => true, 234 ), 235 'legend' => array( 236 'align' => true, 237 ), 238 'li' => array( 239 'align' => true, 240 'value' => true, 241 ), 242 'main' => array( 243 'align' => true, 244 ), 245 'map' => array( 246 'name' => true, 247 ), 248 'mark' => array(), 249 'menu' => array( 250 'type' => true, 251 ), 252 'meter' => array( 253 'high' => true, 254 'low' => true, 255 'max' => true, 256 'min' => true, 257 'optimum' => true, 258 'value' => true, 259 ), 260 'nav' => array( 261 'align' => true, 262 ), 263 'object' => array( 264 'data' => array( 265 'required' => true, 266 'value_callback' => '_wp_kses_allow_pdf_objects', 267 ), 268 'type' => array( 269 'required' => true, 270 'values' => array( 'application/pdf' ), 271 ), 272 ), 273 'p' => array( 274 'align' => true, 275 ), 276 'pre' => array( 277 'width' => true, 278 ), 279 'progress' => array( 280 'max' => true, 281 'value' => true, 282 ), 283 'q' => array( 284 'cite' => true, 285 ), 286 'rb' => array(), 287 'rp' => array(), 288 'rt' => array(), 289 'rtc' => array(), 290 'ruby' => array(), 291 's' => array(), 292 'samp' => array(), 293 'search' => array(), 294 'span' => array( 295 'align' => true, 296 ), 297 'section' => array( 298 'align' => true, 299 ), 300 'small' => array(), 301 'strike' => array(), 302 'strong' => array(), 303 'sub' => array(), 304 'summary' => array( 305 'align' => true, 306 ), 307 'sup' => array(), 308 'table' => array( 309 'align' => true, 310 'bgcolor' => true, 311 'border' => true, 312 'cellpadding' => true, 313 'cellspacing' => true, 314 'rules' => true, 315 'summary' => true, 316 'width' => true, 317 ), 318 'tbody' => array( 319 'align' => true, 320 'char' => true, 321 'charoff' => true, 322 'valign' => true, 323 ), 324 'td' => array( 325 'abbr' => true, 326 'align' => true, 327 'axis' => true, 328 'bgcolor' => true, 329 'char' => true, 330 'charoff' => true, 331 'colspan' => true, 332 'headers' => true, 333 'height' => true, 334 'nowrap' => true, 335 'rowspan' => true, 336 'scope' => true, 337 'valign' => true, 338 'width' => true, 339 ), 340 'textarea' => array( 341 'cols' => true, 342 'rows' => true, 343 'disabled' => true, 344 'name' => true, 345 'readonly' => true, 346 ), 347 'tfoot' => array( 348 'align' => true, 349 'char' => true, 350 'charoff' => true, 351 'valign' => true, 352 ), 353 'th' => array( 354 'abbr' => true, 355 'align' => true, 356 'axis' => true, 357 'bgcolor' => true, 358 'char' => true, 359 'charoff' => true, 360 'colspan' => true, 361 'headers' => true, 362 'height' => true, 363 'nowrap' => true, 364 'rowspan' => true, 365 'scope' => true, 366 'valign' => true, 367 'width' => true, 368 ), 369 'thead' => array( 370 'align' => true, 371 'char' => true, 372 'charoff' => true, 373 'valign' => true, 374 ), 375 'time' => array( 376 'datetime' => true, 377 ), 378 'title' => array(), 379 'tr' => array( 380 'align' => true, 381 'bgcolor' => true, 382 'char' => true, 383 'charoff' => true, 384 'valign' => true, 385 ), 386 'track' => array( 387 'default' => true, 388 'kind' => true, 389 'label' => true, 390 'src' => true, 391 'srclang' => true, 392 ), 393 'tt' => array(), 394 'u' => array(), 395 'ul' => array( 396 'type' => true, 397 'popover' => true, 398 'role' => true, 399 ), 400 'ol' => array( 401 'start' => true, 402 'type' => true, 403 'reversed' => true, 404 ), 405 'var' => array(), 406 'video' => array( 407 'autoplay' => true, 408 'controls' => true, 409 'height' => true, 410 'loop' => true, 411 'muted' => true, 412 'playsinline' => true, 413 'poster' => true, 414 'preload' => true, 415 'src' => true, 416 'width' => true, 417 ), 418 'wbr' => array(), 419 ); 420 421 // https://www.w3.org/TR/mathml-core/#global-attributes 422 // Except common attributes added by _wp_add_global_attributes. 423 $math_global_attributes = array( 424 'displaystyle' => true, 425 'scriptlevel' => true, 426 'mathbackground' => true, 427 'mathcolor' => true, 428 'mathsize' => true, 429 // Common attributes also defined by _wp_add_global_attributes. 430 // We do not want to add all those global attributes though. 431 'class' => true, 432 'data-*' => true, 433 'dir' => true, 434 'id' => true, 435 'style' => true, 436 ); 437 438 $math_overunder_attributes = array( 439 'accentunder' => true, 440 'accent' => true, 441 ); 442 443 $allowedposttags = array_merge( 444 $allowedposttags, 445 array( 446 // https://www.w3.org/TR/mathml-core/#the-top-level-math-element 447 'math' => array_merge( 448 $math_global_attributes, 449 array( 450 'display' => true, 451 ) 452 ), 453 454 // https://www.w3.org/TR/mathml-core/#token-elements 455 // https://www.w3.org/TR/mathml-core/#text-mtext 456 'mtext' => $math_global_attributes, 457 // https://www.w3.org/TR/mathml-core/#the-mi-element 458 'mi' => array_merge( 459 $math_global_attributes, 460 array( 461 'mathvariant' => true, 462 ) 463 ), 464 // https://www.w3.org/TR/mathml-core/#number-mn 465 'mn' => $math_global_attributes, 466 // https://www.w3.org/TR/mathml-core/#operator-fence-separator-or-accent-mo 467 'mo' => array_merge( 468 $math_global_attributes, 469 array( 470 'form' => true, 471 'fence' => true, 472 'separator' => true, 473 'lspace' => true, 474 'rspace' => true, 475 'stretchy' => true, 476 'symmetric' => true, 477 'maxsize' => true, 478 'minsize' => true, 479 'largeop' => true, 480 'movablelimits' => true, 481 ) 482 ), 483 // https://www.w3.org/TR/mathml-core/#space-mspace 484 'mspace' => array_merge( 485 $math_global_attributes, 486 array( 487 'width' => true, 488 'height' => true, 489 'depth' => true, 490 ) 491 ), 492 // https://www.w3.org/TR/mathml-core/#string-literal-ms 493 'ms' => $math_global_attributes, 494 495 // https://www.w3.org/TR/mathml-core/#general-layout-schemata 496 // https://www.w3.org/TR/mathml-core/#horizontally-group-sub-expressions-mrow 497 'mrow' => $math_global_attributes, 498 // https://www.w3.org/TR/mathml-core/#fractions-mfrac 499 'mfrac' => array_merge( 500 $math_global_attributes, 501 array( 502 'linethickness' => true, 503 ) 504 ), 505 // https://www.w3.org/TR/mathml-core/#radicals-msqrt-mroot 506 'msqrt' => $math_global_attributes, 507 'mroot' => $math_global_attributes, 508 // https://www.w3.org/TR/mathml-core/#style-change-mstyle 509 'mstyle' => $math_global_attributes, 510 // https://www.w3.org/TR/mathml-core/#error-message-merror 511 'merror' => $math_global_attributes, 512 // https://www.w3.org/TR/mathml-core/#adjust-space-around-content-mpadded 513 'mpadded' => array_merge( 514 $math_global_attributes, 515 array( 516 'width' => true, 517 'height' => true, 518 'depth' => true, 519 'lspace' => true, 520 'voffset' => true, 521 ) 522 ), 523 // https://www.w3.org/TR/mathml-core/#making-sub-expressions-invisible-mphantom 524 'mphantom' => $math_global_attributes, 525 526 // https://www.w3.org/TR/mathml-core/#script-and-limit-schemata 527 // https://www.w3.org/TR/mathml-core/#subscripts-and-superscripts-msub-msup-msubsup 528 'msub' => $math_global_attributes, 529 'msup' => $math_global_attributes, 530 'msubsup' => $math_global_attributes, 531 // https://www.w3.org/TR/mathml-core/#underscripts-and-overscripts-munder-mover-munderover 532 'munder' => array_merge( $math_global_attributes, $math_overunder_attributes ), 533 'mover' => array_merge( $math_global_attributes, $math_overunder_attributes ), 534 'munderover' => array_merge( $math_global_attributes, $math_overunder_attributes ), 535 // https://www.w3.org/TR/mathml-core/#prescripts-and-tensor-indices-mmultiscripts 536 'mmultiscripts' => $math_global_attributes, 537 'mprescripts' => $math_global_attributes, 538 539 // https://www.w3.org/TR/mathml-core/#tabular-math 540 // https://www.w3.org/TR/mathml-core/#table-or-matrix-mtable 541 'mtable' => array_merge( 542 $math_global_attributes, 543 array( 544 // Non-standard, used by temml/katex. 545 // https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element/mtable 546 'columnalign' => true, 547 'rowspacing' => true, 548 'columnspacing' => true, 549 'align' => true, 550 'rowalign' => true, 551 'columnlines' => true, 552 'rowlines' => true, 553 'frame' => true, 554 'framespacing' => true, 555 'width' => true, 556 ) 557 ), 558 // https://www.w3.org/TR/mathml-core/#row-in-table-or-matrix-mtr 559 'mtr' => array_merge( 560 $math_global_attributes, 561 array( 562 // Non-standard, used by temml/katex. 563 // https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element/mtr 564 'columnalign' => true, 565 'rowalign' => true, 566 ) 567 ), 568 // https://www.w3.org/TR/mathml-core/#entry-in-table-or-matrix-mtd 569 'mtd' => array_merge( 570 $math_global_attributes, 571 array( 572 'columnspan' => true, 573 'rowspan' => true, 574 // Non-standard, used by temml/katex. 575 // https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element/mtd 576 'columnalign' => true, 577 'rowalign' => true, 578 ) 579 ), 580 581 // https://www.w3.org/TR/mathml-core/#semantics-and-presentation 582 'semantics' => $math_global_attributes, 583 'annotation' => array_merge( 584 $math_global_attributes, 585 array( 586 'encoding' => true, 587 ) 588 ), 589 590 // Non-standard but widely supported, used by temml/katex. 591 'menclose' => array_merge( 592 $math_global_attributes, 593 array( 594 'notation' => true, 595 ) 596 ), 597 ) 598 ); 599 600 /** 601 * @var array[] $allowedtags Array of KSES allowed HTML elements. 602 * @since 1.0.0 603 */ 604 $allowedtags = array( 605 'a' => array( 606 'href' => true, 607 'title' => true, 608 ), 609 'abbr' => array( 610 'title' => true, 611 ), 612 'acronym' => array( 613 'title' => true, 614 ), 615 'b' => array(), 616 'blockquote' => array( 617 'cite' => true, 618 ), 619 'cite' => array(), 620 'code' => array(), 621 'del' => array( 622 'datetime' => true, 623 ), 624 'em' => array(), 625 'i' => array(), 626 'q' => array( 627 'cite' => true, 628 ), 629 's' => array(), 630 'strike' => array(), 631 'strong' => array(), 632 ); 633 634 /** 635 * @var string[] $allowedentitynames Array of KSES allowed HTML entity names. 636 * @since 1.0.0 637 */ 638 $allowedentitynames = array( 639 'nbsp', 640 'iexcl', 641 'cent', 642 'pound', 643 'curren', 644 'yen', 645 'brvbar', 646 'sect', 647 'uml', 648 'copy', 649 'ordf', 650 'laquo', 651 'not', 652 'shy', 653 'reg', 654 'macr', 655 'deg', 656 'plusmn', 657 'acute', 658 'micro', 659 'para', 660 'middot', 661 'cedil', 662 'ordm', 663 'raquo', 664 'iquest', 665 'Agrave', 666 'Aacute', 667 'Acirc', 668 'Atilde', 669 'Auml', 670 'Aring', 671 'AElig', 672 'Ccedil', 673 'Egrave', 674 'Eacute', 675 'Ecirc', 676 'Euml', 677 'Igrave', 678 'Iacute', 679 'Icirc', 680 'Iuml', 681 'ETH', 682 'Ntilde', 683 'Ograve', 684 'Oacute', 685 'Ocirc', 686 'Otilde', 687 'Ouml', 688 'times', 689 'Oslash', 690 'Ugrave', 691 'Uacute', 692 'Ucirc', 693 'Uuml', 694 'Yacute', 695 'THORN', 696 'szlig', 697 'agrave', 698 'aacute', 699 'acirc', 700 'atilde', 701 'auml', 702 'aring', 703 'aelig', 704 'ccedil', 705 'egrave', 706 'eacute', 707 'ecirc', 708 'euml', 709 'igrave', 710 'iacute', 711 'icirc', 712 'iuml', 713 'eth', 714 'ntilde', 715 'ograve', 716 'oacute', 717 'ocirc', 718 'otilde', 719 'ouml', 720 'divide', 721 'oslash', 722 'ugrave', 723 'uacute', 724 'ucirc', 725 'uuml', 726 'yacute', 727 'thorn', 728 'yuml', 729 'quot', 730 'amp', 731 'lt', 732 'gt', 733 'apos', 734 'OElig', 735 'oelig', 736 'Scaron', 737 'scaron', 738 'Yuml', 739 'circ', 740 'tilde', 741 'ensp', 742 'emsp', 743 'thinsp', 744 'zwnj', 745 'zwj', 746 'lrm', 747 'rlm', 748 'ndash', 749 'mdash', 750 'lsquo', 751 'rsquo', 752 'sbquo', 753 'ldquo', 754 'rdquo', 755 'bdquo', 756 'dagger', 757 'Dagger', 758 'permil', 759 'lsaquo', 760 'rsaquo', 761 'euro', 762 'fnof', 763 'Alpha', 764 'Beta', 765 'Gamma', 766 'Delta', 767 'Epsilon', 768 'Zeta', 769 'Eta', 770 'Theta', 771 'Iota', 772 'Kappa', 773 'Lambda', 774 'Mu', 775 'Nu', 776 'Xi', 777 'Omicron', 778 'Pi', 779 'Rho', 780 'Sigma', 781 'Tau', 782 'Upsilon', 783 'Phi', 784 'Chi', 785 'Psi', 786 'Omega', 787 'alpha', 788 'beta', 789 'gamma', 790 'delta', 791 'epsilon', 792 'zeta', 793 'eta', 794 'theta', 795 'iota', 796 'kappa', 797 'lambda', 798 'mu', 799 'nu', 800 'xi', 801 'omicron', 802 'pi', 803 'rho', 804 'sigmaf', 805 'sigma', 806 'tau', 807 'upsilon', 808 'phi', 809 'chi', 810 'psi', 811 'omega', 812 'thetasym', 813 'upsih', 814 'piv', 815 'bull', 816 'hellip', 817 'prime', 818 'Prime', 819 'oline', 820 'frasl', 821 'weierp', 822 'image', 823 'real', 824 'trade', 825 'alefsym', 826 'larr', 827 'uarr', 828 'rarr', 829 'darr', 830 'harr', 831 'crarr', 832 'lArr', 833 'uArr', 834 'rArr', 835 'dArr', 836 'hArr', 837 'forall', 838 'part', 839 'exist', 840 'empty', 841 'nabla', 842 'isin', 843 'notin', 844 'ni', 845 'prod', 846 'sum', 847 'minus', 848 'lowast', 849 'radic', 850 'prop', 851 'infin', 852 'ang', 853 'and', 854 'or', 855 'cap', 856 'cup', 857 'int', 858 'sim', 859 'cong', 860 'asymp', 861 'ne', 862 'equiv', 863 'le', 864 'ge', 865 'sub', 866 'sup', 867 'nsub', 868 'sube', 869 'supe', 870 'oplus', 871 'otimes', 872 'perp', 873 'sdot', 874 'lceil', 875 'rceil', 876 'lfloor', 877 'rfloor', 878 'lang', 879 'rang', 880 'loz', 881 'spades', 882 'clubs', 883 'hearts', 884 'diams', 885 'sup1', 886 'sup2', 887 'sup3', 888 'frac14', 889 'frac12', 890 'frac34', 891 'there4', 892 ); 893 894 /** 895 * @var string[] $allowedxmlentitynames Array of KSES allowed XML entity names. 896 * @since 5.5.0 897 */ 898 $allowedxmlentitynames = array( 899 'amp', 900 'lt', 901 'gt', 902 'apos', 903 'quot', 904 ); 905 906 $allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags ); 907 } else { 908 $required_kses_globals = array( 909 'allowedposttags', 910 'allowedtags', 911 'allowedentitynames', 912 'allowedxmlentitynames', 913 ); 914 $missing_kses_globals = array(); 915 916 foreach ( $required_kses_globals as $global_name ) { 917 if ( ! isset( $GLOBALS[ $global_name ] ) || ! is_array( $GLOBALS[ $global_name ] ) ) { 918 $missing_kses_globals[] = '<code>$' . $global_name . '</code>'; 919 } 920 } 921 922 if ( $missing_kses_globals ) { 923 _doing_it_wrong( 924 'wp_kses_allowed_html', 925 sprintf( 926 /* translators: 1: CUSTOM_TAGS, 2: Global variable names. */ 927 __( 'When using the %1$s constant, make sure to set these globals to an array: %2$s.' ), 928 '<code>CUSTOM_TAGS</code>', 929 implode( ', ', $missing_kses_globals ) 930 ), 931 '6.2.0' 932 ); 933 } 934 935 $allowedtags = wp_kses_array_lc( $allowedtags ); 936 $allowedposttags = wp_kses_array_lc( $allowedposttags ); 937 } 938 939 /** 940 * Filters text content and strips out disallowed HTML. 941 * 942 * This function makes sure that only the allowed HTML element names, attribute 943 * names, attribute values, and HTML entities will occur in the given text string. 944 * 945 * This function expects unslashed data. 946 * 947 * @see wp_kses_post() for specifically filtering post content and fields. 948 * @see wp_allowed_protocols() for the default allowed protocols in link URLs. 949 * 950 * @since 1.0.0 951 * 952 * @param string $content Text content to filter. 953 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 954 * or a context name such as 'post'. See wp_kses_allowed_html() 955 * for the list of accepted context names. 956 * @param string[] $allowed_protocols Optional. Array of allowed URL protocols. 957 * Defaults to the result of wp_allowed_protocols(). 958 * @return string Filtered content containing only the allowed HTML. 959 */ 960 function wp_kses( $content, $allowed_html, $allowed_protocols = array() ) { 961 if ( empty( $allowed_protocols ) ) { 962 $allowed_protocols = wp_allowed_protocols(); 963 } 964 965 $content = wp_kses_no_null( $content, array( 'slash_zero' => 'keep' ) ); 966 $content = wp_kses_normalize_entities( $content ); 967 $content = wp_kses_hook( $content, $allowed_html, $allowed_protocols ); 968 969 return wp_kses_split( $content, $allowed_html, $allowed_protocols ); 970 } 971 972 /** 973 * Filters one HTML attribute and ensures its value is allowed. 974 * 975 * This function can escape data in some situations where `wp_kses()` must strip the whole attribute. 976 * 977 * @since 4.2.3 978 * 979 * @param string $attr The 'whole' attribute, including name and value. 980 * @param string $element The HTML element name to which the attribute belongs. 981 * @return string Filtered attribute. 982 */ 983 function wp_kses_one_attr( $attr, $element ) { 984 $uris = wp_kses_uri_attributes(); 985 $allowed_html = wp_kses_allowed_html( 'post' ); 986 $allowed_protocols = wp_allowed_protocols(); 987 $attr = wp_kses_no_null( $attr, array( 'slash_zero' => 'keep' ) ); 988 989 // Preserve leading and trailing whitespace. 990 $matches = array(); 991 preg_match( '/^\s*/', $attr, $matches ); 992 $lead = $matches[0]; 993 preg_match( '/\s*$/', $attr, $matches ); 994 $trail = $matches[0]; 995 if ( empty( $trail ) ) { 996 $attr = substr( $attr, strlen( $lead ) ); 997 } else { 998 $attr = substr( $attr, strlen( $lead ), -strlen( $trail ) ); 999 } 1000 1001 // Parse attribute name and value from input. 1002 $split = preg_split( '/\s*=\s*/', $attr, 2 ); 1003 $name = $split[0]; 1004 if ( count( $split ) === 2 ) { 1005 $value = $split[1]; 1006 1007 /* 1008 * Remove quotes surrounding $value. 1009 * Also guarantee correct quoting in $attr for this one attribute. 1010 */ 1011 if ( '' === $value ) { 1012 $quote = ''; 1013 } else { 1014 $quote = $value[0]; 1015 } 1016 if ( '"' === $quote || "'" === $quote ) { 1017 if ( ! str_ends_with( $value, $quote ) ) { 1018 return ''; 1019 } 1020 $value = substr( $value, 1, -1 ); 1021 } else { 1022 $quote = '"'; 1023 } 1024 1025 // Sanitize quotes, angle braces, and entities. 1026 $value = esc_attr( $value ); 1027 1028 // Sanitize URI values. 1029 if ( in_array( strtolower( $name ), $uris, true ) ) { 1030 $value = wp_kses_bad_protocol( $value, $allowed_protocols ); 1031 } 1032 1033 $attr = "$name=$quote$value$quote"; 1034 $vless = 'n'; 1035 } else { 1036 $value = ''; 1037 $vless = 'y'; 1038 } 1039 1040 // Sanitize attribute by name. 1041 wp_kses_attr_check( $name, $value, $attr, $vless, $element, $allowed_html ); 1042 1043 // Restore whitespace. 1044 return $lead . $attr . $trail; 1045 } 1046 1047 /** 1048 * Returns an array of allowed HTML tags and attributes for a given context. 1049 * 1050 * @since 3.5.0 1051 * @since 5.0.1 `form` removed as allowable HTML tag. 1052 * 1053 * @global array $allowedposttags 1054 * @global array $allowedtags 1055 * @global array $allowedentitynames 1056 * 1057 * @param string|array $context The context for which to retrieve tags. Allowed values are 'post', 1058 * 'strip', 'data', 'entities', or the name of a field filter such as 1059 * 'pre_user_description', or an array of allowed HTML elements and attributes. 1060 * @return array Array of allowed HTML tags and their allowed attributes. 1061 */ 1062 function wp_kses_allowed_html( $context = '' ) { 1063 global $allowedposttags, $allowedtags, $allowedentitynames; 1064 1065 if ( is_array( $context ) ) { 1066 // When `$context` is an array it's actually an array of allowed HTML elements and attributes. 1067 $html = $context; 1068 $context = 'explicit'; 1069 1070 /** 1071 * Filters the HTML tags that are allowed for a given context. 1072 * 1073 * HTML tags and attribute names are case-insensitive in HTML but must be 1074 * added to the KSES allow list in lowercase. An item added to the allow list 1075 * in upper or mixed case will not recognized as permitted by KSES. 1076 * 1077 * @since 3.5.0 1078 * 1079 * @param array[] $html Allowed HTML tags. 1080 * @param string $context Context name. 1081 */ 1082 return apply_filters( 'wp_kses_allowed_html', $html, $context ); 1083 } 1084 1085 switch ( $context ) { 1086 case 'post': 1087 /** This filter is documented in wp-includes/kses.php */ 1088 $tags = apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context ); 1089 1090 // 5.0.1 removed the `<form>` tag, allow it if a filter is allowing it's sub-elements `<input>` or `<select>`. 1091 if ( ! CUSTOM_TAGS && ! isset( $tags['form'] ) && ( isset( $tags['input'] ) || isset( $tags['select'] ) ) ) { 1092 $tags = $allowedposttags; 1093 1094 $tags['form'] = array( 1095 'action' => true, 1096 'accept' => true, 1097 'accept-charset' => true, 1098 'enctype' => true, 1099 'method' => true, 1100 'name' => true, 1101 'target' => true, 1102 ); 1103 1104 /** This filter is documented in wp-includes/kses.php */ 1105 $tags = apply_filters( 'wp_kses_allowed_html', $tags, $context ); 1106 } 1107 1108 return $tags; 1109 1110 case 'user_description': 1111 case 'pre_term_description': 1112 case 'pre_user_description': 1113 $tags = $allowedtags; 1114 $tags['a']['rel'] = true; 1115 $tags['a']['target'] = true; 1116 /** This filter is documented in wp-includes/kses.php */ 1117 return apply_filters( 'wp_kses_allowed_html', $tags, $context ); 1118 1119 case 'strip': 1120 /** This filter is documented in wp-includes/kses.php */ 1121 return apply_filters( 'wp_kses_allowed_html', array(), $context ); 1122 1123 case 'entities': 1124 /** This filter is documented in wp-includes/kses.php */ 1125 return apply_filters( 'wp_kses_allowed_html', $allowedentitynames, $context ); 1126 1127 case 'data': 1128 default: 1129 /** This filter is documented in wp-includes/kses.php */ 1130 return apply_filters( 'wp_kses_allowed_html', $allowedtags, $context ); 1131 } 1132 } 1133 1134 /** 1135 * You add any KSES hooks here. 1136 * 1137 * There is currently only one KSES WordPress hook, {@see 'pre_kses'}, and it is called here. 1138 * All parameters are passed to the hooks and expected to receive a string. 1139 * 1140 * @since 1.0.0 1141 * 1142 * @param string $content Content to filter through KSES. 1143 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 1144 * or a context name such as 'post'. See wp_kses_allowed_html() 1145 * for the list of accepted context names. 1146 * @param string[] $allowed_protocols Array of allowed URL protocols. 1147 * @return string Filtered content through {@see 'pre_kses'} hook. 1148 */ 1149 function wp_kses_hook( $content, $allowed_html, $allowed_protocols ) { 1150 /** 1151 * Filters content to be run through KSES. 1152 * 1153 * @since 2.3.0 1154 * 1155 * @param string $content Content to filter through KSES. 1156 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 1157 * or a context name such as 'post'. See wp_kses_allowed_html() 1158 * for the list of accepted context names. 1159 * @param string[] $allowed_protocols Array of allowed URL protocols. 1160 */ 1161 return apply_filters( 'pre_kses', $content, $allowed_html, $allowed_protocols ); 1162 } 1163 1164 /** 1165 * Returns the version number of KSES. 1166 * 1167 * @since 1.0.0 1168 * 1169 * @return string KSES version number. 1170 */ 1171 function wp_kses_version() { 1172 return '0.2.2'; 1173 } 1174 1175 /** 1176 * Searches for HTML tags, no matter how malformed. 1177 * 1178 * It also matches stray `>` characters. 1179 * 1180 * @since 1.0.0 1181 * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments. 1182 * 1183 * @global array[]|string $pass_allowed_html An array of allowed HTML elements and attributes, 1184 * or a context name such as 'post'. 1185 * @global string[] $pass_allowed_protocols Array of allowed URL protocols. 1186 * 1187 * @param string $content Content to filter. 1188 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 1189 * or a context name such as 'post'. See wp_kses_allowed_html() 1190 * for the list of accepted context names. 1191 * @param string[] $allowed_protocols Array of allowed URL protocols. 1192 * @return string Content with fixed HTML tags 1193 */ 1194 function wp_kses_split( $content, $allowed_html, $allowed_protocols ) { 1195 global $pass_allowed_html, $pass_allowed_protocols; 1196 1197 $pass_allowed_html = $allowed_html; 1198 $pass_allowed_protocols = $allowed_protocols; 1199 1200 $token_pattern = <<<REGEX 1201 ~ 1202 ( # Detect comments of various flavors before attempting to find tags. 1203 (<!--.*?(-->|$)) # - Normative HTML comments. 1204 | 1205 </[^a-zA-Z][^>]*> # - Closing tags with invalid tag names. 1206 | 1207 <![^>]*> # - Invalid markup declaration nodes. Not all invalid nodes 1208 # are matched so as to avoid breaking legacy behaviors. 1209 ) 1210 | 1211 (<[^>]*(>|$)|>) # Tag-like spans of text. 1212 ~x 1213 REGEX; 1214 return preg_replace_callback( $token_pattern, '_wp_kses_split_callback', $content ); 1215 } 1216 1217 /** 1218 * Returns an array of HTML attribute names whose value contains a URL. 1219 * 1220 * This function returns a list of all HTML attributes that must contain 1221 * a URL according to the HTML specification. 1222 * 1223 * This list includes URI attributes both allowed and disallowed by KSES. 1224 * 1225 * @link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes 1226 * 1227 * @since 5.0.1 1228 * 1229 * @return string[] HTML attribute names whose value contains a URL. 1230 */ 1231 function wp_kses_uri_attributes() { 1232 $uri_attributes = array( 1233 'action', 1234 'archive', 1235 'background', 1236 'cite', 1237 'classid', 1238 'codebase', 1239 'data', 1240 'formaction', 1241 'href', 1242 'icon', 1243 'longdesc', 1244 'manifest', 1245 'poster', 1246 'profile', 1247 'src', 1248 'usemap', 1249 'xmlns', 1250 ); 1251 1252 /** 1253 * Filters the list of attributes that are required to contain a URL. 1254 * 1255 * Use this filter to add any `data-` attributes that are required to be 1256 * validated as a URL. 1257 * 1258 * @since 5.0.1 1259 * 1260 * @param string[] $uri_attributes HTML attribute names whose value contains a URL. 1261 */ 1262 $uri_attributes = apply_filters( 'wp_kses_uri_attributes', $uri_attributes ); 1263 1264 return $uri_attributes; 1265 } 1266 1267 /** 1268 * Callback for `wp_kses_split()`. 1269 * 1270 * @since 3.1.0 1271 * @access private 1272 * @ignore 1273 * 1274 * @global array[]|string $pass_allowed_html An array of allowed HTML elements and attributes, 1275 * or a context name such as 'post'. 1276 * @global string[] $pass_allowed_protocols Array of allowed URL protocols. 1277 * 1278 * @param array $matches preg_replace regexp matches 1279 * @return string 1280 */ 1281 function _wp_kses_split_callback( $matches ) { 1282 global $pass_allowed_html, $pass_allowed_protocols; 1283 1284 return wp_kses_split2( $matches[0], $pass_allowed_html, $pass_allowed_protocols ); 1285 } 1286 1287 /** 1288 * Callback for `wp_kses_split()` for fixing malformed HTML tags. 1289 * 1290 * This function does a lot of work. It rejects some very malformed things like 1291 * `<:::>`. It returns an empty string, if the element isn't allowed (look ma, no 1292 * `strip_tags()`!). Otherwise it splits the tag into an element and an attribute 1293 * list. 1294 * 1295 * After the tag is split into an element and an attribute list, it is run 1296 * through another filter which will remove illegal attributes and once that is 1297 * completed, will be returned. 1298 * 1299 * @access private 1300 * @ignore 1301 * @since 1.0.0 1302 * @since 6.6.0 Recognize additional forms of invalid HTML which convert into comments. 1303 * 1304 * @param string $content Content to filter. 1305 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 1306 * or a context name such as 'post'. See wp_kses_allowed_html() 1307 * for the list of accepted context names. 1308 * @param string[] $allowed_protocols Array of allowed URL protocols. 1309 * 1310 * @return string Fixed HTML element 1311 */ 1312 function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) { 1313 $content = wp_kses_stripslashes( $content ); 1314 1315 /* 1316 * The regex pattern used to split HTML into chunks attempts 1317 * to split on HTML token boundaries. This function should 1318 * thus receive chunks that _either_ start with meaningful 1319 * syntax tokens, like a tag `<div>` or a comment `<!-- ... -->`. 1320 * 1321 * If the first character of the `$content` chunk _isn't_ one 1322 * of these syntax elements, which always starts with `<`, then 1323 * the match had to be for the final alternation of `>`. In such 1324 * case, it's probably standing on its own and could be encoded 1325 * with a character reference to remove ambiguity. 1326 * 1327 * In other words, if this chunk isn't from a match of a syntax 1328 * token, it's just a plaintext greater-than (`>`) sign. 1329 */ 1330 if ( ! str_starts_with( $content, '<' ) ) { 1331 return '>'; 1332 } 1333 1334 /* 1335 * When certain invalid syntax constructs appear, the HTML parser 1336 * shifts into what's called the "bogus comment state." This is a 1337 * plaintext state that consumes everything until the nearest `>` 1338 * and then transforms the entire span into an HTML comment. 1339 * 1340 * Preserve these comments and do not treat them like tags. 1341 * 1342 * @see https://html.spec.whatwg.org/#bogus-comment-state 1343 */ 1344 if ( 1 === preg_match( '~^(?:</[^a-zA-Z][^>]*>|<![a-z][^>]*>)$~', $content ) ) { 1345 /** 1346 * Since the pattern matches `</…>` and also `<!…>`, this will 1347 * preserve the type of the cleaned-up token in the output. 1348 */ 1349 $opener = $content[1]; 1350 $content = substr( $content, 2, -1 ); 1351 1352 do { 1353 $prev = $content; 1354 $content = wp_kses( $content, $allowed_html, $allowed_protocols ); 1355 } while ( $prev !== $content ); 1356 1357 // Recombine the modified inner content with the original token structure. 1358 return "<{$opener}{$content}>"; 1359 } 1360 1361 /* 1362 * Normative HTML comments should be handled separately as their 1363 * parsing rules differ from those for tags and text nodes. 1364 */ 1365 if ( str_starts_with( $content, '<!--' ) ) { 1366 $content = str_replace( array( '<!--', '-->' ), '', $content ); 1367 1368 while ( ( $newstring = wp_kses( $content, $allowed_html, $allowed_protocols ) ) !== $content ) { 1369 $content = $newstring; 1370 } 1371 1372 if ( '' === $content ) { 1373 return ''; 1374 } 1375 1376 // Prevent multiple dashes in comments. 1377 $content = preg_replace( '/--+/', '-', $content ); 1378 // Prevent three dashes closing a comment. 1379 $content = preg_replace( '/-$/', '', $content ); 1380 1381 return "<!--{$content}-->"; 1382 } 1383 1384 // It's seriously malformed. 1385 if ( ! preg_match( '%^<\s*(/\s*)?([a-zA-Z0-9-]+)([^>]*)>?$%', $content, $matches ) ) { 1386 return ''; 1387 } 1388 1389 $slash = trim( $matches[1] ); 1390 $elem = $matches[2]; 1391 $attrlist = $matches[3]; 1392 1393 if ( ! is_array( $allowed_html ) ) { 1394 $allowed_html = wp_kses_allowed_html( $allowed_html ); 1395 } 1396 1397 // They are using a not allowed HTML element. 1398 if ( ! isset( $allowed_html[ strtolower( $elem ) ] ) ) { 1399 return ''; 1400 } 1401 1402 // No attributes are allowed for closing elements. 1403 if ( '' !== $slash ) { 1404 return "</$elem>"; 1405 } 1406 1407 return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols ); 1408 } 1409 1410 /** 1411 * Removes all attributes, if none are allowed for this element. 1412 * 1413 * If some are allowed it calls `wp_kses_hair()` to split them further, and then 1414 * it builds up new HTML code from the data that `wp_kses_hair()` returns. It also 1415 * removes `<` and `>` characters, if there are any left. One more thing it does 1416 * is to check if the tag has a closing XHTML slash, and if it does, it puts one 1417 * in the returned code as well. 1418 * 1419 * An array of allowed values can be defined for attributes. If the attribute value 1420 * doesn't fall into the list, the attribute will be removed from the tag. 1421 * 1422 * Attributes can be marked as required. If a required attribute is not present, 1423 * KSES will remove all attributes from the tag. As KSES doesn't match opening and 1424 * closing tags, it's not possible to safely remove the tag itself, the safest 1425 * fallback is to strip all attributes from the tag, instead. 1426 * 1427 * @since 1.0.0 1428 * @since 5.9.0 Added support for an array of allowed values for attributes. 1429 * Added support for required attributes. 1430 * 1431 * @param string $element HTML element/tag. 1432 * @param string $attr HTML attributes from HTML element to closing HTML element tag. 1433 * @param array[]|string $allowed_html An array of allowed HTML elements and attributes, 1434 * or a context name such as 'post'. See wp_kses_allowed_html() 1435 * for the list of accepted context names. 1436 * @param string[] $allowed_protocols Array of allowed URL protocols. 1437 * @return string Sanitized HTML element. 1438 */ 1439 function wp_kses_attr( $element, $attr, $allowed_html, $allowed_protocols ) { 1440 if ( ! is_array( $allowed_html ) ) { 1441 $allowed_html = wp_kses_allowed_html( $allowed_html ); 1442 } 1443 1444 // Is there a closing XHTML slash at the end of the attributes? 1445 $xhtml_slash = ''; 1446 if ( preg_match( '%\s*/\s*$%', $attr ) ) { 1447 $xhtml_slash = ' /'; 1448 } 1449 1450 // Are any attributes allowed at all for this element? 1451 $element_low = strtolower( $element ); 1452 if ( empty( $allowed_html[ $element_low ] ) || true === $allowed_html[ $element_low ] ) { 1453 return "<$element$xhtml_slash>"; 1454 } 1455 1456 // Split it. 1457 $attrarr = wp_kses_hair( $attr, $allowed_protocols ); 1458 1459 // Check if there are attributes that are required. 1460 $required_attrs = array_filter( 1461 $allowed_html[ $element_low ], 1462 static function ( $required_attr_limits ) { 1463 return isset( $required_attr_limits['required'] ) && true === $required_attr_limits['required']; 1464 } 1465 ); 1466 1467 /* 1468 * If a required attribute check fails, we can return nothing for a self-closing tag, 1469 * but for a non-self-closing tag the best option is to return the element with attributes, 1470 * as KSES doesn't handle matching the relevant closing tag. 1471 */ 1472 $stripped_tag = ''; 1473 if ( empty( $xhtml_slash ) ) { 1474 $stripped_tag = "<$element>"; 1475 } 1476 1477 // Go through $attrarr, and save the allowed attributes for this element in $attr2. 1478 $attr2 = ''; 1479 foreach ( $attrarr as $arreach ) { 1480 // Check if this attribute is required. 1481 $required = isset( $required_attrs[ strtolower( $arreach['name'] ) ] ); 1482 1483 if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) { 1484 $attr2 .= ' ' . $arreach['whole']; 1485 1486 // If this was a required attribute, we can mark it as found. 1487 if ( $required ) { 1488 unset( $required_attrs[ strtolower( $arreach['name'] ) ] ); 1489 } 1490 } elseif ( $required ) { 1491 // This attribute was required, but didn't pass the check. The entire tag is not allowed. 1492 return $stripped_tag; 1493 } 1494 } 1495 1496 // If some required attributes weren't set, the entire tag is not allowed. 1497 if ( ! empty( $required_attrs ) ) { 1498 return $stripped_tag; 1499 } 1500 1501 // Remove any "<" or ">" characters. 1502 $attr2 = preg_replace( '/[<>]/', '', $attr2 ); 1503 1504 return "<$element$attr2$xhtml_slash>"; 1505 } 1506 1507 /** 1508 * Determines whether an attribute is allowed. 1509 * 1510 * @since 4.2.3 1511 * @since 5.0.0 Added support for `data-*` wildcard attributes. 1512 * 1513 * @param string $name The attribute name. Passed by reference. Returns empty string when not allowed. 1514 * @param string $value The attribute value. Passed by reference. Returns a filtered value. 1515 * @param string $whole The `name=value` input. Passed by reference. Returns filtered input. 1516 * @param string $vless Whether the attribute is valueless. Use 'y' or 'n'. 1517 * @param string $element The name of the element to which this attribute belongs. 1518 * @param array $allowed_html The full list of allowed elements and attributes. 1519 * @return bool Whether or not the attribute is allowed. 1520 */ 1521 function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) { 1522 $name_low = strtolower( $name ); 1523 $element_low = strtolower( $element ); 1524 1525 if ( ! isset( $allowed_html[ $element_low ] ) ) { 1526 $name = ''; 1527 $value = ''; 1528 $whole = ''; 1529 return false; 1530 } 1531 1532 $allowed_attr = $allowed_html[ $element_low ]; 1533 1534 if ( ! isset( $allowed_attr[ $name_low ] ) || '' === $allowed_attr[ $name_low ] ) { 1535 /* 1536 * Allow `data-*` attributes. 1537 * 1538 * When specifying `$allowed_html`, the attribute name should be set as 1539 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see 1540 * https://www.w3.org/TR/html40/struct/objects.html#adef-data). 1541 * 1542 * Note: the attribute name should only contain `A-Za-z0-9_-` chars. 1543 */ 1544 if ( str_starts_with( $name_low, 'data-' ) && ! empty( $allowed_attr['data-*'] ) 1545 && preg_match( '/^data-[a-z0-9_-]+$/', $name_low, $match ) 1546 ) { 1547 /* 1548 * Add the whole attribute name to the allowed attributes and set any restrictions 1549 * for the `data-*` attribute values for the current element. 1550 */ 1551 $allowed_attr[ $match[0] ] = $allowed_attr['data-*']; 1552 } else { 1553 $name = ''; 1554 $value = ''; 1555 $whole = ''; 1556 return false; 1557 } 1558 } 1559 1560 if ( 'style' === $name_low ) { 1561 $decoded_value = WP_HTML_Decoder::decode_attribute( $value ); 1562 $new_value = safecss_filter_attr( $decoded_value ); 1563 1564 if ( empty( $new_value ) ) { 1565 $name = ''; 1566 $value = ''; 1567 $whole = ''; 1568 return false; 1569 } 1570 1571 if ( $new_value !== $decoded_value ) { 1572 $encoded_value = esc_attr( $new_value ); 1573 $whole = str_replace( $value, $encoded_value, $whole ); 1574 $value = $encoded_value; 1575 } 1576 } 1577 1578 if ( is_array( $allowed_attr[ $name_low ] ) ) { 1579 // There are some checks. 1580 foreach ( $allowed_attr[ $name_low ] as $currkey => $currval ) { 1581 if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) { 1582 $name = ''; 1583 $value = ''; 1584 $whole = ''; 1585 return false; 1586 } 1587 } 1588 } 1589 1590 return true; 1591 } 1592 1593 /** 1594 * Given a string of HTML attributes and values, parse into a structured attribute list. 1595 * 1596 * This function performs a number of transformations while parsing attribute strings: 1597 * - It normalizes attribute values and surrounds them with double quotes. 1598 * - It normalizes HTML character references inside attribute values. 1599 * - It removes “bad” URL protocols from attribute values. 1600 * 1601 * Otherwise this reads the attributes as if they were part of an HTML tag. It performs 1602 * these transformations to lower the risk of mis-parsing down the line and to perform 1603 * URL sanitization in line with the rest of the `kses` subsystem. Importantly, it does 1604 * not decode the attribute values, meaning that special HTML syntax characters will 1605 * be left with character references in the `value` property. 1606 * 1607 * Example: 1608 * 1609 * $attrs = wp_kses_hair( 'class="is-wide" inert data-lazy=\'<img>\' =/🐮=/' ); 1610 * $attrs === array( 1611 * 'class' => array( 'name' => 'class', 'value' => 'is-wide', 'whole' => 'class="is-wide"', 'vless' => 'n' ), 1612 * 'inert' => array( 'name' => 'inert', 'value' => '', 'whole' => 'inert', 'vless' => 'y' ), 1613 * 'data-lazy' => array( 'name' => 'data-lazy', 'value' => '<img>', 'whole' => 'data-lazy="<img>"', 'vless' => 'n' ), 1614 * '=' => array( 'name' => '=', 'value' => '', 'whole' => '=', 'vless' => 'y' ), 1615 * '🐮' => array( 'name' => '🐮', 'value' => '/', 'whole' => '🐮="/"', 'vless' => 'n' ), 1616 * ); 1617 * 1618 * @since 1.0.0 1619 * @since 7.0.0 Reliably parses HTML via the HTML API. 1620 * 1621 * @param string $attr Attribute list from HTML element to closing HTML element tag. 1622 * @param string[] $allowed_protocols Array of allowed URL protocols. 1623 * @return array<string, array{name: string, value: string, whole: string, vless: 'y'|'n'}> Array of attribute information after parsing. 1624 */ 1625 function wp_kses_hair( $attr, $allowed_protocols ) { 1626 $attributes = array(); 1627 $uris = wp_kses_uri_attributes(); 1628 1629 $processor = new WP_HTML_Tag_Processor( "<wp {$attr}>" ); 1630 $processor->next_token(); 1631 1632 $attribute_names = $processor->get_attribute_names_with_prefix( '' ); 1633 if ( null === $attribute_names || 0 === count( $attribute_names ) ) { 1634 return $attributes; 1635 } 1636 1637 $syntax_characters = array( 1638 '&' => '&', 1639 '<' => '<', 1640 '>' => '>', 1641 "'" => ''', 1642 '"' => '"', 1643 ); 1644 1645 foreach ( $attribute_names as $name ) { 1646 $value = $processor->get_attribute( $name ); 1647 $is_bool = true === $value; 1648 if ( is_string( $value ) && in_array( $name, $uris, true ) ) { 1649 $value = wp_kses_bad_protocol( $value, $allowed_protocols ); 1650 } 1651 1652 // Reconstruct and normalize the attribute value. 1653 $recoded = $is_bool ? '' : strtr( $value, $syntax_characters ); 1654 $whole = $is_bool ? $name : "{$name}=\"{$recoded}\""; 1655 1656 $attributes[ $name ] = array( 1657 'name' => $name, 1658 'value' => $recoded, 1659 'whole' => $whole, 1660 'vless' => $is_bool ? 'y' : 'n', 1661 ); 1662 } 1663 1664 return $attributes; 1665 } 1666 1667 /** 1668 * Finds all attributes of an HTML element. 1669 * 1670 * Does not modify input. May return "evil" output. 1671 * 1672 * Based on `wp_kses_split2()` and `wp_kses_attr()`. 1673 * 1674 * @since 4.2.3 1675 * 1676 * @param string $element HTML element. 1677 * @return array|false List of attributes found in the element. Returns false on failure. 1678 */ 1679 function wp_kses_attr_parse( $element ) { 1680 $valid = preg_match( '%^(<\s*)(/\s*)?([a-zA-Z0-9]+\s*)([^>]*)(>?)$%', $element, $matches ); 1681 if ( 1 !== $valid ) { 1682 return false; 1683 } 1684 1685 $begin = $matches[1]; 1686 $slash = $matches[2]; 1687 $elname = $matches[3]; 1688 $attr = $matches[4]; 1689 $end = $matches[5]; 1690 1691 if ( '' !== $slash ) { 1692 // Closing elements do not get parsed. 1693 return false; 1694 } 1695 1696 // Is there a closing XHTML slash at the end of the attributes? 1697 if ( 1 === preg_match( '%\s*/\s*$%', $attr, $matches ) ) { 1698 $xhtml_slash = $matches[0]; 1699 $attr = substr( $attr, 0, -strlen( $xhtml_slash ) ); 1700 } else { 1701 $xhtml_slash = ''; 1702 } 1703 1704 // Split it. 1705 $attrarr = wp_kses_hair_parse( $attr ); 1706 if ( false === $attrarr ) { 1707 return false; 1708 } 1709 1710 // Make sure all input is returned by adding front and back matter. 1711 array_unshift( $attrarr, $begin . $slash . $elname ); 1712 array_push( $attrarr, $xhtml_slash . $end ); 1713 1714 return $attrarr; 1715 } 1716 1717 /** 1718 * Builds an attribute list from string containing attributes. 1719 * 1720 * Does not modify input. May return "evil" output. 1721 * In case of unexpected input, returns false instead of stripping things. 1722 * 1723 * Based on `wp_kses_hair()` but does not return a multi-dimensional array. 1724 * 1725 * @since 4.2.3 1726 * 1727 * @param string $attr Attribute list from HTML element to closing HTML element tag. 1728 * @return array|false List of attributes found in $attr. Returns false on failure. 1729 */ 1730 function wp_kses_hair_parse( $attr ) { 1731 if ( '' === $attr ) { 1732 return array(); 1733 } 1734 1735 $regex = 1736 '(?: 1737 [_a-zA-Z][-_a-zA-Z0-9:.]* # Attribute name. 1738 | 1739 \[\[?[^\[\]]+\]\]? # Shortcode in the name position implies unfiltered_html. 1740 ) 1741 (?: # Attribute value. 1742 \s*=\s* # All values begin with "=". 1743 (?: 1744 "[^"]*" # Double-quoted. 1745 | 1746 \'[^\']*\' # Single-quoted. 1747 | 1748 [^\s"\']+ # Non-quoted. 1749 (?:\s|$) # Must have a space. 1750 ) 1751 | 1752 (?:\s|$) # If attribute has no value, space is required. 1753 ) 1754 \s* # Trailing space is optional except as mentioned above. 1755 '; 1756 1757 /* 1758 * Although it is possible to reduce this procedure to a single regexp, 1759 * we must run that regexp twice to get exactly the expected result. 1760 * 1761 * Note: do NOT remove the `x` modifiers as they are essential for the above regex! 1762 */ 1763 1764 $validation = "/^($regex)+$/x"; 1765 $extraction = "/$regex/x"; 1766 1767 if ( 1 === preg_match( $validation, $attr ) ) { 1768 preg_match_all( $extraction, $attr, $attrarr ); 1769 return $attrarr[0]; 1770 } else { 1771 return false; 1772 } 1773 } 1774 1775 /** 1776 * Performs different checks for attribute values. 1777 * 1778 * The currently implemented checks are "maxlen", "minlen", "maxval", "minval", 1779 * and "valueless". 1780 * 1781 * @since 1.0.0 1782 * 1783 * @param string $value Attribute value. 1784 * @param string $vless Whether the attribute is valueless. Use 'y' or 'n'. 1785 * @param string $checkname What $checkvalue is checking for. 1786 * @param mixed $checkvalue What constraint the value should pass. 1787 * @return bool Whether check passes. 1788 */ 1789 function wp_kses_check_attr_val( $value, $vless, $checkname, $checkvalue ) { 1790 $ok = true; 1791 1792 switch ( strtolower( $checkname ) ) { 1793 case 'maxlen': 1794 /* 1795 * The maxlen check makes sure that the attribute value has a length not 1796 * greater than the given value. This can be used to avoid Buffer Overflows 1797 * in WWW clients and various Internet servers. 1798 */ 1799 1800 if ( strlen( $value ) > $checkvalue ) { 1801 $ok = false; 1802 } 1803 break; 1804 1805 case 'minlen': 1806 /* 1807 * The minlen check makes sure that the attribute value has a length not 1808 * smaller than the given value. 1809 */ 1810 1811 if ( strlen( $value ) < $checkvalue ) { 1812 $ok = false; 1813 } 1814 break; 1815 1816 case 'maxval': 1817 /* 1818 * The maxval check does two things: it checks that the attribute value is 1819 * an integer from 0 and up, without an excessive amount of zeroes or 1820 * whitespace (to avoid Buffer Overflows). It also checks that the attribute 1821 * value is not greater than the given value. 1822 * This check can be used to avoid Denial of Service attacks. 1823 */ 1824 1825 if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) { 1826 $ok = false; 1827 } 1828 if ( $value > $checkvalue ) { 1829 $ok = false; 1830 } 1831 break; 1832 1833 case 'minval': 1834 /* 1835 * The minval check makes sure that the attribute value is a positive integer, 1836 * and that it is not smaller than the given value. 1837 */ 1838 1839 if ( ! preg_match( '/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value ) ) { 1840 $ok = false; 1841 } 1842 if ( $value < $checkvalue ) { 1843 $ok = false; 1844 } 1845 break; 1846 1847 case 'valueless': 1848 /* 1849 * The valueless check makes sure if the attribute has a value 1850 * (like `<a href="blah">`) or not (`<option selected>`). If the given value 1851 * is a "y" or a "Y", the attribute must not have a value. 1852 * If the given value is an "n" or an "N", the attribute must have a value. 1853 */ 1854 1855 if ( strtolower( $checkvalue ) !== $vless ) { 1856 $ok = false; 1857 } 1858 break; 1859 1860 case 'values': 1861 /* 1862 * The values check is used when you want to make sure that the attribute 1863 * has one of the given values. 1864 */ 1865 1866 if ( false === array_search( strtolower( $value ), $checkvalue, true ) ) { 1867 $ok = false; 1868 } 1869 break; 1870 1871 case 'value_callback': 1872 /* 1873 * The value_callback check is used when you want to make sure that the attribute 1874 * value is accepted by the callback function. 1875 */ 1876 1877 if ( ! call_user_func( $checkvalue, $value ) ) { 1878 $ok = false; 1879 } 1880 break; 1881 } // End switch. 1882 1883 return $ok; 1884 } 1885 1886 /** 1887 * Sanitizes a string and removed disallowed URL protocols. 1888 * 1889 * This function removes all non-allowed protocols from the beginning of the 1890 * string. It ignores whitespace and the case of the letters, and it does 1891 * understand HTML entities. It does its work recursively, so it won't be 1892 * fooled by a string like `javascript:javascript:alert(57)`. 1893 * 1894 * @since 1.0.0 1895 * 1896 * @param string $content Content to filter bad protocols from. 1897 * @param string[] $allowed_protocols Array of allowed URL protocols. 1898 * @return string Filtered content. 1899 */ 1900 function wp_kses_bad_protocol( $content, $allowed_protocols ) { 1901 $content = wp_kses_no_null( $content ); 1902 1903 // Short-circuit if the string starts with `https://` or `http://`. Most common cases. 1904 if ( 1905 ( str_starts_with( $content, 'https://' ) && in_array( 'https', $allowed_protocols, true ) ) || 1906 ( str_starts_with( $content, 'http://' ) && in_array( 'http', $allowed_protocols, true ) ) 1907 ) { 1908 return $content; 1909 } 1910 1911 $iterations = 0; 1912 1913 do { 1914 $original_content = $content; 1915 $content = wp_kses_bad_protocol_once( $content, $allowed_protocols ); 1916 } while ( $original_content !== $content && ++$iterations < 6 ); 1917 1918 if ( $original_content !== $content ) { 1919 return ''; 1920 } 1921 1922 return $content; 1923 } 1924 1925 /** 1926 * Removes any invalid control characters in a text string. 1927 * 1928 * Also removes any instance of the `\0` string. 1929 * 1930 * @since 1.0.0 1931 * 1932 * @param string $content Content to filter null characters from. 1933 * @param array $options Set 'slash_zero' => 'keep' when '\0' is allowed. Default is 'remove'. 1934 * @return string Filtered content. 1935 */ 1936 function wp_kses_no_null( $content, $options = null ) { 1937 if ( ! isset( $options['slash_zero'] ) ) { 1938 $options = array( 'slash_zero' => 'remove' ); 1939 } 1940 1941 $content = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $content ); 1942 if ( 'remove' === $options['slash_zero'] ) { 1943 $content = preg_replace( '/\\\\+0+/', '', $content ); 1944 } 1945 1946 return $content; 1947 } 1948 1949 /** 1950 * Strips slashes from in front of quotes. 1951 * 1952 * This function changes the character sequence `\"` to just `"`. It leaves all other 1953 * slashes alone. The quoting from `preg_replace(//e)` requires this. 1954 * 1955 * @since 1.0.0 1956 * 1957 * @param string $content String to strip slashes from. 1958 * @return string Fixed string with quoted slashes. 1959 */ 1960 function wp_kses_stripslashes( $content ) { 1961 return preg_replace( '%\\\\"%', '"', $content ); 1962 } 1963 1964 /** 1965 * Converts the keys of an array to lowercase. 1966 * 1967 * @since 1.0.0 1968 * 1969 * @param array $inarray Unfiltered array. 1970 * @return array Fixed array with all lowercase keys. 1971 */ 1972 function wp_kses_array_lc( $inarray ) { 1973 $outarray = array(); 1974 1975 foreach ( (array) $inarray as $inkey => $inval ) { 1976 $outkey = strtolower( $inkey ); 1977 $outarray[ $outkey ] = array(); 1978 1979 foreach ( (array) $inval as $inkey2 => $inval2 ) { 1980 $outkey2 = strtolower( $inkey2 ); 1981 $outarray[ $outkey ][ $outkey2 ] = $inval2; 1982 } 1983 } 1984 1985 return $outarray; 1986 } 1987 1988 /** 1989 * Handles parsing errors in `wp_kses_hair()`. 1990 * 1991 * The general plan is to remove everything to and including some whitespace, 1992 * but it deals with quotes and apostrophes as well. 1993 * 1994 * @since 1.0.0 1995 * 1996 * @param string $attr 1997 * @return string 1998 */ 1999 function wp_kses_html_error( $attr ) { 2000 return preg_replace( '/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $attr ); 2001 } 2002 2003 /** 2004 * Sanitizes content from bad protocols and other characters. 2005 * 2006 * This function searches for URL protocols at the beginning of the string, while 2007 * handling whitespace and HTML entities. 2008 * 2009 * @since 1.0.0 2010 * 2011 * @param string $content Content to check for bad protocols. 2012 * @param string[] $allowed_protocols Array of allowed URL protocols. 2013 * @param int $count Depth of call recursion to this function. 2014 * @return string Sanitized content. 2015 */ 2016 function wp_kses_bad_protocol_once( $content, $allowed_protocols, $count = 1 ) { 2017 $content = preg_replace( '/(�*58(?![;0-9])|�*3a(?![;a-f0-9]))/i', '$1;', $content ); 2018 $content2 = preg_split( '/:|�*58;|�*3a;|:/i', $content, 2 ); 2019 2020 if ( isset( $content2[1] ) && ! preg_match( '%/\?%', $content2[0] ) ) { 2021 $content = trim( $content2[1] ); 2022 $protocol = wp_kses_bad_protocol_once2( $content2[0], $allowed_protocols ); 2023 if ( 'feed:' === $protocol ) { 2024 if ( $count > 2 ) { 2025 return ''; 2026 } 2027 $content = wp_kses_bad_protocol_once( $content, $allowed_protocols, ++$count ); 2028 if ( empty( $content ) ) { 2029 return $content; 2030 } 2031 } 2032 $content = $protocol . $content; 2033 } 2034 2035 return $content; 2036 } 2037 2038 /** 2039 * Callback for `wp_kses_bad_protocol_once()` regular expression. 2040 * 2041 * This function processes URL protocols, checks to see if they're in the 2042 * list of allowed protocols or not, and returns different data depending 2043 * on the answer. 2044 * 2045 * @access private 2046 * @ignore 2047 * @since 1.0.0 2048 * 2049 * @param string $scheme URI scheme to check against the list of allowed protocols. 2050 * @param string[] $allowed_protocols Array of allowed URL protocols. 2051 * @return string Sanitized content. 2052 */ 2053 function wp_kses_bad_protocol_once2( $scheme, $allowed_protocols ) { 2054 $scheme = wp_kses_decode_entities( $scheme ); 2055 $scheme = preg_replace( '/\s/', '', $scheme ); 2056 $scheme = wp_kses_no_null( $scheme ); 2057 $scheme = strtolower( $scheme ); 2058 2059 $allowed = array_any( (array) $allowed_protocols, fn( $protocol ) => strtolower( $protocol ) === $scheme ); 2060 2061 if ( $allowed ) { 2062 return "$scheme:"; 2063 } else { 2064 return ''; 2065 } 2066 } 2067 2068 /** 2069 * Converts and fixes HTML entities. 2070 * 2071 * This function normalizes HTML entities. It will convert `AT&T` to the correct 2072 * `AT&T`, `:` to `:`, `&#XYZZY;` to `&#XYZZY;` and so on. 2073 * 2074 * When `$context` is set to 'xml', HTML entities are converted to their code points. For 2075 * example, `AT&T…&#XYZZY;` is converted to `AT&T…&#XYZZY;`. 2076 * 2077 * @since 1.0.0 2078 * @since 5.5.0 Added `$context` parameter. 2079 * 2080 * @param string $content Content to normalize entities. 2081 * @param string $context Context for normalization. Can be either 'html' or 'xml'. 2082 * Default 'html'. 2083 * @return string Content with normalized entities. 2084 */ 2085 function wp_kses_normalize_entities( $content, $context = 'html' ) { 2086 // Disarm all entities by converting & to & 2087 $content = str_replace( '&', '&', $content ); 2088 2089 /* 2090 * Decode any character references that are now double-encoded. 2091 * 2092 * It's important that the following normalizations happen in the correct order. 2093 * 2094 * At this point, all `&` have been transformed to `&`. Double-encoded named character 2095 * references like `&amp;` will be decoded back to their single-encoded form `&`. 2096 * 2097 * First, numeric (decimal and hexadecimal) character references must be handled so that 2098 * `&#09;` becomes `	`. If the named character references were handled first, there 2099 * would be no way to know whether the double-encoded character reference had been produced 2100 * in this function or was the original input. 2101 * 2102 * Consider the two examples, first with named entity decoding followed by numeric 2103 * entity decoding. We'll use U+002E FULL STOP (.) in our example, this table follows the 2104 * string processing from left to right: 2105 * 2106 * | Input | &-encoded | Named ref double-decoded | Numeric ref double-decoded | 2107 * | ------------ | ---------------- | ------------------------- | -------------------------- | 2108 * | `.` | `&#x2E;` | `&#x2E;` | `.` | 2109 * | `&#x2E;` | `&amp;#x2E;` | `&#x2E;` | `.` | 2110 * 2111 * Notice in the example above that different inputs result in the same result. The second case 2112 * was not normalized and produced HTML that is semantically different from the input. 2113 * 2114 * | Input | &-encoded | Numeric ref double-decoded | Named ref double-decoded | 2115 * | ------------ | ---------------- | --------------------------- | ------------------------ | 2116 * | `.` | `&#x2E;` | `.` | `.` | 2117 * | `&#x2E;` | `&amp;#x2E;` | `&amp;#x2E;` | `&#x2E;` | 2118 * 2119 * Here, each input is normalized to an appropriate output. 2120 */ 2121 $content = preg_replace_callback( '/&#(0*[1-9][0-9]{0,6});/', 'wp_kses_normalize_entities2', $content ); 2122 $content = preg_replace_callback( '/&#[Xx](0*[1-9A-Fa-f][0-9A-Fa-f]{0,5});/', 'wp_kses_normalize_entities3', $content ); 2123 if ( 'xml' === $context ) { 2124 $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $content ); 2125 } else { 2126 $content = preg_replace_callback( '/&([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $content ); 2127 } 2128 2129 return $content; 2130 } 2131 2132 /** 2133 * Callback for `wp_kses_normalize_entities()` regular expression. 2134 * 2135 * This function only accepts valid named entity references, which are finite, 2136 * case-sensitive, and highly scrutinized by HTML and XML validators. 2137 * 2138 * @since 3.0.0 2139 * 2140 * @global array $allowedentitynames 2141 * 2142 * @param array $matches preg_replace_callback() matches array. 2143 * @return string Correctly encoded entity. 2144 */ 2145 function wp_kses_named_entities( $matches ) { 2146 global $allowedentitynames; 2147 2148 if ( empty( $matches[1] ) ) { 2149 return ''; 2150 } 2151 2152 $i = $matches[1]; 2153 return ( ! in_array( $i, $allowedentitynames, true ) ) ? "&$i;" : "&$i;"; 2154 } 2155 2156 /** 2157 * Callback for `wp_kses_normalize_entities()` regular expression. 2158 * 2159 * This function only accepts valid named entity references, which are finite, 2160 * case-sensitive, and highly scrutinized by XML validators. HTML named entity 2161 * references are converted to their code points. 2162 * 2163 * @since 5.5.0 2164 * 2165 * @global array $allowedentitynames 2166 * @global array $allowedxmlentitynames 2167 * 2168 * @param array $matches preg_replace_callback() matches array. 2169 * @return string Correctly encoded entity. 2170 */ 2171 function wp_kses_xml_named_entities( $matches ) { 2172 global $allowedentitynames, $allowedxmlentitynames; 2173 2174 if ( empty( $matches[1] ) ) { 2175 return ''; 2176 } 2177 2178 $i = $matches[1]; 2179 2180 if ( in_array( $i, $allowedxmlentitynames, true ) ) { 2181 return "&$i;"; 2182 } elseif ( in_array( $i, $allowedentitynames, true ) ) { 2183 return html_entity_decode( "&$i;", ENT_HTML5 ); 2184 } 2185 2186 return "&$i;"; 2187 } 2188 2189 /** 2190 * Callback for `wp_kses_normalize_entities()` regular expression. 2191 * 2192 * This function helps `wp_kses_normalize_entities()` to only accept 16-bit 2193 * values and nothing more for `&#number;` entities. 2194 * 2195 * @access private 2196 * @ignore 2197 * @since 1.0.0 2198 * 2199 * @param array $matches `preg_replace_callback()` matches array. 2200 * @return string Correctly encoded entity. 2201 */ 2202 function wp_kses_normalize_entities2( $matches ) { 2203 if ( empty( $matches[1] ) ) { 2204 return ''; 2205 } 2206 2207 $i = $matches[1]; 2208 2209 if ( valid_unicode( $i ) ) { 2210 $i = str_pad( ltrim( $i, '0' ), 3, '0', STR_PAD_LEFT ); 2211 $i = "&#$i;"; 2212 } else { 2213 $i = "&#$i;"; 2214 } 2215 2216 return $i; 2217 } 2218 2219 /** 2220 * Callback for `wp_kses_normalize_entities()` for regular expression. 2221 * 2222 * This function helps `wp_kses_normalize_entities()` to only accept valid Unicode 2223 * numeric entities in hex form. 2224 * 2225 * @since 2.7.0 2226 * @access private 2227 * @ignore 2228 * 2229 * @param array $matches `preg_replace_callback()` matches array. 2230 * @return string Correctly encoded entity. 2231 */ 2232 function wp_kses_normalize_entities3( $matches ) { 2233 if ( empty( $matches[1] ) ) { 2234 return ''; 2235 } 2236 2237 $hexchars = $matches[1]; 2238 2239 return ( ! valid_unicode( hexdec( $hexchars ) ) ) ? "&#x$hexchars;" : '&#x' . ltrim( $hexchars, '0' ) . ';'; 2240 } 2241 2242 /** 2243 * Determines if a Unicode codepoint is valid. 2244 * 2245 * The definition of a valid Unicode codepoint is taken from the XML definition: 2246 * 2247 * > Characters 2248 * > 2249 * > … 2250 * > Legal characters are tab, carriage return, line feed, and the legal characters of 2251 * > Unicode and ISO/IEC 10646. 2252 * > … 2253 * > Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] 2254 * 2255 * @since 2.7.0 2256 * 2257 * @see https://www.w3.org/TR/xml/#charsets 2258 * 2259 * @param int $i Unicode codepoint. 2260 * @return bool Whether or not the codepoint is a valid Unicode codepoint. 2261 */ 2262 function valid_unicode( $i ) { 2263 $i = (int) $i; 2264 2265 return ( 2266 0x9 === $i || // U+0009 HORIZONTAL TABULATION (HT) 2267 0xA === $i || // U+000A LINE FEED (LF) 2268 0xD === $i || // U+000D CARRIAGE RETURN (CR) 2269 /* 2270 * The valid Unicode characters according to the XML specification: 2271 * 2272 * > any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. 2273 */ 2274 ( 0x20 <= $i && $i <= 0xD7FF ) || 2275 ( 0xE000 <= $i && $i <= 0xFFFD ) || 2276 ( 0x10000 <= $i && $i <= 0x10FFFF ) 2277 ); 2278 } 2279 2280 /** 2281 * Converts all numeric HTML entities to their named counterparts. 2282 * 2283 * This function decodes numeric HTML entities (`A` and `A`). 2284 * It doesn't do anything with named entities like `ä`, but we don't 2285 * need them in the allowed URL protocols system anyway. 2286 * 2287 * @since 1.0.0 2288 * 2289 * @param string $content Content to change entities. 2290 * @return string Content after decoded entities. 2291 */ 2292 function wp_kses_decode_entities( $content ) { 2293 $content = preg_replace_callback( '/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $content ); 2294 $content = preg_replace_callback( '/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $content ); 2295 2296 return $content; 2297 } 2298 2299 /** 2300 * Regex callback for `wp_kses_decode_entities()`. 2301 * 2302 * @since 2.9.0 2303 * @access private 2304 * @ignore 2305 * 2306 * @param array $matches preg match 2307 * @return string 2308 */ 2309 function _wp_kses_decode_entities_chr( $matches ) { 2310 return chr( $matches[1] ); 2311 } 2312 2313 /** 2314 * Regex callback for `wp_kses_decode_entities()`. 2315 * 2316 * @since 2.9.0 2317 * @access private 2318 * @ignore 2319 * 2320 * @param array $matches preg match 2321 * @return string 2322 */ 2323 function _wp_kses_decode_entities_chr_hexdec( $matches ) { 2324 return chr( hexdec( $matches[1] ) ); 2325 } 2326 2327 /** 2328 * Sanitize content with allowed HTML KSES rules. 2329 * 2330 * This function expects slashed data. 2331 * 2332 * @since 1.0.0 2333 * 2334 * @param string $data Content to filter, expected to be escaped with slashes. 2335 * @return string Filtered content. 2336 */ 2337 function wp_filter_kses( $data ) { 2338 return addslashes( wp_kses( stripslashes( $data ), current_filter() ) ); 2339 } 2340 2341 /** 2342 * Sanitize content with allowed HTML KSES rules. 2343 * 2344 * This function expects unslashed data. 2345 * 2346 * @since 2.9.0 2347 * 2348 * @param string $data Content to filter, expected to not be escaped. 2349 * @return string Filtered content. 2350 */ 2351 function wp_kses_data( $data ) { 2352 return wp_kses( $data, current_filter() ); 2353 } 2354 2355 /** 2356 * Sanitizes content for allowed HTML tags for post content. 2357 * 2358 * Post content refers to the page contents of the 'post' type and not `$_POST` 2359 * data from forms. 2360 * 2361 * This function expects slashed data. 2362 * 2363 * @since 2.0.0 2364 * 2365 * @param string $data Post content to filter, expected to be escaped with slashes. 2366 * @return string Filtered post content with allowed HTML tags and attributes intact. 2367 */ 2368 function wp_filter_post_kses( $data ) { 2369 return addslashes( wp_kses( stripslashes( $data ), 'post' ) ); 2370 } 2371 2372 /** 2373 * Sanitizes global styles user content removing unsafe rules. 2374 * 2375 * @since 5.9.0 2376 * 2377 * @param string $data Post content to filter. 2378 * @return string Filtered post content with unsafe rules removed. 2379 */ 2380 function wp_filter_global_styles_post( $data ) { 2381 $decoded_data = json_decode( wp_unslash( $data ), true ); 2382 $json_decoding_error = json_last_error(); 2383 if ( 2384 JSON_ERROR_NONE === $json_decoding_error && 2385 is_array( $decoded_data ) && 2386 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && 2387 $decoded_data['isGlobalStylesUserThemeJSON'] 2388 ) { 2389 unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); 2390 2391 $data_to_encode = WP_Theme_JSON::remove_insecure_properties( $decoded_data, 'custom' ); 2392 2393 $data_to_encode['isGlobalStylesUserThemeJSON'] = true; 2394 /** 2395 * JSON encode the data stored in post content. 2396 * Escape characters that are likely to be mangled by HTML filters: "<>&". 2397 * 2398 * This matches the escaping in {@see WP_REST_Global_Styles_Controller::prepare_item_for_database()}. 2399 */ 2400 return wp_slash( wp_json_encode( $data_to_encode, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) ); 2401 } 2402 return $data; 2403 } 2404 2405 /** 2406 * Sanitizes content for allowed HTML tags for post content. 2407 * 2408 * Post content refers to the page contents of the 'post' type and not `$_POST` 2409 * data from forms. 2410 * 2411 * This function expects unslashed data. 2412 * 2413 * @since 2.9.0 2414 * 2415 * @param string $data Post content to filter. 2416 * @return string Filtered post content with allowed HTML tags and attributes intact. 2417 */ 2418 function wp_kses_post( $data ) { 2419 return wp_kses( $data, 'post' ); 2420 } 2421 2422 /** 2423 * Navigates through an array, object, or scalar, and sanitizes content for 2424 * allowed HTML tags for post content. 2425 * 2426 * @since 4.4.2 2427 * 2428 * @see map_deep() 2429 * 2430 * @param mixed $data The array, object, or scalar value to inspect. 2431 * @return mixed The filtered content. 2432 */ 2433 function wp_kses_post_deep( $data ) { 2434 return map_deep( $data, 'wp_kses_post' ); 2435 } 2436 2437 /** 2438 * Strips all HTML from a text string. 2439 * 2440 * This function expects slashed data. 2441 * 2442 * @since 2.1.0 2443 * 2444 * @param string $data Content to strip all HTML from. 2445 * @return string Filtered content without any HTML. 2446 */ 2447 function wp_filter_nohtml_kses( $data ) { 2448 return addslashes( wp_kses( stripslashes( $data ), 'strip' ) ); 2449 } 2450 2451 /** 2452 * Adds all KSES input form content filters. 2453 * 2454 * All hooks have default priority. The `wp_filter_kses()` function is added to 2455 * the 'pre_comment_content' and 'title_save_pre' hooks. 2456 * 2457 * The `wp_filter_post_kses()` function is added to the 'content_save_pre', 2458 * 'excerpt_save_pre', and 'content_filtered_save_pre' hooks. 2459 * 2460 * @since 2.0.0 2461 */ 2462 function kses_init_filters() { 2463 // Normal filtering. 2464 add_filter( 'title_save_pre', 'wp_filter_kses' ); 2465 2466 // Comment filtering. 2467 if ( current_user_can( 'unfiltered_html' ) ) { 2468 add_filter( 'pre_comment_content', 'wp_filter_post_kses' ); 2469 } else { 2470 add_filter( 'pre_comment_content', 'wp_filter_kses' ); 2471 } 2472 2473 // Global Styles filtering: Global Styles filters should be executed before normal post_kses HTML filters. 2474 add_filter( 'content_save_pre', 'wp_filter_global_styles_post', 9 ); 2475 add_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 ); 2476 2477 // Post filtering. 2478 add_filter( 'content_save_pre', 'wp_filter_post_kses' ); 2479 add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' ); 2480 add_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' ); 2481 } 2482 2483 /** 2484 * Removes all KSES input form content filters. 2485 * 2486 * A quick procedural method to removing all of the filters that KSES uses for 2487 * content in WordPress Loop. 2488 * 2489 * Does not remove the `kses_init()` function from {@see 'init'} hook (priority is 2490 * default). Also does not remove `kses_init()` function from {@see 'set_current_user'} 2491 * hook (priority is also default). 2492 * 2493 * @since 2.0.6 2494 */ 2495 function kses_remove_filters() { 2496 // Normal filtering. 2497 remove_filter( 'title_save_pre', 'wp_filter_kses' ); 2498 2499 // Comment filtering. 2500 remove_filter( 'pre_comment_content', 'wp_filter_post_kses' ); 2501 remove_filter( 'pre_comment_content', 'wp_filter_kses' ); 2502 2503 // Global Styles filtering. 2504 remove_filter( 'content_save_pre', 'wp_filter_global_styles_post', 9 ); 2505 remove_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 ); 2506 2507 // Post filtering. 2508 remove_filter( 'content_save_pre', 'wp_filter_post_kses' ); 2509 remove_filter( 'excerpt_save_pre', 'wp_filter_post_kses' ); 2510 remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' ); 2511 } 2512 2513 /** 2514 * Sets up most of the KSES filters for input form content. 2515 * 2516 * First removes all of the KSES filters in case the current user does not need 2517 * to have KSES filter the content. If the user does not have `unfiltered_html` 2518 * capability, then KSES filters are added. 2519 * 2520 * @since 2.0.0 2521 */ 2522 function kses_init() { 2523 kses_remove_filters(); 2524 2525 if ( ! current_user_can( 'unfiltered_html' ) ) { 2526 kses_init_filters(); 2527 } 2528 } 2529 2530 /** 2531 * Filters an inline style attribute and removes disallowed rules. 2532 * 2533 * @since 2.8.1 2534 * @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`. 2535 * @since 4.6.0 Added support for `list-style-type`. 2536 * @since 5.0.0 Added support for `background-image`. 2537 * @since 5.1.0 Added support for `text-transform`. 2538 * @since 5.2.0 Added support for `background-position` and `grid-template-columns`. 2539 * @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties. 2540 * Extended `background-*` support for individual properties. 2541 * @since 5.3.1 Added support for gradient backgrounds. 2542 * @since 5.7.1 Added support for `object-position`. 2543 * @since 5.8.0 Added support for `calc()` and `var()` values. 2544 * @since 6.1.0 Added support for `min()`, `max()`, `minmax()`, `clamp()`, 2545 * nested `var()` values, and assigning values to CSS variables. 2546 * Added support for `object-fit`, `gap`, `column-gap`, `row-gap`, and `flex-wrap`. 2547 * Extended `margin-*` and `padding-*` support for logical properties. 2548 * @since 6.2.0 Added support for `aspect-ratio`, `position`, `top`, `right`, `bottom`, `left`, 2549 * and `z-index` CSS properties. 2550 * @since 6.3.0 Extended support for `filter` to accept a URL and added support for repeat(). 2551 * Added support for `box-shadow`. 2552 * @since 6.4.0 Added support for `writing-mode`. 2553 * @since 6.5.0 Added support for `background-repeat`. 2554 * @since 6.6.0 Added support for `grid-column`, `grid-row`, and `container-type`. 2555 * @since 6.9.0 Added support for `white-space`. 2556 * 2557 * @param string $css A string of CSS rules, decoded from an HTML `style` attribute. 2558 * @param string $deprecated Not used. 2559 * @return string Filtered string of CSS rules, needing HTML escaping before sending back to a `style` attribute. 2560 */ 2561 function safecss_filter_attr( $css, $deprecated = '' ) { 2562 if ( ! empty( $deprecated ) ) { 2563 _deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented. 2564 } 2565 2566 $css = wp_kses_no_null( $css ); 2567 $css = str_replace( array( "\n", "\r", "\t" ), '', $css ); 2568 2569 $allowed_protocols = wp_allowed_protocols(); 2570 2571 /** @todo Parse enough CSS to split rules without breaking on things like quoted strings. */ 2572 $css_array = explode( ';', trim( $css ) ); 2573 2574 /** 2575 * Filters the list of allowed CSS attributes. 2576 * 2577 * @since 2.8.1 2578 * @since 7.1.0 Added support for SVG presentation attributes. 2579 * 2580 * @param string[] $attr Array of allowed CSS attributes. 2581 */ 2582 $allowed_attr = apply_filters( 2583 'safe_style_css', 2584 array( 2585 'background', 2586 'background-color', 2587 'background-image', 2588 'background-position', 2589 'background-repeat', 2590 'background-size', 2591 'background-attachment', 2592 'background-blend-mode', 2593 2594 'border', 2595 'border-radius', 2596 'border-width', 2597 'border-color', 2598 'border-style', 2599 'border-right', 2600 'border-right-color', 2601 'border-right-style', 2602 'border-right-width', 2603 'border-bottom', 2604 'border-bottom-color', 2605 'border-bottom-left-radius', 2606 'border-bottom-right-radius', 2607 'border-bottom-style', 2608 'border-bottom-width', 2609 'border-bottom-right-radius', 2610 'border-bottom-left-radius', 2611 'border-left', 2612 'border-left-color', 2613 'border-left-style', 2614 'border-left-width', 2615 'border-top', 2616 'border-top-color', 2617 'border-top-left-radius', 2618 'border-top-right-radius', 2619 'border-top-style', 2620 'border-top-width', 2621 'border-top-left-radius', 2622 'border-top-right-radius', 2623 2624 'border-spacing', 2625 'border-collapse', 2626 'caption-side', 2627 2628 'columns', 2629 'column-count', 2630 'column-fill', 2631 'column-gap', 2632 'column-rule', 2633 'column-span', 2634 'column-width', 2635 2636 'display', 2637 2638 'color', 2639 'filter', 2640 'font', 2641 'font-family', 2642 'font-size', 2643 'font-style', 2644 'font-variant', 2645 'font-weight', 2646 'letter-spacing', 2647 'line-height', 2648 'text-align', 2649 'text-decoration', 2650 'text-indent', 2651 'text-transform', 2652 'white-space', 2653 2654 'height', 2655 'min-height', 2656 'max-height', 2657 2658 'width', 2659 'min-width', 2660 'max-width', 2661 2662 'margin', 2663 'margin-right', 2664 'margin-bottom', 2665 'margin-left', 2666 'margin-top', 2667 'margin-block-start', 2668 'margin-block-end', 2669 'margin-inline-start', 2670 'margin-inline-end', 2671 2672 'padding', 2673 'padding-right', 2674 'padding-bottom', 2675 'padding-left', 2676 'padding-top', 2677 'padding-block-start', 2678 'padding-block-end', 2679 'padding-inline-start', 2680 'padding-inline-end', 2681 2682 'flex', 2683 'flex-basis', 2684 'flex-direction', 2685 'flex-flow', 2686 'flex-grow', 2687 'flex-shrink', 2688 'flex-wrap', 2689 2690 'gap', 2691 'column-gap', 2692 'row-gap', 2693 2694 'grid-template-columns', 2695 'grid-auto-columns', 2696 'grid-column-start', 2697 'grid-column-end', 2698 'grid-column', 2699 'grid-column-gap', 2700 'grid-template-rows', 2701 'grid-auto-rows', 2702 'grid-row-start', 2703 'grid-row-end', 2704 'grid-row', 2705 'grid-row-gap', 2706 'grid-gap', 2707 2708 'justify-content', 2709 'justify-items', 2710 'justify-self', 2711 'align-content', 2712 'align-items', 2713 'align-self', 2714 2715 'clear', 2716 'cursor', 2717 'direction', 2718 'float', 2719 'list-style-type', 2720 'object-fit', 2721 'object-position', 2722 'opacity', 2723 'overflow', 2724 'vertical-align', 2725 'writing-mode', 2726 2727 'position', 2728 'top', 2729 'right', 2730 'bottom', 2731 'left', 2732 'z-index', 2733 'box-shadow', 2734 'aspect-ratio', 2735 'container-type', 2736 2737 'fill', 2738 'fill-opacity', 2739 'fill-rule', 2740 2741 'stroke', 2742 'stroke-dasharray', 2743 'stroke-dashoffset', 2744 'stroke-linecap', 2745 'stroke-linejoin', 2746 'stroke-miterlimit', 2747 'stroke-opacity', 2748 'stroke-width', 2749 2750 'color-interpolation', 2751 'color-interpolation-filters', 2752 'paint-order', 2753 'stop-color', 2754 'stop-opacity', 2755 'flood-color', 2756 'flood-opacity', 2757 'lighting-color', 2758 2759 'marker', 2760 'marker-end', 2761 'marker-mid', 2762 'marker-start', 2763 2764 'clip-path', 2765 'clip-rule', 2766 'mask', 2767 'mask-type', 2768 2769 'cx', 2770 'cy', 2771 'r', 2772 'rx', 2773 'ry', 2774 'x', 2775 'y', 2776 'd', 2777 2778 'alignment-baseline', 2779 'baseline-shift', 2780 'dominant-baseline', 2781 'glyph-orientation-horizontal', 2782 'glyph-orientation-vertical', 2783 'text-anchor', 2784 'unicode-bidi', 2785 'word-spacing', 2786 2787 'font-size-adjust', 2788 'font-stretch', 2789 2790 'color-rendering', 2791 'image-rendering', 2792 'shape-rendering', 2793 'text-rendering', 2794 'vector-effect', 2795 2796 'transform', 2797 'transform-origin', 2798 2799 'pointer-events', 2800 'visibility', 2801 2802 // Custom CSS properties. 2803 '--*', 2804 ) 2805 ); 2806 2807 /* 2808 * CSS attributes that accept URL data types. 2809 * 2810 * This is in accordance to the CSS spec and unrelated to 2811 * the sub-set of supported attributes above. 2812 * 2813 * See: https://developer.mozilla.org/en-US/docs/Web/CSS/url 2814 */ 2815 $css_url_data_types = array( 2816 'background', 2817 'background-image', 2818 2819 'cursor', 2820 'filter', 2821 2822 'list-style', 2823 'list-style-image', 2824 ); 2825 2826 /* 2827 * CSS attributes that accept gradient data types. 2828 * 2829 */ 2830 $css_gradient_data_types = array( 2831 'background', 2832 'background-image', 2833 ); 2834 2835 if ( empty( $allowed_attr ) ) { 2836 return $css; 2837 } 2838 2839 $css = ''; 2840 foreach ( $css_array as $css_item ) { 2841 if ( '' === $css_item ) { 2842 continue; 2843 } 2844 2845 $css_item = trim( $css_item ); 2846 $css_test_string = $css_item; 2847 $found = false; 2848 $url_attr = false; 2849 $gradient_attr = false; 2850 $is_custom_var = false; 2851 2852 if ( ! str_contains( $css_item, ':' ) ) { 2853 $found = true; 2854 } else { 2855 $parts = explode( ':', $css_item, 2 ); 2856 $css_selector = trim( $parts[0] ); 2857 2858 // Allow assigning values to CSS variables. 2859 if ( in_array( '--*', $allowed_attr, true ) && preg_match( '/^--[a-zA-Z0-9-_]+$/', $css_selector ) ) { 2860 $allowed_attr[] = $css_selector; 2861 $is_custom_var = true; 2862 } 2863 2864 if ( in_array( $css_selector, $allowed_attr, true ) ) { 2865 $found = true; 2866 $url_attr = in_array( $css_selector, $css_url_data_types, true ); 2867 $gradient_attr = in_array( $css_selector, $css_gradient_data_types, true ); 2868 } 2869 2870 if ( $is_custom_var ) { 2871 $css_value = trim( $parts[1] ); 2872 $url_attr = str_starts_with( $css_value, 'url(' ); 2873 $gradient_attr = str_contains( $css_value, '-gradient(' ); 2874 } 2875 } 2876 2877 if ( $found && $url_attr ) { 2878 // Simplified: matches the sequence `url(*)`. 2879 preg_match_all( '/url\([^)]+\)/', $parts[1], $url_matches ); 2880 2881 foreach ( $url_matches[0] as $url_match ) { 2882 // Clean up the URL from each of the matches above. 2883 preg_match( '/^url\(\s*([\'\"]?)(.*)(\g1)\s*\)$/', $url_match, $url_pieces ); 2884 2885 if ( empty( $url_pieces[2] ) ) { 2886 $found = false; 2887 break; 2888 } 2889 2890 $url = trim( $url_pieces[2] ); 2891 2892 if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) { 2893 $found = false; 2894 break; 2895 } else { 2896 // Remove the whole `url(*)` bit that was matched above from the CSS. 2897 $css_test_string = str_replace( $url_match, '', $css_test_string ); 2898 } 2899 } 2900 } 2901 2902 if ( $found && $gradient_attr ) { 2903 $css_value = trim( $parts[1] ); 2904 if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient\(([^()]|rgb[a]?\([^()]*\))*\)$/', $css_value ) ) { 2905 // Remove the whole `gradient` bit that was matched above from the CSS. 2906 $css_test_string = str_replace( $css_value, '', $css_test_string ); 2907 } 2908 } 2909 2910 if ( $found ) { 2911 /* 2912 * Allow CSS functions like var(), calc(), etc. by removing them from the test string. 2913 * Nested functions and parentheses are also removed, so long as the parentheses are balanced. 2914 */ 2915 $css_test_string = preg_replace( 2916 '/\b(?:var|calc|min|max|minmax|clamp|repeat)(\((?:[^()]|(?1))*\))/', 2917 '', 2918 $css_test_string 2919 ); 2920 2921 /* 2922 * Disallow CSS containing \ ( & } = or comments, except for within url(), var(), calc(), etc. 2923 * which were removed from the test string above. 2924 */ 2925 $allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string ); 2926 2927 /** 2928 * Filters the check for unsafe CSS in `safecss_filter_attr`. 2929 * 2930 * Enables developers to determine whether a section of CSS should be allowed or discarded. 2931 * By default, the value will be false if the part contains \ ( & } = or comments. 2932 * Return true to allow the CSS part to be included in the output. 2933 * 2934 * @since 5.5.0 2935 * 2936 * @param bool $allow_css Whether the CSS in the test string is considered safe. 2937 * @param string $css_test_string The CSS string to test. 2938 */ 2939 $allow_css = apply_filters( 'safecss_filter_attr_allow_css', $allow_css, $css_test_string ); 2940 2941 // Only add the CSS part if it passes the regex check. 2942 if ( $allow_css ) { 2943 if ( '' !== $css ) { 2944 $css .= ';'; 2945 } 2946 2947 $css .= $css_item; 2948 } 2949 } 2950 } 2951 2952 return $css; 2953 } 2954 2955 /** 2956 * Helper function to add global attributes to a tag in the allowed HTML list. 2957 * 2958 * @since 3.5.0 2959 * @since 5.0.0 Added support for `data-*` wildcard attributes. 2960 * @since 6.0.0 Added `dir`, `lang`, and `xml:lang` to global attributes. 2961 * @since 6.3.0 Added `aria-controls`, `aria-current`, and `aria-expanded` attributes. 2962 * @since 6.4.0 Added `aria-live` and `hidden` attributes. 2963 * 2964 * @access private 2965 * @ignore 2966 * 2967 * @param array $value An array of attributes. 2968 * @return array The array of attributes with global attributes added. 2969 */ 2970 function _wp_add_global_attributes( $value ) { 2971 $global_attributes = array( 2972 'aria-controls' => true, 2973 'aria-current' => true, 2974 'aria-describedby' => true, 2975 'aria-details' => true, 2976 'aria-expanded' => true, 2977 'aria-hidden' => true, 2978 'aria-label' => true, 2979 'aria-labelledby' => true, 2980 'aria-live' => true, 2981 'class' => true, 2982 'data-*' => true, 2983 'dir' => true, 2984 'hidden' => true, 2985 'id' => true, 2986 'lang' => true, 2987 'style' => true, 2988 'title' => true, 2989 'role' => true, 2990 'xml:lang' => true, 2991 ); 2992 2993 if ( true === $value ) { 2994 $value = array(); 2995 } 2996 2997 if ( is_array( $value ) ) { 2998 return array_merge( $value, $global_attributes ); 2999 } 3000 3001 return $value; 3002 } 3003 3004 /** 3005 * Helper function to check if this is a safe PDF URL. 3006 * 3007 * @since 5.9.0 3008 * @access private 3009 * @ignore 3010 * 3011 * @param string $url The URL to check. 3012 * @return bool True if the URL is safe, false otherwise. 3013 */ 3014 function _wp_kses_allow_pdf_objects( $url ) { 3015 // We're not interested in URLs that contain query strings or fragments. 3016 if ( str_contains( $url, '?' ) || str_contains( $url, '#' ) ) { 3017 return false; 3018 } 3019 3020 // If it doesn't have a PDF extension, it's not safe. 3021 if ( ! str_ends_with( $url, '.pdf' ) ) { 3022 return false; 3023 } 3024 3025 // If the URL host matches the current site's media URL, it's safe. 3026 $upload_info = wp_upload_dir( null, false ); 3027 $parsed_url = wp_parse_url( $upload_info['url'] ); 3028 $upload_host = $parsed_url['host'] ?? ''; 3029 $upload_port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : ''; 3030 3031 if ( str_starts_with( $url, "http://$upload_host$upload_port/" ) 3032 || str_starts_with( $url, "https://$upload_host$upload_port/" ) 3033 ) { 3034 return true; 3035 } 3036 3037 return false; 3038 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Jun 28 08:20:12 2026 | Cross-referenced by PHPXref |