| @@ -32,8 +32,52 @@ | ||
| 32 | 32 | */ |
| 33 | 33 | protected $fonts = array(); |
| 34 | 34 | |
| 35 | 35 | /** |
| 36 | + * Handle used for the theme-stylesheet backward-compat fallback (frontend only). | |
| 37 | + * | |
| 38 | + * @var string | |
| 39 | + */ | |
| 40 | + const THEME_STYLESHEET_FALLBACK_HANDLE = 'tablekit-theme-stylesheet-fallback'; | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Max nesting depth when following synced patterns / registered patterns. | |
| 44 | + * | |
| 45 | + * @var int | |
| 46 | + */ | |
| 47 | + const MAX_PATTERN_DEPTH = 10; | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * Keys of table blocks whose CSS has already been collected/output this request. | |
| 51 | + * | |
| 52 | + * @var array<string,bool> | |
| 53 | + */ | |
| 54 | + protected $emitted_blocks = array(); | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * CSS of table blocks rendered after the <head> CSS was finalised, keyed by | |
| 58 | + * block key, waiting to be printed right before that block's markup. | |
| 59 | + * | |
| 60 | + * @var array<string,string> | |
| 61 | + */ | |
| 62 | + protected $pending_late_css = array(); | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * Whether the collected CSS has already been handed to WordPress for <head>. | |
| 66 | + * | |
| 67 | + * @var bool | |
| 68 | + */ | |
| 69 | + protected $head_css_added = false; | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * Nesting counter for renders that print their own CSS (e.g. [tableKit] | |
| 73 | + * shortcode, Elementor widget). While > 0, late CSS is not printed. | |
| 74 | + * | |
| 75 | + * @var int | |
| 76 | + */ | |
| 77 | + protected $late_css_suspended = 0; | |
| 78 | + | |
| 79 | + /** | |
| 36 | 80 | * AssetGenerator class constructor. |
| 37 | 81 | * private for singleton |
| 38 | 82 | * |
| 39 | 83 | * @return void |
| @@ -45,8 +89,10 @@ | ||
| 45 | 89 | add_filter( 'render_block_data', array( $this, 'set_blocks_css' ), 10 ); |
| 46 | 90 | add_filter( 'wp_resource_hints', array( $this, 'fonts_resource_hints' ), 10, 2 ); |
| 47 | 91 | add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10 ); |
| 48 | 92 | add_action( 'enqueue_block_assets', array( $this, 'block_assets' ), 10 ); |
| 93 | + add_action( 'wp_enqueue_scripts', array( $this, 'maybe_dequeue_theme_stylesheet_fallback' ), 999 ); | |
| 94 | + add_filter( 'render_block', array( $this, 'prepend_late_block_css' ), 1000, 2 ); | |
| 49 | 95 | |
| 50 | 96 | // Clear FSE cache when templates are saved. |
| 51 | 97 | add_action( 'save_post_wp_template', array( $this, 'clear_fse_cache' ) ); |
| 52 | 98 | add_action( 'save_post_wp_template_part', array( $this, 'clear_fse_cache' ) ); |
| @@ -346,9 +392,9 @@ | ||
| 346 | 392 | $blocks_css[ $device ] = ''; |
| 347 | 393 | } |
| 348 | 394 | |
| 349 | 395 | if ( is_string( $css ) ) { |
| 350 | - $blocks_css[ $device ] .= $css; | |
| 396 | + $blocks_css[ $device ] .= preg_replace( '/<[^>]*>?/', '', $css ); | |
| 351 | 397 | } |
| 352 | 398 | } |
| 353 | 399 | } |
| 354 | 400 | |
| @@ -361,9 +407,11 @@ | ||
| 361 | 407 | if ( ! isset( $blocks_css[ $device ] ) ) { |
| 362 | 408 | $blocks_css[ $device ] = ''; |
| 363 | 409 | } |
| 364 | 410 | |
| 365 | - $blocks_css[ $device ] .= $css; | |
| 411 | + if ( is_string( $css ) ) { | |
| 412 | + $blocks_css[ $device ] .= preg_replace( '/<[^>]*>?/', '', $css ); | |
| 413 | + } | |
| 366 | 414 | } |
| 367 | 415 | } |
| 368 | 416 | } |
| 369 | 417 | |
| @@ -474,16 +522,287 @@ | ||
| 474 | 522 | * @param array $parsed_block The parsed block data. |
| 475 | 523 | * @return array The modified parsed block data. |
| 476 | 524 | */ |
| 477 | 525 | public function set_blocks_css( $parsed_block ) { |
| 478 | - $css_content = $this->combine_blocks_assets( $parsed_block ); | |
| 479 | - if ( ! empty( $css_content ) ) { | |
| 480 | - $this->css .= $css_content; | |
| 526 | + if ( ! is_array( $parsed_block ) || ! $this->is_table_block( $parsed_block ) ) { | |
| 527 | + return $parsed_block; | |
| 481 | 528 | } |
| 529 | + | |
| 530 | + $key = $this->get_block_key( $parsed_block ); | |
| 531 | + | |
| 532 | + if ( isset( $this->emitted_blocks[ $key ] ) ) { | |
| 533 | + return $parsed_block; | |
| 534 | + } | |
| 535 | + | |
| 536 | + // Before the <head> CSS is finalised (block themes render templates early, | |
| 537 | + // classic themes are pre-collected in enqueue_scripts()): add to the inline CSS. | |
| 538 | + if ( ! $this->head_css_added ) { | |
| 539 | + $this->emitted_blocks[ $key ] = true; | |
| 540 | + | |
| 541 | + $css_content = $this->combine_blocks_assets( $parsed_block ); | |
| 542 | + if ( ! empty( $css_content ) ) { | |
| 543 | + $this->css .= $css_content; | |
| 544 | + } | |
| 545 | + | |
| 546 | + return $parsed_block; | |
| 547 | + } | |
| 548 | + | |
| 549 | + // Rendered after <head> (widgets, archive loops, nested queries...): collect | |
| 550 | + // this block's whole subtree once and print it before the block markup. | |
| 551 | + $late_css = $this->collect_late_tree_css( $parsed_block ); | |
| 552 | + | |
| 553 | + if ( '' !== $late_css && 0 === $this->late_css_suspended ) { | |
| 554 | + $this->pending_late_css[ $key ] = $late_css; | |
| 555 | + } | |
| 556 | + | |
| 482 | 557 | return $parsed_block; |
| 483 | 558 | } |
| 484 | 559 | |
| 485 | 560 | /** |
| 561 | + * Collects CSS for a table block and all its inner table blocks that haven't | |
| 562 | + * been output yet, marking each one as output. | |
| 563 | + * | |
| 564 | + * @param array $parsed_block Parsed block. | |
| 565 | + * @return string Combined CSS. | |
| 566 | + */ | |
| 567 | + protected function collect_late_tree_css( array $parsed_block ): string { | |
| 568 | + $css = ''; | |
| 569 | + | |
| 570 | + if ( $this->is_table_block( $parsed_block ) ) { | |
| 571 | + $key = $this->get_block_key( $parsed_block ); | |
| 572 | + | |
| 573 | + if ( ! isset( $this->emitted_blocks[ $key ] ) ) { | |
| 574 | + $this->emitted_blocks[ $key ] = true; | |
| 575 | + $css .= $this->combine_blocks_assets( $parsed_block ); | |
| 576 | + } | |
| 577 | + } | |
| 578 | + | |
| 579 | + foreach ( $parsed_block['innerBlocks'] ?? array() as $inner_block ) { | |
| 580 | + if ( is_array( $inner_block ) ) { | |
| 581 | + $css .= $this->collect_late_tree_css( $inner_block ); | |
| 582 | + } | |
| 583 | + } | |
| 584 | + | |
| 585 | + return $css; | |
| 586 | + } | |
| 587 | + | |
| 588 | + /** | |
| 589 | + * Prints CSS collected for a late-rendered table block right before its markup. | |
| 590 | + * Hooked to "render_block" at a late priority so other markup filters | |
| 591 | + * (which target the block's first tag) run first. | |
| 592 | + * | |
| 593 | + * @param string $block_content Rendered block HTML. | |
| 594 | + * @param array $parsed_block Parsed block data. | |
| 595 | + * @return string | |
| 596 | + */ | |
| 597 | + public function prepend_late_block_css( $block_content, $parsed_block ) { | |
| 598 | + if ( empty( $this->pending_late_css ) || ! is_array( $parsed_block ) || ! $this->is_table_block( $parsed_block ) ) { | |
| 599 | + return $block_content; | |
| 600 | + } | |
| 601 | + | |
| 602 | + $key = $this->get_block_key( $parsed_block ); | |
| 603 | + | |
| 604 | + if ( ! isset( $this->pending_late_css[ $key ] ) ) { | |
| 605 | + return $block_content; | |
| 606 | + } | |
| 607 | + | |
| 608 | + $css = $this->pending_late_css[ $key ]; | |
| 609 | + unset( $this->pending_late_css[ $key ] ); | |
| 610 | + | |
| 611 | + return '<style id="tablekit-css-' . esc_attr( sanitize_html_class( $key ) ) . '">' . $css . '</style>' . $block_content; | |
| 612 | + } | |
| 613 | + | |
| 614 | + /** | |
| 615 | + * Suspends late CSS printing, for renderers that print their own CSS | |
| 616 | + * (e.g. the [tableKit] shortcode or the Elementor widget). Must be paired | |
| 617 | + * with resume_late_css(). | |
| 618 | + * | |
| 619 | + * @return void | |
| 620 | + */ | |
| 621 | + public function suspend_late_css(): void { | |
| 622 | + ++$this->late_css_suspended; | |
| 623 | + } | |
| 624 | + | |
| 625 | + /** | |
| 626 | + * Resumes late CSS printing after suspend_late_css(). | |
| 627 | + * | |
| 628 | + * @return void | |
| 629 | + */ | |
| 630 | + public function resume_late_css(): void { | |
| 631 | + $this->late_css_suspended = max( 0, $this->late_css_suspended - 1 ); | |
| 632 | + } | |
| 633 | + | |
| 634 | + /** | |
| 635 | + * Whether a parsed block is one of TableKit's blocks. | |
| 636 | + * | |
| 637 | + * @param array $parsed_block Parsed block. | |
| 638 | + * @return bool | |
| 639 | + */ | |
| 640 | + protected function is_table_block( array $parsed_block ): bool { | |
| 641 | + return isset( $parsed_block['blockName'] ) && is_string( $parsed_block['blockName'] ) && false !== strpos( $parsed_block['blockName'], 'tablebuilder' ); | |
| 642 | + } | |
| 643 | + | |
| 644 | + /** | |
| 645 | + * Stable per-request key for a table block (its blockID, or a hash of its attributes). | |
| 646 | + * | |
| 647 | + * @param array $parsed_block Parsed block. | |
| 648 | + * @return string | |
| 649 | + */ | |
| 650 | + protected function get_block_key( array $parsed_block ): string { | |
| 651 | + $block_id = $parsed_block['attrs']['blockID'] ?? ''; | |
| 652 | + | |
| 653 | + if ( is_string( $block_id ) && '' !== $block_id ) { | |
| 654 | + return $parsed_block['blockName'] . ':' . $block_id; | |
| 655 | + } | |
| 656 | + | |
| 657 | + return $parsed_block['blockName'] . ':' . md5( (string) wp_json_encode( $parsed_block['attrs'] ?? array() ) ); | |
| 658 | + } | |
| 659 | + | |
| 660 | + /** | |
| 661 | + * Collects TableKit CSS (and enqueues block assets) for the current post on | |
| 662 | + * classic themes WITHOUT rendering any block. | |
| 663 | + * | |
| 664 | + * IMPORTANT: never render blocks here (no do_blocks, render_block or the_content). Rendering | |
| 665 | + * third-party blocks early triggers their one-time side effects (e.g. Formidable | |
| 666 | + * Forms prints and marks its stylesheet as loaded inside the discarded output, | |
| 667 | + * so forms lose their styling) and processes form submissions twice. | |
| 668 | + * | |
| 669 | + * @param \WP_Post $post Post to collect CSS for. | |
| 670 | + * @return void | |
| 671 | + */ | |
| 672 | + protected function collect_post_css( \WP_Post $post ): void { | |
| 673 | + $content = (string) $post->post_content; | |
| 674 | + | |
| 675 | + if ( '' === $content | |
| 676 | + || ( false === strpos( $content, 'tablebuilder' ) | |
| 677 | + && false === strpos( $content, '<!-- wp:block ' ) | |
| 678 | + && false === strpos( $content, '<!-- wp:pattern ' ) ) | |
| 679 | + ) { | |
| 680 | + return; | |
| 681 | + } | |
| 682 | + | |
| 683 | + $seen = array(); | |
| 684 | + $this->collect_css_from_blocks( parse_blocks( $content ), $seen, 0 ); | |
| 685 | + } | |
| 686 | + | |
| 687 | + /** | |
| 688 | + * Walks a parsed block tree (following synced patterns and registered | |
| 689 | + * patterns) and collects CSS for every TableKit block, in document order. | |
| 690 | + * | |
| 691 | + * @param array $blocks Parsed blocks. | |
| 692 | + * @param array $seen Pattern references already followed (loop guard). | |
| 693 | + * @param int $depth Current pattern nesting depth. | |
| 694 | + * @return void | |
| 695 | + */ | |
| 696 | + protected function collect_css_from_blocks( array $blocks, array &$seen, int $depth ): void { | |
| 697 | + foreach ( $blocks as $block ) { | |
| 698 | + if ( ! is_array( $block ) ) { | |
| 699 | + continue; | |
| 700 | + } | |
| 701 | + | |
| 702 | + $name = $block['blockName'] ?? ''; | |
| 703 | + | |
| 704 | + if ( $this->is_table_block( $block ) ) { | |
| 705 | + $this->set_blocks_css( $block ); | |
| 706 | + $this->enqueue_table_block_assets( $name ); | |
| 707 | + } | |
| 708 | + | |
| 709 | + if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) { | |
| 710 | + $this->collect_css_from_blocks( $block['innerBlocks'], $seen, $depth ); | |
| 711 | + } | |
| 712 | + | |
| 713 | + if ( $depth >= self::MAX_PATTERN_DEPTH ) { | |
| 714 | + continue; | |
| 715 | + } | |
| 716 | + | |
| 717 | + $pattern_content = $this->get_pattern_content( $block, $seen ); | |
| 718 | + | |
| 719 | + if ( '' !== $pattern_content ) { | |
| 720 | + $this->collect_css_from_blocks( parse_blocks( $pattern_content ), $seen, $depth + 1 ); | |
| 721 | + } | |
| 722 | + } | |
| 723 | + } | |
| 724 | + | |
| 725 | + /** | |
| 726 | + * Returns the content of a synced pattern (core/block) or registered pattern | |
| 727 | + * (core/pattern) block, once per request. | |
| 728 | + * | |
| 729 | + * @param array $block Parsed block. | |
| 730 | + * @param array $seen Pattern references already followed (loop guard). | |
| 731 | + * @return string Pattern content, or an empty string. | |
| 732 | + */ | |
| 733 | + protected function get_pattern_content( array $block, array &$seen ): string { | |
| 734 | + $name = $block['blockName'] ?? ''; | |
| 735 | + | |
| 736 | + if ( 'core/block' === $name && ! empty( $block['attrs']['ref'] ) ) { | |
| 737 | + $ref = 'ref:' . absint( $block['attrs']['ref'] ); | |
| 738 | + | |
| 739 | + if ( isset( $seen[ $ref ] ) ) { | |
| 740 | + return ''; | |
| 741 | + } | |
| 742 | + $seen[ $ref ] = true; | |
| 743 | + | |
| 744 | + $pattern = get_post( absint( $block['attrs']['ref'] ) ); | |
| 745 | + | |
| 746 | + if ( $pattern instanceof \WP_Post && 'wp_block' === $pattern->post_type && 'publish' === $pattern->post_status ) { | |
| 747 | + return (string) $pattern->post_content; | |
| 748 | + } | |
| 749 | + | |
| 750 | + return ''; | |
| 751 | + } | |
| 752 | + | |
| 753 | + if ( 'core/pattern' === $name && ! empty( $block['attrs']['slug'] ) && class_exists( '\\WP_Block_Patterns_Registry' ) ) { | |
| 754 | + $slug = 'slug:' . (string) $block['attrs']['slug']; | |
| 755 | + | |
| 756 | + if ( isset( $seen[ $slug ] ) ) { | |
| 757 | + return ''; | |
| 758 | + } | |
| 759 | + $seen[ $slug ] = true; | |
| 760 | + | |
| 761 | + $registered = \WP_Block_Patterns_Registry::get_instance()->get_registered( (string) $block['attrs']['slug'] ); | |
| 762 | + | |
| 763 | + return is_array( $registered ) && ! empty( $registered['content'] ) ? (string) $registered['content'] : ''; | |
| 764 | + } | |
| 765 | + | |
| 766 | + return ''; | |
| 767 | + } | |
| 768 | + | |
| 769 | + /** | |
| 770 | + * Enqueues a TableKit block's registered frontend style/view-script handles | |
| 771 | + * up front, so they still print in <head> exactly as they did when the block | |
| 772 | + * was rendered early (prevents a flash of unstyled table). | |
| 773 | + * | |
| 774 | + * @param string $block_name Block name, e.g. "tablebuilder/table-builder". | |
| 775 | + * @return void | |
| 776 | + */ | |
| 777 | + protected function enqueue_table_block_assets( string $block_name ): void { | |
| 778 | + static $done = array(); | |
| 779 | + | |
| 780 | + if ( isset( $done[ $block_name ] ) ) { | |
| 781 | + return; | |
| 782 | + } | |
| 783 | + $done[ $block_name ] = true; | |
| 784 | + | |
| 785 | + $block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); | |
| 786 | + | |
| 787 | + if ( ! $block_type ) { | |
| 788 | + return; | |
| 789 | + } | |
| 790 | + | |
| 791 | + foreach ( (array) ( $block_type->style_handles ?? array() ) as $handle ) { | |
| 792 | + if ( is_string( $handle ) && wp_style_is( $handle, 'registered' ) ) { | |
| 793 | + wp_enqueue_style( $handle ); | |
| 794 | + } | |
| 795 | + } | |
| 796 | + | |
| 797 | + foreach ( (array) ( $block_type->view_script_handles ?? array() ) as $handle ) { | |
| 798 | + if ( is_string( $handle ) && wp_script_is( $handle, 'registered' ) ) { | |
| 799 | + wp_enqueue_script( $handle ); | |
| 800 | + } | |
| 801 | + } | |
| 802 | + } | |
| 803 | + | |
| 804 | + /** | |
| 486 | 805 | * Adds a preconnect resource hint for Google Fonts. |
| 487 | 806 | * |
| 488 | 807 | * @param array $urls URLs to print for resource hints. |
| 489 | 808 | * @param string $relation_type The relation type the URLs are printed. |
| @@ -508,10 +827,12 @@ | ||
| 508 | 827 | */ |
| 509 | 828 | public function enqueue_scripts() { |
| 510 | 829 | global $post; |
| 511 | 830 | |
| 831 | + // Classic themes render the post after <head>, so collect its TableKit CSS | |
| 832 | + // now by reading block attributes only, never by rendering (see collect_post_css()). | |
| 512 | 833 | if ( ! wp_is_block_theme() && $post instanceof \WP_Post && ! empty( $post->post_content ) ) { |
| 513 | - do_blocks( $post->post_content ); | |
| 834 | + $this->collect_post_css( $post ); | |
| 514 | 835 | } |
| 515 | 836 | |
| 516 | 837 | $fonts_url = $this->generate_fonts_url(); |
| 517 | 838 | if ( $fonts_url ) { |
| @@ -519,16 +840,137 @@ | ||
| 519 | 840 | } |
| 520 | 841 | |
| 521 | 842 | if ( $this->css ) { |
| 522 | 843 | wp_add_inline_style( 'table-builder-style', $this->css ); |
| 844 | + $this->print_inline_css_after_block_styles(); | |
| 523 | 845 | } |
| 846 | + | |
| 847 | + $this->head_css_added = true; | |
| 524 | 848 | } |
| 525 | 849 | |
| 526 | 850 | /** |
| 851 | + * Makes TableKit's per-block inline CSS print AFTER the TableKit block | |
| 852 | + * stylesheets (style-index.css) that are enqueued on this page. | |
| 853 | + * | |
| 854 | + * The per-block CSS generated from block settings uses selectors with the | |
| 855 | + * same specificity as the block's base stylesheet, so it only wins when it | |
| 856 | + * comes later in the page. Classic themes (e.g. Kadence) enqueue things in | |
| 857 | + * an order where the inline CSS was printed first, so user-chosen colours, | |
| 858 | + * paddings etc. were silently overridden by the defaults. | |
| 859 | + * | |
| 860 | + * @return void | |
| 861 | + */ | |
| 862 | + protected function print_inline_css_after_block_styles(): void { | |
| 863 | + $styles = wp_styles(); | |
| 864 | + | |
| 865 | + if ( empty( $styles->registered['table-builder-style'] ) ) { | |
| 866 | + return; | |
| 867 | + } | |
| 868 | + | |
| 869 | + $deps = array(); | |
| 870 | + | |
| 871 | + foreach ( \WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_name => $block_type ) { | |
| 872 | + if ( false === strpos( (string) $block_name, 'tablebuilder' ) ) { | |
| 873 | + continue; | |
| 874 | + } | |
| 875 | + | |
| 876 | + foreach ( (array) ( $block_type->style_handles ?? array() ) as $handle ) { | |
| 877 | + if ( is_string( $handle ) && 'table-builder-style' !== $handle && wp_style_is( $handle, 'enqueued' ) ) { | |
| 878 | + $deps[] = $handle; | |
| 879 | + } | |
| 880 | + } | |
| 881 | + } | |
| 882 | + | |
| 883 | + if ( empty( $deps ) ) { | |
| 884 | + return; | |
| 885 | + } | |
| 886 | + | |
| 887 | + $style = $styles->registered['table-builder-style']; | |
| 888 | + $style->deps = array_values( array_unique( array_merge( (array) $style->deps, $deps ) ) ); | |
| 889 | + } | |
| 890 | + | |
| 891 | + /** | |
| 527 | 892 | * Enqueues block assets (CSS & JS). |
| 528 | 893 | * |
| 529 | 894 | * @return void |
| 530 | 895 | */ |
| 531 | 896 | public function block_assets() { |
| 532 | - wp_enqueue_style( 'table-builder-style', get_stylesheet_uri(), array(), TABLE_BUILDER_BLOCK_PLUGIN_VERSION ); | |
| 897 | + // Block editor: unchanged behaviour. | |
| 898 | + if ( is_admin() ) { | |
| 899 | + wp_enqueue_style( 'table-builder-style', get_stylesheet_uri(), array(), TABLE_BUILDER_BLOCK_PLUGIN_VERSION ); | |
| 900 | + return; | |
| 901 | + } | |
| 902 | + | |
| 903 | + /** | |
| 904 | + * Frontend backward-compat: some (child) themes never enqueue their own | |
| 905 | + * style.css and may have relied on TableKit loading it. It is enqueued here | |
| 906 | + * (same position as before) and dequeued again in | |
| 907 | + * maybe_dequeue_theme_stylesheet_fallback() when the theme loads it itself, | |
| 908 | + * so it is never printed twice. | |
| 909 | + * | |
| 910 | + * @since 2.2.15 | |
| 911 | + * | |
| 912 | + * @param bool $load Whether to load the theme stylesheet as a fallback. Default true. | |
| 913 | + */ | |
| 914 | + if ( apply_filters( 'tablekit_load_theme_stylesheet_fallback', true ) ) { | |
| 915 | + wp_enqueue_style( self::THEME_STYLESHEET_FALLBACK_HANDLE, get_stylesheet_uri(), array(), TABLE_BUILDER_BLOCK_PLUGIN_VERSION ); | |
| 916 | + } | |
| 917 | + | |
| 918 | + // Source-less handle that only carries TableKit's inline CSS. | |
| 919 | + wp_register_style( 'table-builder-style', false, array(), TABLE_BUILDER_BLOCK_PLUGIN_VERSION ); | |
| 920 | + wp_enqueue_style( 'table-builder-style' ); | |
| 921 | + } | |
| 922 | + | |
| 923 | + /** | |
| 924 | + * Dequeues the theme-stylesheet fallback when the theme (or anything else) | |
| 925 | + * already enqueues the same stylesheet, directly or as a dependency. | |
| 926 | + * | |
| 927 | + * @return void | |
| 928 | + */ | |
| 929 | + public function maybe_dequeue_theme_stylesheet_fallback() { | |
| 930 | + if ( is_admin() || ! wp_style_is( self::THEME_STYLESHEET_FALLBACK_HANDLE, 'enqueued' ) ) { | |
| 931 | + return; | |
| 932 | + } | |
| 933 | + | |
| 934 | + $target = $this->normalize_style_src( get_stylesheet_uri() ); | |
| 935 | + $styles = wp_styles(); | |
| 936 | + $stack = (array) $styles->queue; | |
| 937 | + $seen = array(); | |
| 938 | + | |
| 939 | + while ( ! empty( $stack ) ) { | |
| 940 | + $handle = array_pop( $stack ); | |
| 941 | + | |
| 942 | + if ( ! is_string( $handle ) || isset( $seen[ $handle ] ) || self::THEME_STYLESHEET_FALLBACK_HANDLE === $handle ) { | |
| 943 | + continue; | |
| 944 | + } | |
| 945 | + $seen[ $handle ] = true; | |
| 946 | + | |
| 947 | + $style = $styles->registered[ $handle ] ?? null; | |
| 948 | + | |
| 949 | + if ( ! $style ) { | |
| 950 | + continue; | |
| 951 | + } | |
| 952 | + | |
| 953 | + if ( is_string( $style->src ) && '' !== $style->src && $this->normalize_style_src( $style->src ) === $target ) { | |
| 954 | + wp_dequeue_style( self::THEME_STYLESHEET_FALLBACK_HANDLE ); | |
| 955 | + return; | |
| 956 | + } | |
| 957 | + | |
| 958 | + foreach ( (array) $style->deps as $dep ) { | |
| 959 | + $stack[] = $dep; | |
| 960 | + } | |
| 961 | + } | |
| 962 | + } | |
| 963 | + | |
| 964 | + /** | |
| 965 | + * Normalises a stylesheet URL for comparison (drops scheme, host and query). | |
| 966 | + * | |
| 967 | + * @param string $src Stylesheet URL (absolute, protocol-relative or root-relative). | |
| 968 | + * @return string | |
| 969 | + */ | |
| 970 | + protected function normalize_style_src( string $src ): string { | |
| 971 | + $src = strtok( $src, '?#' ); | |
| 972 | + $src = preg_replace( '#^(?:https?:)?//[^/]+#i', '', (string) $src ); | |
| 973 | + | |
| 974 | + return strtolower( untrailingslashit( (string) $src ) ); | |
| 533 | 975 | } |
| 534 | 976 | } |