[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> kses.php (source)

   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 '&gt;';
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=\'&lt;img&#00062\' =/🐮=/' );
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' => '&lt;img&gt;', 'whole' => 'data-lazy="&lt;img&gt;"', '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          '&' => '&amp;',
1639          '<' => '&lt;',
1640          '>' => '&gt;',
1641          "'" => '&apos;',
1642          '"' => '&quot;',
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( '/(&#0*58(?![;0-9])|&#x0*3a(?![;a-f0-9]))/i', '$1;', $content );
2018      $content2 = preg_split( '/:|&#0*58;|&#x0*3a;|&colon;/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&amp;T`, `&#00058;` to `&#058;`, `&#XYZZY;` to `&amp;#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&hellip;&#XYZZY;` is converted to `AT&amp;T…&amp;#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 &amp;
2087      $content = str_replace( '&', '&amp;', $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 `&amp;`. Double-encoded named character
2095       * references like `&amp;amp;` will be decoded back to their single-encoded form `&amp;`.
2096       *
2097       * First, numeric (decimal and hexadecimal) character references must be handled so that
2098       * `&amp;#09;` becomes `&#9;`. 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;`     | `&amp;#x2E;`     | `&amp;#x2E;`              | `&#x2E;`                   |
2109       * | `&amp;#x2E;` | `&amp;amp;#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;`     | `&amp;#x2E;`     | `&#x2E;`                    | `&#x2E;`                 |
2117       * | `&amp;#x2E;` | `&amp;amp;#x2E;` | `&amp;amp;#x2E;`            | `&amp;#x2E;`             |
2118       *
2119       * Here, each input is normalized to an appropriate output.
2120       */
2121      $content = preg_replace_callback( '/&amp;#(0*[1-9][0-9]{0,6});/', 'wp_kses_normalize_entities2', $content );
2122      $content = preg_replace_callback( '/&amp;#[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( '/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_xml_named_entities', $content );
2125      } else {
2126          $content = preg_replace_callback( '/&amp;([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 ) ) ? "&amp;$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 "&amp;$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 = "&amp;#$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 ) ) ) ? "&amp;#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 (`&#65;` and `&#x41;`).
2284   * It doesn't do anything with named entities like `&auml;`, 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  }


Generated : Sun Jun 28 08:20:12 2026 Cross-referenced by PHPXref