jetpack
/
vendor
/
wp-php-toolkit
/
data-liberation
/
BlockMarkup
/
class-blockmarkupurlprocessor.php
class-blockmarkupprocessor.php
1 week ago
class-blockmarkupurlprocessor.php
1 week ago
class-blockobject.php
1 week ago
class-blockmarkupurlprocessor.php
543 lines
| 1 | <?php |
| 2 | |
| 3 | namespace WordPress\DataLiberation\BlockMarkup; |
| 4 | |
| 5 | use Rowbot\URL\URL; |
| 6 | use WordPress\DataLiberation\URL\URLInTextProcessor; |
| 7 | use WordPress\DataLiberation\URL\CSSURLProcessor; |
| 8 | use WordPress\DataLiberation\URL\WPURL; |
| 9 | |
| 10 | /** |
| 11 | * Reports all the URLs in the imported post and enables rewriting them. |
| 12 | */ |
| 13 | class BlockMarkupUrlProcessor extends BlockMarkupProcessor { |
| 14 | |
| 15 | private $raw_url; |
| 16 | /** |
| 17 | * @var URL |
| 18 | */ |
| 19 | private $parsed_url; |
| 20 | private $base_url_string; |
| 21 | private $base_url_object; |
| 22 | private $url_in_text_processor; |
| 23 | private $url_in_text_node_updated; |
| 24 | private $css_url_processor; |
| 25 | private $css_url_processor_updated; |
| 26 | |
| 27 | /** |
| 28 | * The list of names of URL-related HTML attributes that may be available on |
| 29 | * the current token. They will be inspected by next_url_attribute(). |
| 30 | * |
| 31 | * Possible values: |
| 32 | * |
| 33 | * - null: We haven't inspected any attribute yet. |
| 34 | * - array: The first element is the currently inspected attribute |
| 35 | * and the rest of the list are elements yet to be inspected on |
| 36 | * the upcoming next_url_attribute() call. |
| 37 | * - empty array: We've already inspected all the URL-related attributes. |
| 38 | * |
| 39 | * @var array<string>|null |
| 40 | */ |
| 41 | private $inspecting_html_attributes; |
| 42 | |
| 43 | public function __construct( $html, ?string $base_url_string = null ) { |
| 44 | parent::__construct( $html ); |
| 45 | $this->base_url_string = $base_url_string; |
| 46 | $this->base_url_object = $base_url_string ? WPURL::parse( $base_url_string ) : null; |
| 47 | } |
| 48 | |
| 49 | public function get_updated_html(): string { |
| 50 | if ( $this->url_in_text_node_updated ) { |
| 51 | $this->set_modifiable_text( $this->url_in_text_processor->get_updated_text() ); |
| 52 | $this->url_in_text_node_updated = false; |
| 53 | } |
| 54 | |
| 55 | if ( $this->css_url_processor_updated ) { |
| 56 | if ( null !== $this->css_url_processor ) { |
| 57 | $updated_css = $this->css_url_processor->get_updated_css(); |
| 58 | $this->set_attribute( 'style', $updated_css ); |
| 59 | } |
| 60 | $this->css_url_processor_updated = false; |
| 61 | } |
| 62 | |
| 63 | return parent::get_updated_html(); |
| 64 | } |
| 65 | |
| 66 | public function get_raw_url() { |
| 67 | return $this->raw_url; |
| 68 | } |
| 69 | |
| 70 | public function get_parsed_url() { |
| 71 | return $this->parsed_url; |
| 72 | } |
| 73 | |
| 74 | public function next_token(): bool { |
| 75 | $this->get_updated_html(); |
| 76 | |
| 77 | $this->raw_url = null; |
| 78 | $this->parsed_url = null; |
| 79 | $this->inspecting_html_attributes = null; |
| 80 | $this->url_in_text_processor = null; |
| 81 | $this->css_url_processor = null; |
| 82 | /* |
| 83 | * Do not reset url_in_text_node_updated or css_url_processor_updated – they're reset |
| 84 | * in get_updated_html() which is called in parent::next_token(). |
| 85 | */ |
| 86 | |
| 87 | return parent::next_token(); |
| 88 | } |
| 89 | |
| 90 | public function next_url() { |
| 91 | do { |
| 92 | if ( $this->next_url_in_current_token() ) { |
| 93 | return true; |
| 94 | } |
| 95 | } while ( false !== $this->next_token() ); |
| 96 | |
| 97 | return false; |
| 98 | } |
| 99 | |
| 100 | public function next_url_in_current_token() { |
| 101 | $this->raw_url = null; |
| 102 | switch ( parent::get_token_type() ) { |
| 103 | case '#tag': |
| 104 | return $this->next_url_attribute(); |
| 105 | case '#block-comment': |
| 106 | return $this->next_url_block_attribute(); |
| 107 | case '#text': |
| 108 | return $this->next_url_in_text_node(); |
| 109 | default: |
| 110 | return false; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | private function next_url_in_text_node() { |
| 115 | if ( '#text' !== $this->get_token_type() ) { |
| 116 | return false; |
| 117 | } |
| 118 | |
| 119 | if ( null === $this->url_in_text_processor ) { |
| 120 | /* |
| 121 | * Use the base URL for URLs matched in text nodes. This is the only |
| 122 | * way to recognize a substring "WordPress.org" as a URL. We might |
| 123 | * get some false positives this way, e.g. in this string: |
| 124 | * |
| 125 | * > And that's how you build a theme. Now let's take a look at..." |
| 126 | * |
| 127 | * `theme.Now` would be recognized as a URL. It's up to the API consumer |
| 128 | * to filter out such false positives e.g. by checking the domain against |
| 129 | * a list of accepted domains, or the TLD against a list of public suffixes. |
| 130 | */ |
| 131 | $this->url_in_text_processor = new URLInTextProcessor( $this->get_modifiable_text(), $this->base_url_string ); |
| 132 | } |
| 133 | |
| 134 | while ( $this->url_in_text_processor->next_url() ) { |
| 135 | $this->raw_url = $this->url_in_text_processor->get_raw_url(); |
| 136 | $this->parsed_url = $this->url_in_text_processor->get_parsed_url(); |
| 137 | |
| 138 | return true; |
| 139 | } |
| 140 | |
| 141 | return false; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Advances to the next CSS URL in the `style` attribute of the current tag token. |
| 146 | * |
| 147 | * @return bool Whether a CSS URL was found. |
| 148 | */ |
| 149 | private function next_url_in_css() { |
| 150 | if ( '#tag' !== $this->get_token_type() ) { |
| 151 | return false; |
| 152 | } |
| 153 | |
| 154 | if ( null === $this->css_url_processor ) { |
| 155 | $css_value = $this->get_attribute( 'style' ); |
| 156 | if ( ! is_string( $css_value ) ) { |
| 157 | return false; |
| 158 | } |
| 159 | |
| 160 | $this->css_url_processor = new CSSURLProcessor( $css_value ); |
| 161 | } |
| 162 | |
| 163 | while ( $this->css_url_processor->next_url() ) { |
| 164 | /** |
| 165 | * Skip data URIs. They may be really large and they don't |
| 166 | * have a hostname to migrate. |
| 167 | */ |
| 168 | if ( $this->css_url_processor->is_data_uri() ) { |
| 169 | continue; |
| 170 | } |
| 171 | $this->raw_url = $this->css_url_processor->get_raw_url(); |
| 172 | $this->parsed_url = WPURL::parse( $this->raw_url, $this->base_url_string ); |
| 173 | if ( false === $this->parsed_url ) { |
| 174 | continue; |
| 175 | } |
| 176 | |
| 177 | return true; |
| 178 | } |
| 179 | |
| 180 | return false; |
| 181 | } |
| 182 | |
| 183 | private function next_url_attribute() { |
| 184 | $tag = $this->get_tag(); |
| 185 | |
| 186 | // Check if we have a style attribute with CSS URLs to process. |
| 187 | if ( null !== $this->css_url_processor ) { |
| 188 | if ( $this->next_url_in_css() ) { |
| 189 | return true; |
| 190 | } |
| 191 | // Done with CSS URLs in this attribute, apply any pending updates and move on. |
| 192 | $this->get_updated_html(); |
| 193 | $this->css_url_processor = null; |
| 194 | } |
| 195 | |
| 196 | if ( null === $this->inspecting_html_attributes ) { |
| 197 | if ( array_key_exists( $tag, self::HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM ) ) { |
| 198 | /** |
| 199 | * Initialize the list on the first call to next_url_attribute() |
| 200 | * for the current token. The last element is the attribute we'll |
| 201 | * inspect in the while() loop below. |
| 202 | */ |
| 203 | $this->inspecting_html_attributes = self::HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $tag ]; |
| 204 | // Add style attribute to the list if it exists. |
| 205 | if ( null !== $this->get_attribute( 'style' ) ) { |
| 206 | $this->inspecting_html_attributes[] = 'style'; |
| 207 | } |
| 208 | } elseif ( null !== $this->get_attribute( 'style' ) ) { |
| 209 | $this->inspecting_html_attributes = array( 'style' ); |
| 210 | } else { |
| 211 | return false; |
| 212 | } |
| 213 | } else { |
| 214 | /** |
| 215 | * Forget the attribute we've inspected on the previous call to |
| 216 | * next_url_attribute(). |
| 217 | */ |
| 218 | array_pop( $this->inspecting_html_attributes ); |
| 219 | } |
| 220 | |
| 221 | while ( count( $this->inspecting_html_attributes ) > 0 ) { |
| 222 | $attr = $this->inspecting_html_attributes[ count( $this->inspecting_html_attributes ) - 1 ]; |
| 223 | $url_maybe = $this->get_attribute( $attr ); |
| 224 | if ( ! is_string( $url_maybe ) ) { |
| 225 | array_pop( $this->inspecting_html_attributes ); |
| 226 | continue; |
| 227 | } |
| 228 | |
| 229 | // Rewrite any CSS `url()` declarations in the `style` attribute. |
| 230 | if ( 'style' === $attr ) { |
| 231 | $this->css_url_processor = new CSSURLProcessor( $url_maybe ); |
| 232 | if ( $this->next_url_in_css() ) { |
| 233 | return true; |
| 234 | } |
| 235 | // No CSS URLs found, move to next attribute. |
| 236 | $this->css_url_processor = null; |
| 237 | array_pop( $this->inspecting_html_attributes ); |
| 238 | continue; |
| 239 | } |
| 240 | |
| 241 | /* |
| 242 | * Use base URL to resolve known URI attributes as we are certain we're |
| 243 | * dealing with URI values. |
| 244 | * With a base URL, the string "plugins.php" in <a href="plugins.php"> will |
| 245 | * be correctly recognized as a URL. |
| 246 | * Without a base URL, this Processor would incorrectly skip it. |
| 247 | */ |
| 248 | $parsed_url = WPURL::parse( $url_maybe, $this->base_url_string ); |
| 249 | |
| 250 | if ( false === $parsed_url ) { |
| 251 | array_pop( $this->inspecting_html_attributes ); |
| 252 | continue; |
| 253 | } |
| 254 | $this->raw_url = $url_maybe; |
| 255 | $this->parsed_url = $parsed_url; |
| 256 | |
| 257 | return true; |
| 258 | } |
| 259 | |
| 260 | return false; |
| 261 | } |
| 262 | |
| 263 | private function next_url_block_attribute() { |
| 264 | while ( $this->next_block_attribute() ) { |
| 265 | $url_maybe = $this->get_block_attribute_value(); |
| 266 | if ( ! is_string( $url_maybe ) || |
| 267 | count( $this->get_block_attribute_path() ) > 1 |
| 268 | ) { |
| 269 | // @TODO: support arrays, objects, and other non-string data structures. |
| 270 | continue; |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Decide whether the current block attribute holds a URL. |
| 275 | * |
| 276 | * Known URL attributes can be assumed to hold a URL and be |
| 277 | * parsed with the base URL. For example, a "/about-us" value |
| 278 | * in a wp:navigation-link block's `url` attribute is a |
| 279 | * relative URL to the `/about-us` page. |
| 280 | * |
| 281 | * Other attributes may or may not contain URLs, but we cannot assume |
| 282 | * they do. A value `/about-us` could be a relative URL or a class name. |
| 283 | * In those cases, we'll let go of relative URLs and only detect |
| 284 | * absolute URLs to avoid treating every string as a URL. This requires |
| 285 | * parsing without a base URL. |
| 286 | */ |
| 287 | $is_relative_url_block_attribute = ( |
| 288 | isset( self::BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $this->get_block_name() ] ) && |
| 289 | in_array( $this->get_block_attribute_key(), self::BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM[ $this->get_block_name() ], true ) |
| 290 | ); |
| 291 | |
| 292 | /** |
| 293 | * Filters whether a block attribute is known to contain a relative URL. |
| 294 | * |
| 295 | * This filter allows extending the list of block attributes that are |
| 296 | * recognized as containing URLs. When a block attribute is marked as |
| 297 | * a known URL attribute, it will be parsed with the base URL, allowing |
| 298 | * relative URLs to be properly resolved. |
| 299 | * |
| 300 | * @since 6.8.0 |
| 301 | * |
| 302 | * @param bool $is_relative_url_block_attribute Whether the block attribute is known to contain a relative URL. |
| 303 | * @param array $context { |
| 304 | * Context information about the block attribute. |
| 305 | * |
| 306 | * @type string $block_name The name of the block (e.g., 'wp:image', 'wp:button'). |
| 307 | * @type string $attribute_name The name of the attribute (e.g., 'url', 'href'). |
| 308 | * } |
| 309 | */ |
| 310 | $is_relative_url_block_attribute = apply_filters( |
| 311 | 'url_processor_is_relative_url_block_attribute', |
| 312 | $is_relative_url_block_attribute, |
| 313 | array( |
| 314 | 'block_name' => $this->get_block_name(), |
| 315 | 'attribute_name' => $this->get_block_attribute_key(), |
| 316 | ) |
| 317 | ); |
| 318 | |
| 319 | $parsed_url = false; |
| 320 | if ( $is_relative_url_block_attribute ) { |
| 321 | // Known relative URL attribute – let's parse with the base URL. |
| 322 | $parsed_url = WPURL::parse( $url_maybe, $this->base_url_string ); |
| 323 | } else { |
| 324 | // Other attributes – let's parse without a base URL (and only detect absolute URLs). |
| 325 | $parsed_url = WPURL::parse( $url_maybe ); |
| 326 | } |
| 327 | |
| 328 | if ( false === $parsed_url ) { |
| 329 | continue; |
| 330 | } |
| 331 | |
| 332 | $this->raw_url = $url_maybe; |
| 333 | $this->parsed_url = $parsed_url; |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | return false; |
| 338 | } |
| 339 | |
| 340 | /** |
| 341 | * Replaces the currently matched URL with a new one. |
| 342 | * |
| 343 | * @param string $raw_url The raw URL. |
| 344 | * @param URL $parsed_url The parsed version of the raw URL. It is required |
| 345 | * as $raw_url might be a relative URL pointing to a different |
| 346 | * host than this processor's base URL. |
| 347 | * |
| 348 | * @return bool True if the URL was set, false otherwise. |
| 349 | */ |
| 350 | public function set_url( $raw_url, $parsed_url ) { |
| 351 | if ( null === $this->raw_url ) { |
| 352 | return false; |
| 353 | } |
| 354 | $this->raw_url = $raw_url; |
| 355 | $this->parsed_url = $parsed_url; |
| 356 | switch ( parent::get_token_type() ) { |
| 357 | case '#tag': |
| 358 | // Check if we're processing a CSS URL. |
| 359 | if ( null !== $this->css_url_processor ) { |
| 360 | $this->css_url_processor_updated = true; |
| 361 | return $this->css_url_processor->set_raw_url( $raw_url ); |
| 362 | } |
| 363 | |
| 364 | $attr = $this->get_inspected_attribute_name(); |
| 365 | if ( false === $attr ) { |
| 366 | return false; |
| 367 | } |
| 368 | $this->set_attribute( $attr, $raw_url ); |
| 369 | |
| 370 | return true; |
| 371 | |
| 372 | case '#block-comment': |
| 373 | return $this->set_block_attribute_value( $raw_url ); |
| 374 | |
| 375 | case '#text': |
| 376 | if ( null === $this->url_in_text_processor ) { |
| 377 | return false; |
| 378 | } |
| 379 | $this->url_in_text_node_updated = true; |
| 380 | |
| 381 | return $this->url_in_text_processor->set_raw_url( $raw_url ); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | /** |
| 386 | * Rewrites the components of the currently matched URL from ones |
| 387 | * provided in $from_url to ones specified in $to_url. |
| 388 | * |
| 389 | * It preserves the relative nature of the matched URL. |
| 390 | * |
| 391 | * @TODO: Should this method live in this class? It's specific to the import process |
| 392 | * and the URL rewriting logic and has knowledge about the quirks of detecting |
| 393 | * relative URLs in text nodes. On the other hand, the detection is performed |
| 394 | * by this WPURL_In_Text_Processor class so maybe the two do go hand in hand? |
| 395 | */ |
| 396 | public function replace_base_url( $to_url, $base_url = null ) { |
| 397 | $base_url = $base_url ?? $this->base_url_object; |
| 398 | if ( ! $base_url ) { |
| 399 | return false; |
| 400 | } |
| 401 | |
| 402 | $result = WPURL::replace_base_url( |
| 403 | $this->get_parsed_url(), |
| 404 | array( |
| 405 | 'old_base_url' => $base_url, |
| 406 | 'new_base_url' => $to_url, |
| 407 | 'raw_url' => $this->get_raw_url(), |
| 408 | 'is_relative' => ( |
| 409 | /** |
| 410 | * In text nodes, the only detected URLs are absolute. The tricky part |
| 411 | * is they may start without a protocol, e.g. `wordpress.org`. Therefore, |
| 412 | * we need to tell WPURL::replace_base_url what's our intention regarding |
| 413 | * the URL's relativity. It cannot just infer it from the URL itself. |
| 414 | */ |
| 415 | '#text' !== $this->get_token_type() && |
| 416 | ! WPURL::can_parse( $this->get_raw_url() ) |
| 417 | ), |
| 418 | ) |
| 419 | ); |
| 420 | |
| 421 | if ( false === $result ) { |
| 422 | return false; |
| 423 | } |
| 424 | |
| 425 | $this->set_url( $result . '', $result->new_url ); |
| 426 | |
| 427 | return true; |
| 428 | } |
| 429 | |
| 430 | /** |
| 431 | * Returns true if the currently matched URL is absolute. |
| 432 | * |
| 433 | * @return bool Whether the currently matched URL is absolute. |
| 434 | */ |
| 435 | public function is_url_absolute() { |
| 436 | return WPURL::can_parse( $this->get_raw_url() ); |
| 437 | } |
| 438 | |
| 439 | public function get_inspected_attribute_name() { |
| 440 | if ( '#tag' !== $this->get_token_type() ) { |
| 441 | return false; |
| 442 | } |
| 443 | |
| 444 | if ( null === $this->inspecting_html_attributes ) { |
| 445 | return false; |
| 446 | } |
| 447 | |
| 448 | if ( empty( $this->inspecting_html_attributes ) ) { |
| 449 | return false; |
| 450 | } |
| 451 | |
| 452 | return $this->inspecting_html_attributes[ count( $this->inspecting_html_attributes ) - 1 ]; |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * A list of block attributes that are known to contain URLs. |
| 457 | * |
| 458 | * It covers WordPress core blocks as of WordPress version 6.9. It can be |
| 459 | * extended by plugins and themes via the "url_processor_is_relative_url_block_attribute" |
| 460 | * filter. |
| 461 | * |
| 462 | * @var array |
| 463 | */ |
| 464 | public const BLOCK_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM = array( |
| 465 | 'wp:button' => array( 'url', 'linkTarget' ), |
| 466 | 'wp:cover' => array( 'url' ), |
| 467 | 'wp:embed' => array( 'url' ), |
| 468 | 'wp:gallery' => array( 'url', 'fullUrl' ), |
| 469 | 'wp:image' => array( 'url', 'src', 'href' ), |
| 470 | 'wp:media-text' => array( 'mediaUrl', 'href' ), |
| 471 | 'wp:navigation-link' => array( 'url' ), |
| 472 | 'wp:navigation-submenu' => array( 'url' ), |
| 473 | 'wp:rss' => array( 'feedURL' ), |
| 474 | ); |
| 475 | |
| 476 | /** |
| 477 | * A list of HTML attributes meant to contain URLs, as defined in the HTML specification. |
| 478 | * It includes some deprecated attributes like `lowsrc` and `highsrc` for the `IMG` element. |
| 479 | * |
| 480 | * See https://html.spec.whatwg.org/multipage/indices.html#attributes-1. |
| 481 | * See https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value. |
| 482 | */ |
| 483 | public const HTML_ATTRIBUTES_TO_ACCEPT_RELATIVE_URLS_FROM = array( |
| 484 | 'A' => array( 'href' ), |
| 485 | 'APPLET' => array( 'codebase', 'archive' ), |
| 486 | 'AREA' => array( 'href' ), |
| 487 | 'AUDIO' => array( 'src' ), |
| 488 | 'BASE' => array( 'href' ), |
| 489 | 'BLOCKQUOTE' => array( 'cite' ), |
| 490 | 'BODY' => array( 'background' ), |
| 491 | 'BUTTON' => array( 'formaction' ), |
| 492 | 'COMMAND' => array( 'icon' ), |
| 493 | 'DEL' => array( 'cite' ), |
| 494 | 'EMBED' => array( 'src' ), |
| 495 | 'FORM' => array( 'action' ), |
| 496 | 'FRAME' => array( 'longdesc', 'src' ), |
| 497 | 'HEAD' => array( 'profile' ), |
| 498 | 'HTML' => array( 'manifest' ), |
| 499 | 'IFRAME' => array( 'longdesc', 'src' ), |
| 500 | // SVG <image> element. |
| 501 | 'IMAGE' => array( 'href' ), |
| 502 | 'IMG' => array( 'longdesc', 'src', 'usemap', 'lowsrc', 'highsrc' ), |
| 503 | 'INPUT' => array( 'src', 'usemap', 'formaction' ), |
| 504 | 'INS' => array( 'cite' ), |
| 505 | 'LINK' => array( 'href' ), |
| 506 | 'OBJECT' => array( 'classid', 'codebase', 'data', 'usemap' ), |
| 507 | 'Q' => array( 'cite' ), |
| 508 | 'SCRIPT' => array( 'src' ), |
| 509 | 'SOURCE' => array( 'src' ), |
| 510 | 'TRACK' => array( 'src' ), |
| 511 | 'VIDEO' => array( 'poster', 'src' ), |
| 512 | ); |
| 513 | |
| 514 | /** |
| 515 | * @TODO: Either explicitly support these attributes, or explicitly drop support for |
| 516 | * handling their subsyntax. A generic URL matcher might be good enough. |
| 517 | */ |
| 518 | public const HTML_ATTRIBUTES_WITH_SUBSYNTAX_TO_ACCEPT_RELATIVE_URLS_FROM = array( |
| 519 | '*' => array( 'style' ), // background(), background-image(). |
| 520 | 'APPLET' => array( 'archive' ), |
| 521 | 'IMG' => array( 'srcset' ), |
| 522 | 'META' => array( 'content' ), |
| 523 | 'SOURCE' => array( 'srcset' ), |
| 524 | 'OBJECT' => array( 'archive' ), |
| 525 | ); |
| 526 | |
| 527 | /** |
| 528 | * Also <style> and <script> tag content can contain URLs. |
| 529 | * <style> has specific syntax rules we can use for matching, but perhaps a generic matcher would be good enough? |
| 530 | * |
| 531 | * <style> |
| 532 | * #domID { background:url(https://mysite.com/wp-content/uploads/image.png) } |
| 533 | * </style> |
| 534 | * |
| 535 | * @TODO: Either explicitly support these tags, or explicitly drop support for |
| 536 | * handling their subsyntax. A generic URL matcher might be good enough. |
| 537 | */ |
| 538 | public const HTML_TAGS_WITH_SUBSYNTAX_TO_ACCEPT_RELATIVE_URLS_FROM = array( |
| 539 | 'STYLE', |
| 540 | 'SCRIPT', |
| 541 | ); |
| 542 | } |
| 543 |