| @@ -8,13 +8,18 @@ | ||
| 8 | 8 | use ABlocks\Classes\FileUpload; |
| 9 | 9 | use ABlocks\Classes\AssetsGenerator; |
| 10 | 10 | use ABlocks\Classes\RegisterScripts; |
| 11 | 11 | use ABlocks\Classes\GlobalCssGenerator; |
| 12 | +use ABlocks\Classes\GlobalClasses; | |
| 12 | 13 | use ABlocks\Classes\FontLoadLocally; |
| 13 | 14 | use ABlocks\Admin\Menu; |
| 14 | 15 | use ABlocks\Helper; |
| 15 | 16 | |
| 16 | 17 | class Assets { |
| 18 | + | |
| 19 | + /** Records which plugin version last generated the page stylesheets. */ | |
| 20 | + const BUILD_OPTION = 'ablocks_assets_build'; | |
| 21 | + | |
| 17 | 22 | public $current_page_template_part = []; |
| 18 | 23 | public $current_page_blocks = []; |
| 19 | 24 | private $FileUpload; |
| 20 | 25 | private $current_page_slug = ''; |
| @@ -106,18 +111,32 @@ | ||
| 106 | 111 | 'ajax_url' => esc_url( admin_url( 'admin-ajax.php' ) ), |
| 107 | 112 | 'is_pro' => (bool) Helper::is_active_ablocks_pro(), |
| 108 | 113 | 'is_archive' => (bool) is_archive(), |
| 109 | 114 | 'archive_post_type' => $this->get_current_archive_post_type(), |
| 115 | + // Responsive breakpoint widths (px) shared with the JS CSS generators | |
| 116 | + // and the block editor so media queries match the frontend. | |
| 117 | + 'breakpoints' => Helper::get_breakpoints(), | |
| 118 | + // Ordered device list (Desktop + built-ins + custom breakpoints) for | |
| 119 | + // the atomic block's responsive device switcher. Widest-first, so | |
| 120 | + // the editor and the frontend agree on breakpoint precedence. | |
| 121 | + 'responsive_devices' => Helper::get_responsive_devices(), | |
| 122 | + // Whether breakpoint queries overlap (cascade) or are exclusive | |
| 123 | + // bands (strict). The editor preview builds matching queries. | |
| 124 | + 'breakpoint_mode' => Helper::get_breakpoint_mode(), | |
| 110 | 125 | ]; |
| 111 | 126 | } |
| 112 | 127 | |
| 113 | 128 | public function get_localize_dashboard_data() { |
| 114 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 129 | + // Anyone permitted to reach an aBlocks screen needs the nonce, or the | |
| 130 | + // dashboard loads and then 403s on every request it makes. | |
| 131 | + if ( ! current_user_can( Permissions::ACCESS ) ) { | |
| 115 | 132 | return $this->get_localize_script_data(); |
| 116 | 133 | } |
| 117 | 134 | return array_merge($this->get_localize_script_data(), [ |
| 118 | 135 | 'nonce' => wp_create_nonce( 'wp_rest' ), |
| 119 | 136 | 'ablocks_nonce' => wp_create_nonce( 'ablocks_nonce' ), |
| 137 | + 'permissions' => Permissions::for_user(), | |
| 138 | + 'is_admin_user' => Permissions::is_real_admin(), | |
| 120 | 139 | ]); |
| 121 | 140 | } |
| 122 | 141 | public function get_localize_editor_data() { |
| 123 | 142 | if ( ! current_user_can( 'edit_posts' ) ) { |
| @@ -128,14 +147,51 @@ | ||
| 128 | 147 | 'ablocks_nonce' => wp_create_nonce( 'ablocks_nonce' ), |
| 129 | 148 | ]); |
| 130 | 149 | } |
| 131 | 150 | public function get_localize_frontend_data() { |
| 132 | - return array_merge($this->get_localize_script_data(), [ | |
| 151 | + $data = array_merge($this->get_localize_script_data(), [ | |
| 133 | 152 | 'nonce' => wp_create_nonce( 'wp_rest' ), |
| 134 | 153 | 'ablocks_nonce' => wp_create_nonce( 'ablocks_nonce' ), |
| 135 | 154 | ]); |
| 155 | + // The absolute server path is only meaningful to admin-side code; on the | |
| 156 | + // frontend it would publish the filesystem layout in page source. No | |
| 157 | + // frontend script reads it (it is re-exported but never consumed). | |
| 158 | + unset( $data['plugin_root_path'] ); | |
| 159 | + return $data; | |
| 136 | 160 | } |
| 137 | 161 | |
| 162 | + /** | |
| 163 | + * Whether the frontend ABlocksGlobal payload has been attached this request. | |
| 164 | + * | |
| 165 | + * @var bool | |
| 166 | + */ | |
| 167 | + private static $globals_localized = false; | |
| 168 | + | |
| 169 | + /** | |
| 170 | + * Attach the frontend ABlocksGlobal payload to the shared data-only | |
| 171 | + * `ablocks-globals` handle, at most once per request. | |
| 172 | + * | |
| 173 | + * Previously each per-block script handle localized its own copy, so a page | |
| 174 | + * using N block types printed N identical `var ABlocksGlobal = {...}` blocks | |
| 175 | + * (and called wp_create_nonce() N times). Every aBlocks frontend script now | |
| 176 | + * depends on `ablocks-globals`, so WP prints the object once, before any | |
| 177 | + * consumer, in both asset-generation modes. | |
| 178 | + */ | |
| 179 | + public static function localize_globals_once() { | |
| 180 | + if ( self::$globals_localized ) { | |
| 181 | + return; | |
| 182 | + } | |
| 183 | + self::$globals_localized = true; | |
| 184 | + // Self-register so callers never depend on RegisterScripts having run | |
| 185 | + // first — theme-builder templates and block render callbacks fire on | |
| 186 | + // different hooks, and a missing handle would drop the payload silently. | |
| 187 | + if ( ! wp_script_is( 'ablocks-globals', 'registered' ) ) { | |
| 188 | + wp_register_script( 'ablocks-globals', false, array(), ABLOCKS_VERSION, array() ); | |
| 189 | + } | |
| 190 | + $self = new self(); | |
| 191 | + wp_localize_script( 'ablocks-globals', 'ABlocksGlobal', $self->get_localize_frontend_data() ); | |
| 192 | + } | |
| 193 | + | |
| 138 | 194 | public function get_dashboard_localize_script_data() { |
| 139 | 195 | $menu = new Menu(); |
| 140 | 196 | $args = array( |
| 141 | 197 | 'menu' => wp_json_encode( Helper::get_admin_menu_list() ), |
| @@ -148,8 +204,11 @@ | ||
| 148 | 204 | 'enabled_block_copy_paste_style' => (bool) Helper::get_settings( 'enabled_block_copy_paste_style', false ), |
| 149 | 205 | 'enabled_load_google_font_locally' => (bool) Helper::get_settings( 'enabled_load_google_font_locally', false ), |
| 150 | 206 | 'enabled_only_selected_fonts' => (bool) Helper::get_settings( 'enabled_only_selected_fonts', false ), |
| 151 | 207 | 'selected_fonts' => (array) Helper::get_settings( 'selected_fonts', [] ), |
| 208 | + 'font_metric_fallback' => (bool) Helper::get_settings( 'font_metric_fallback', true ), | |
| 209 | + // Global typography picks from the same list the editor offers. | |
| 210 | + 'theme_fonts' => \ABlocks\Classes\FontStack::get_theme_font_families(), | |
| 152 | 211 | ], |
| 153 | 212 | 'is_gutenberg_editor' => Helper::is_gutenberg_editor(), |
| 154 | 213 | 'is_fse_theme' => Helper::is_fse_theme(), |
| 155 | 214 | 'third_party_plugin_status' => [ |
| @@ -156,8 +215,11 @@ | ||
| 156 | 215 | 'academy_lms' => Helper::is_active_academy(), |
| 157 | 216 | 'storeengine' => Helper::is_active_storeengine(), |
| 158 | 217 | 'wp_map_block' => Helper::is_active_wp_map_block(), |
| 159 | 218 | 'easy_content_manager' => Helper::is_active_easy_content_manager(), |
| 219 | + 'zencommunity' => Helper::is_active_zencommunity(), | |
| 220 | + 'gemboards' => Helper::is_active_gemboards(), | |
| 221 | + 'quizpress' => Helper::is_active_quizpress(), | |
| 160 | 222 | ] |
| 161 | 223 | ); |
| 162 | 224 | return apply_filters( |
| 163 | 225 | 'ablocks/assets/dashboard_scripts_data', |
| @@ -179,13 +241,24 @@ | ||
| 179 | 241 | 'enabled_block_copy_paste_style' => (bool) Helper::get_settings( 'enabled_block_copy_paste_style', false ), |
| 180 | 242 | 'enabled_load_google_font_locally' => (bool) Helper::get_settings( 'enabled_load_google_font_locally', false ), |
| 181 | 243 | 'enabled_only_selected_fonts' => (bool) Helper::get_settings( 'enabled_only_selected_fonts', false ), |
| 182 | 244 | 'selected_fonts' => (array) Helper::get_settings( 'selected_fonts', [] ), |
| 245 | + // Design system lock — the editor hides the custom pickers when these | |
| 246 | + // are on, leaving only the global presets. | |
| 247 | + 'lock_global_typography' => (bool) Helper::get_settings( 'lock_global_typography', false ), | |
| 248 | + 'lock_global_typography_strict' => (bool) Helper::get_settings( 'lock_global_typography_strict', false ), | |
| 249 | + 'lock_global_colors' => (bool) Helper::get_settings( 'lock_global_colors', false ), | |
| 183 | 250 | 'frontend_dashboard_page' => (int) Helper::get_settings( 'frontend_dashboard_page' ), |
| 184 | 251 | 'global_color' => (array) Helper::get_settings( 'global_color', [] ), |
| 185 | 252 | 'global_typography' => (array) Helper::get_settings( 'global_typography', [] ), |
| 186 | 253 | 'global_typography_list' => wp_list_pluck( Helper::get_settings( 'global_typography', [] ), 'value', 'id' ), |
| 187 | 254 | 'global_font_family_fallback' => (string) Helper::get_settings( 'global_font_family_fallback', 'Sans-serif' ), |
| 255 | + // Editor paste — see includes/api/paste-controller.php. | |
| 256 | + 'paste_google_docs' => (bool) Helper::get_settings( 'paste_google_docs', true ), | |
| 257 | + 'paste_convert_webp' => (bool) Helper::get_settings( 'paste_convert_webp', true ), | |
| 258 | + // Theme.json + Font Library families, so uploaded/theme fonts are | |
| 259 | + // selectable next to the Google catalog. | |
| 260 | + 'theme_fonts' => \ABlocks\Classes\FontStack::get_theme_font_families(), | |
| 188 | 261 | ], |
| 189 | 262 | 'is_gutenberg_editor' => Helper::is_gutenberg_editor(), |
| 190 | 263 | 'has_required_block_attribute_migration' => get_option( 'ablocks_has_required_block_attribute_migration' ), |
| 191 | 264 | 'third_party_plugin_status' => [ |
| @@ -195,9 +268,13 @@ | ||
| 195 | 268 | 'quizpress' => Helper::is_active_quizpress(), |
| 196 | 269 | 'easy_content_manager' => Helper::is_active_easy_content_manager(), |
| 197 | 270 | ], |
| 198 | 271 | 'blocks_status' => $ablocks_blocks, |
| 199 | - 'post_types' => $post_types | |
| 272 | + 'post_types' => $post_types, | |
| 273 | + // What this user may do inside the editor. Read by | |
| 274 | + // src/utils/permissions.js to hide controls they cannot use. | |
| 275 | + 'permissions' => Permissions::for_user(), | |
| 276 | + 'is_admin_user' => Permissions::is_real_admin(), | |
| 200 | 277 | ); |
| 201 | 278 | return apply_filters( |
| 202 | 279 | 'ablocks/assets/editor_scripts_data', |
| 203 | 280 | array_merge( |
| @@ -382,12 +459,49 @@ | ||
| 382 | 459 | // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents |
| 383 | 460 | wp_add_inline_style( 'ablocks-blocks-combine-style', (string) file_get_contents( $css_path ) ); |
| 384 | 461 | } else { |
| 385 | 462 | wp_enqueue_style( 'ablocks-blocks-combine-style', $this->FileUpload->get_file_url( $this->current_page_slug . '.min.css' ), array(), filemtime( $css_path ), 'all' ); |
| 463 | + | |
| 464 | + // Performance Suite — when the page stylesheet is too large to inline, | |
| 465 | + // load it non-blocking (media="print" + onload swap) so it no longer | |
| 466 | + // delays first paint. Opt-in via perf_async_css (default off: a too- | |
| 467 | + // aggressive critical set risks FOUC, so promote after QA). Editors | |
| 468 | + // viewing the frontend are bypassed so previews render immediately. | |
| 469 | + $async_css = (bool) apply_filters( | |
| 470 | + 'ablocks/perf/perf_async_css', | |
| 471 | + (bool) Helper::get_settings( 'perf_async_css', false ) | |
| 472 | + ); | |
| 473 | + // Critical CSS — inline the above-the-fold/structural subset in | |
| 474 | + // <head> and load the full stylesheet non-blocking. This is a | |
| 475 | + // stronger form of async (it prevents the FOUC async alone can | |
| 476 | + // cause), so enabling it implies async delivery of the full file. | |
| 477 | + $critical_css = (bool) apply_filters( | |
| 478 | + 'ablocks/perf/perf_critical_css', | |
| 479 | + (bool) Helper::get_settings( 'perf_critical_css', false ) | |
| 480 | + ); | |
| 481 | + $bypass_for_editor = is_user_logged_in() && current_user_can( 'edit_posts' ) | |
| 482 | + && (bool) apply_filters( 'ablocks/perf/bypass_optimizations_for_editors', true ); | |
| 483 | + | |
| 484 | + if ( ( $async_css || $critical_css ) && ! $bypass_for_editor ) { | |
| 485 | + // Before deferring the full stylesheet, always inline the | |
| 486 | + // layout-critical CSS so the page paints with correct box | |
| 487 | + // sizes and never reflows when the deferred file loads. The | |
| 488 | + // split is property-based: the deferred remainder is cosmetic | |
| 489 | + // only (colors, shadows, hover states), so it cannot cause a | |
| 490 | + // layout shift (CLS). This makes async delivery safe. | |
| 491 | + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents | |
| 492 | + list( $critical ) = \ABlocks\Classes\CriticalCss::split( (string) file_get_contents( $css_path ) ); | |
| 493 | + if ( '' !== trim( (string) $critical ) ) { | |
| 494 | + wp_register_style( 'ablocks-critical-css', false, array(), ABLOCKS_VERSION ); | |
| 495 | + wp_enqueue_style( 'ablocks-critical-css' ); | |
| 496 | + wp_add_inline_style( 'ablocks-critical-css', $critical ); | |
| 497 | + } | |
| 498 | + add_filter( 'style_loader_tag', [ $this, 'async_combined_style_tag' ], 20, 2 ); | |
| 499 | + } | |
| 386 | 500 | } |
| 387 | 501 | |
| 388 | - wp_enqueue_script( 'ablocks-blocks-combine-script', $this->FileUpload->get_file_url( $this->current_page_slug . '.min.js' ), array(), filemtime( $this->FileUpload->get_file_path( $this->current_page_slug . '.min.js' ) ), true, [ 'strategy' => $script_loading_strategy ] ); | |
| 389 | - wp_localize_script( 'ablocks-blocks-combine-script', 'ABlocksGlobal', $this->get_localize_frontend_data() ); | |
| 502 | + wp_enqueue_script( 'ablocks-blocks-combine-script', $this->FileUpload->get_file_url( $this->current_page_slug . '.min.js' ), array( 'ablocks-globals' ), filemtime( $this->FileUpload->get_file_path( $this->current_page_slug . '.min.js' ) ), true, [ 'strategy' => $script_loading_strategy ] ); | |
| 503 | + self::localize_globals_once(); | |
| 390 | 504 | wp_set_script_translations( 'ablocks-blocks-combine-script', 'ablocks', ABLOCKS_ROOT_DIR_PATH . 'languages' ); |
| 391 | 505 | } else { |
| 392 | 506 | // fallback if assets not available |
| 393 | 507 | add_filter( 'ablocks/is_allow_block_inline_assets', '__return_true' ); |
| @@ -393,8 +507,31 @@ | ||
| 393 | 507 | add_filter( 'ablocks/is_allow_block_inline_assets', '__return_true' ); |
| 394 | 508 | } |
| 395 | 509 | } |
| 396 | 510 | |
| 511 | + /** | |
| 512 | + * Rewrite the combined page stylesheet <link> so it loads without blocking | |
| 513 | + * render: fetch as media="print", then flip to media="all" on load, with a | |
| 514 | + * <noscript> fallback for JS-disabled clients. Only touches the aBlocks | |
| 515 | + * combined handle — third-party stylesheets are left untouched. | |
| 516 | + */ | |
| 517 | + public function async_combined_style_tag( $tag, $handle ) { | |
| 518 | + if ( 'ablocks-blocks-combine-style' !== $handle ) { | |
| 519 | + return $tag; | |
| 520 | + } | |
| 521 | + $async_tag = preg_replace( | |
| 522 | + [ "/media=(['\"])[^'\"]*\\1/", '/>$/', '/\/>$/' ], | |
| 523 | + [ 'media="print" onload="this.media=\'all\'"', '>', '/>' ], | |
| 524 | + trim( $tag ) | |
| 525 | + ); | |
| 526 | + // Guarantee the print/onload attributes even if the original tag had no media. | |
| 527 | + if ( strpos( $async_tag, 'onload=' ) === false ) { | |
| 528 | + $async_tag = str_replace( '<link ', '<link media="print" onload="this.media=\'all\'" ', $async_tag ); | |
| 529 | + } | |
| 530 | + $noscript = '<noscript>' . $tag . '</noscript>'; | |
| 531 | + return $async_tag . "\n" . $noscript; | |
| 532 | + } | |
| 533 | + | |
| 397 | 534 | public function regenerate_missing_assets() { |
| 398 | 535 | if ( $this->is_assets_generated() ) { |
| 399 | 536 | return; |
| 400 | 537 | } |
| @@ -436,9 +573,20 @@ | ||
| 436 | 573 | $register_scripts = RegisterScripts::get_register_scripts(); |
| 437 | 574 | foreach ( $register_scripts as $register_handler => $register_script ) { |
| 438 | 575 | if ( isset( $register_script['dependencies'] ) ) { |
| 439 | 576 | $dependencies = include $register_script['dependencies']; |
| 440 | - $register_script['deps'] = $dependencies['dependencies']; | |
| 577 | + // Merge rather than overwrite: the generated asset.php only knows | |
| 578 | + // about build-time (@wordpress/*) dependencies, so replacing the | |
| 579 | + // declared list would silently drop hand-declared handles such as | |
| 580 | + // ablocks-globals. | |
| 581 | + $register_script['deps'] = array_values( | |
| 582 | + array_unique( | |
| 583 | + array_merge( | |
| 584 | + isset( $register_script['deps'] ) ? (array) $register_script['deps'] : array(), | |
| 585 | + $dependencies['dependencies'] | |
| 586 | + ) | |
| 587 | + ) | |
| 588 | + ); | |
| 441 | 589 | $register_script['ver'] = $dependencies['version']; |
| 442 | 590 | } |
| 443 | 591 | wp_register_script( |
| 444 | 592 | $register_handler, |
| @@ -446,9 +594,9 @@ | ||
| 446 | 594 | $register_script['deps'], |
| 447 | 595 | $register_script['ver'], |
| 448 | 596 | $register_script['args'] |
| 449 | 597 | ); |
| 450 | - } | |
| 598 | + }//end foreach | |
| 451 | 599 | } |
| 452 | 600 | public function global_css_variable() { |
| 453 | 601 | wp_register_style( 'ablocks-editor-global-styles', false, array(), ABLOCKS_VERSION ); |
| 454 | 602 | wp_enqueue_style( 'ablocks-editor-global-styles' ); |
| @@ -478,9 +626,17 @@ | ||
| 478 | 626 | $value = $typography->value; |
| 479 | 627 | |
| 480 | 628 | // Desktop values |
| 481 | 629 | if ( ! empty( $value->fontFamily ) ) { |
| 482 | - $css .= " --ablocks-{$id}-font-family: {$value->fontFamily};\n"; | |
| 630 | + // Every consumer of this variable drops it straight into | |
| 631 | + // font-family, so it has to carry the whole stack. | |
| 632 | + $font_stack = \ABlocks\Classes\FontStack::build( | |
| 633 | + $value->fontFamily, | |
| 634 | + isset( $value->fontFallback ) ? $value->fontFallback : '' | |
| 635 | + ); | |
| 636 | + if ( '' !== $font_stack ) { | |
| 637 | + $css .= " --ablocks-{$id}-font-family: {$font_stack};\n"; | |
| 638 | + } | |
| 483 | 639 | } |
| 484 | 640 | if ( ! empty( $value->weight ) ) { |
| 485 | 641 | $css .= " --ablocks-{$id}-weight: {$value->weight};\n"; |
| 486 | 642 | } |
| @@ -680,23 +836,108 @@ | ||
| 680 | 836 | |
| 681 | 837 | public function is_assets_generated() { |
| 682 | 838 | $file_name = $this->current_page_slug; |
| 683 | 839 | $css_file_path = $this->FileUpload->get_file_path( $file_name . '.min.css' ); |
| 684 | - return file_exists( $css_file_path ); | |
| 840 | + | |
| 841 | + if ( ! file_exists( $css_file_path ) ) { | |
| 842 | + return false; | |
| 843 | + } | |
| 844 | + | |
| 845 | + // The file embeds the global-class CSS this page needs, so editing a | |
| 846 | + // class has to make every generated stylesheet stale. Comparing the | |
| 847 | + // file's mtime against the library's change stamp rebuilds them lazily, | |
| 848 | + // one page at a time on next visit, without stamping a revision into | |
| 849 | + // every file name (which the per-post delete in Blocks relies on). | |
| 850 | + // | |
| 851 | + // Strictly greater, not >=: mtime has one-second resolution, so a file | |
| 852 | + // written in the same second as a class edit is indistinguishable from | |
| 853 | + // one written just after it. Treating that tie as stale costs one extra | |
| 854 | + // regeneration; treating it as fresh would serve the old CSS until the | |
| 855 | + // next edit. | |
| 856 | + return filemtime( $css_file_path ) > max( | |
| 857 | + GlobalClasses::get_revision(), | |
| 858 | + self::build_revision() | |
| 859 | + ); | |
| 685 | 860 | } |
| 686 | 861 | |
| 862 | + /** | |
| 863 | + * When this plugin's own CSS last changed, as a timestamp. | |
| 864 | + * | |
| 865 | + * The generated file bakes in each block's static stylesheet (see | |
| 866 | + * AssetsGenerator, which concatenates get_static_css() into it), so a CSS | |
| 867 | + * fix shipped in an update reached nobody whose pages were generated before | |
| 868 | + * it: the file still existed, still parsed, and nothing about it looked | |
| 869 | + * stale, so it was served unchanged forever. Verified by planting a | |
| 870 | + * stylesheet carrying the previous release's rules — it survived every | |
| 871 | + * subsequent page load untouched. That is why a fix could land in the | |
| 872 | + * editor, which loads each block's style.css directly, and never appear on | |
| 873 | + * the front end. | |
| 874 | + * | |
| 875 | + * Keyed on the version rather than a file scan: it is one option read on | |
| 876 | + * the common path, and every shipped CSS change comes with a version bump. | |
| 877 | + * Each page then rebuilds once, on its next visit, exactly the way a | |
| 878 | + * global-class edit already makes them rebuild. | |
| 879 | + * | |
| 880 | + * @return int Timestamp of the running version's first request. | |
| 881 | + */ | |
| 882 | + public static function build_revision() { | |
| 883 | + $stamp = get_option( self::BUILD_OPTION ); | |
| 884 | + | |
| 885 | + if ( | |
| 886 | + is_array( $stamp ) && | |
| 887 | + isset( $stamp['version'], $stamp['time'] ) && | |
| 888 | + ABLOCKS_VERSION === $stamp['version'] | |
| 889 | + ) { | |
| 890 | + return (int) $stamp['time']; | |
| 891 | + } | |
| 892 | + | |
| 893 | + $now = time(); | |
| 894 | + update_option( | |
| 895 | + self::BUILD_OPTION, | |
| 896 | + [ | |
| 897 | + 'version' => ABLOCKS_VERSION, | |
| 898 | + 'time' => $now, | |
| 899 | + ], | |
| 900 | + true | |
| 901 | + ); | |
| 902 | + | |
| 903 | + return $now; | |
| 904 | + } | |
| 905 | + | |
| 687 | 906 | public function set_current_page_template_part( $content, $block ) { |
| 688 | 907 | if ( ! isset( $block['blockName'] ) && is_array( $block ) ) { |
| 689 | 908 | foreach ( $block as $block_item ) { |
| 690 | - if ( ! empty( $block_item['blockName'] ) && strpos( $block_item['blockName'], 'ablocks/' ) !== false ) { | |
| 909 | + if ( $this->is_page_asset_block( $block_item ) ) { | |
| 691 | 910 | $this->current_page_blocks[] = $block_item; |
| 692 | 911 | } |
| 693 | 912 | } |
| 694 | 913 | } |
| 695 | - if ( ! empty( $block['blockName'] ) && strpos( $block['blockName'], 'ablocks/' ) !== false ) { | |
| 914 | + if ( $this->is_page_asset_block( $block ) ) { | |
| 696 | 915 | $this->current_page_blocks[] = $block; |
| 697 | 916 | } |
| 698 | 917 | return $content; |
| 918 | + } | |
| 919 | + | |
| 920 | + /** | |
| 921 | + * Whether a top-level block contributes to the page's generated assets. | |
| 922 | + * | |
| 923 | + * A synced pattern counts too: AssetsGenerator::recursive_block_parser() | |
| 924 | + * already expands its reference. Collecting only `ablocks/*` names left a | |
| 925 | + * page whose content is just a pattern with no combined CSS/JS at all — so a | |
| 926 | + * Loop Filter placed from a pattern rendered unstyled and did nothing when | |
| 927 | + * clicked. | |
| 928 | + * | |
| 929 | + * @param mixed $block Parsed block. | |
| 930 | + * @return bool | |
| 931 | + */ | |
| 932 | + private function is_page_asset_block( $block ) { | |
| 933 | + if ( ! is_array( $block ) || empty( $block['blockName'] ) ) { | |
| 934 | + return false; | |
| 935 | + } | |
| 936 | + if ( 'core/block' === $block['blockName'] ) { | |
| 937 | + return ! empty( $block['attrs']['ref'] ); | |
| 938 | + } | |
| 939 | + return strpos( $block['blockName'], 'ablocks/' ) !== false; | |
| 699 | 940 | } |
| 700 | 941 | |
| 701 | 942 | public function set_theme_builder_locations( $args ) { |
| 702 | 943 | $this->theme_builder_locations = $args; |