diff --git a/plugin.php b/plugin.php index 1fad10a379..8dae1fff45 100644 --- a/plugin.php +++ b/plugin.php @@ -274,6 +274,7 @@ function is_frontend() { require_once( plugin_dir_path( __FILE__ ) . 'src/jetpack.php' ); require_once( plugin_dir_path( __FILE__ ) . 'src/multisite.php' ); require_once( plugin_dir_path( __FILE__ ) . 'src/kses.php' ); +require_once( plugin_dir_path( __FILE__ ) . 'src/css-file-generator.php' ); require_once( plugin_dir_path( __FILE__ ) . 'src/dynamic-breakpoints.php' ); require_once( plugin_dir_path( __FILE__ ) . 'src/design-library/init.php' ); require_once( plugin_dir_path( __FILE__ ) . 'src/design-library/default-placeholders.php' ); @@ -332,20 +333,21 @@ function is_frontend() { } } -// Deprecated. -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/editor-settings.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/native-global-colors.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/navigation-panel-pre-enabled.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/font-awesome-version.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/global-color-schemes.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/block-defaults.php' ); +// Allow users to disable the deprecated code via STACKABLE_DISABLE_DEPRECATED_CODE. +if ( ! defined( 'STACKABLE_DISABLE_DEPRECATED_CODE' ) ) { + // Deprecated. + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/editor-settings.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/native-global-colors.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/navigation-panel-pre-enabled.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/font-awesome-version.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/global-color-schemes.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/block-defaults.php' ); -/** - * V2 Deprecated - */ -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/init.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/blocks.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/disabled-blocks.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/design-library/init.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/optimization-settings.php' ); -require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/global-settings.php' ); + // V2 Deprecated + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/init.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/blocks.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/disabled-blocks.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/design-library/init.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/optimization-settings.php' ); + require_once( plugin_dir_path( __FILE__ ) . 'src/deprecated/v2/global-settings.php' ); +} \ No newline at end of file diff --git a/src/css-file-generator.php b/src/css-file-generator.php new file mode 100644 index 0000000000..23ae167468 --- /dev/null +++ b/src/css-file-generator.php @@ -0,0 +1,485 @@ + 'boolean', + 'description' => __( 'Enables CSS file generation for the global design system and block style inheritance', STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => true, + ) + ); + } + + /** + * When upgrading from a lower version, disable use CSS files. + */ + public function register_use_css_files_setting_upgraded( $old_version, $new_version ) { + if ( ! empty( $old_version ) && version_compare( $old_version, "3.19.0", "<" ) ) { + update_option( 'stackable_use_css_files', '', 'no' ); + } + } + + /** + * Register REST API routes. + */ + public function register_routes() { + register_rest_route( 'stackable/v3', '/invalidate-css-files', array( + 'methods' => 'POST', + 'callback' => array( __CLASS__, 'invalidate_all_css_files' ), + 'permission_callback' => array( $this, 'check_permissions' ), + ) ); + } + + /** + * Check if user has permission to invalidate CSS files. + * + * @return bool + */ + public function check_permissions() { + return current_user_can( 'manage_options' ); + } + + /** + * Invalidate all generated CSS files. + * + * @param WP_REST_Request $request The request object. + * @return WP_REST_Response|WP_Error + */ + public static function invalidate_all_css_files( $request ) { + try { + // Clear all CSS caches + Stackable_Global_Design_System_CSS_Generator::invalidate_css_file(); + Stackable_Block_Style_Inheritance_CSS_Generator::invalidate_css_file(); + + return new WP_REST_Response( array( + 'success' => true, + 'message' => 'CSS files invalidated successfully', + ), 200 ); + + } catch ( Exception $e ) { + return new WP_Error( + 'css_invalidation_failed', + 'Failed to invalidate CSS files: ' . $e->getMessage(), + array( 'status' => 500 ) + ); + } + } + } + + new Stackable_CSS_File_Generator(); + + // Run on activation to ensure the CSS files are newly generated. + register_activation_hook( STACKABLE_FILE, array( 'Stackable_CSS_File_Generator', 'invalidate_all_css_files' ) ); +} \ No newline at end of file diff --git a/src/dynamic-breakpoints.php b/src/dynamic-breakpoints.php index 5a5047053e..c4a3033590 100644 --- a/src/dynamic-breakpoints.php +++ b/src/dynamic-breakpoints.php @@ -67,7 +67,7 @@ public function get_dynamic_breakpoints() { ) ); // Check if there are saved custom breakpoints from our settings. - $saved_breakpoints = get_option( 'stackable_dynamic_breakpoints' ); + $saved_breakpoints = get_option( 'stackable_dynamic_breakpoints', false ); if ( ! empty( $saved_breakpoints ) ) { // Set breakpoints for tablet and mobile if they are set. if ( ! empty( $saved_breakpoints['tablet'] ) ) { diff --git a/src/global-settings.php b/src/global-settings.php index 0a63fe27ab..3a046cee41 100644 --- a/src/global-settings.php +++ b/src/global-settings.php @@ -69,17 +69,16 @@ function __construct() { * * @since 2.17.1 */ - // Don't do anything if we don't have any global typography. - $typography = get_option( 'stackable_global_typography' ); - if ( ! empty( $typography ) && is_array( $typography ) ) { + // Only do this if we have global typography. + if ( $this->has_global_typography() ) { $this->force_typography = get_option( 'stackable_global_force_typography' ); add_action( 'after_setup_theme', array( $this, 'typography_parse_global_styles' ) ); - } - // For some native blocks, add a note that they're core blocks. - // Only do this when we need to style native blocks. - if ( in_array( $this->get_apply_typography_to(), array( 'blocks-stackable-native', 'blocks-all' ) ) ) { - add_filter( 'render_block', array( $this, 'typography_detect_native_blocks' ), 10, 2 ); + // Optimize by avoiding repeated calls to get_apply_typography_to() and unnecessary filter registration. + $apply_typography_to = $this->get_apply_typography_to(); + if ( $apply_typography_to === 'blocks-stackable-native' || $apply_typography_to === 'blocks-all' ) { + add_filter( 'render_block', array( $this, 'typography_detect_native_blocks' ), 10, 2 ); + } } // Fixes columns issue with Native Posts block. @@ -563,13 +562,31 @@ public function color_add_global_styles( $current_css ) { * Typography functions *-----------------------------------------------------------------------------*/ + /** + * Fast way to check if the global typography is empty. + * + * @return boolean + */ + public function has_global_typography() { + $typography = get_option( 'stackable_global_typography', false ); + if ( empty( $typography ) ) { + return false; + } + + // $typography is an array of arrays. + if ( ! empty( $typography ) && is_array( $typography ) && ! empty( $typography[0] ) && is_array( $typography[0] ) ) { + return ! empty( array_filter( $typography[0] ) ); + } + return true; + } + /** * Add our global typography styles in the frontend. * * @return void */ public function typography_parse_global_styles() { - $typography = get_option( 'stackable_global_typography' ); + $typography = get_option( 'stackable_global_typography', false ); if ( ! $typography || ! is_array( $typography ) ) { return; } diff --git a/src/init.php b/src/init.php index 6ae3840430..0efe99ec1f 100644 --- a/src/init.php +++ b/src/init.php @@ -16,12 +16,6 @@ if ( ! class_exists( 'Stackable_Init' ) ) { class Stackable_Init { - /** - * Holds the scripts which are already enqueued, to ensure we only do it once per script. - * @var Array - */ - public $scripts_loaded = array(); - /** * Enqueue the frontend scripts, ensures we only do it once. * @@ -122,19 +116,13 @@ public function register_frontend_assets() { wp_register_script( 'ugb-block-frontend-js', null, [], STACKABLE_VERSION ); } - // Register inline frontend styles, these are always loaded. - // Register via a dummy style. - wp_register_style( 'ugb-style-css-nodep', false ); - $inline_css = apply_filters( 'stackable_inline_styles_nodep', '' ); - if ( ! empty( $inline_css ) ) { - wp_add_inline_style( 'ugb-style-css-nodep', trim( $inline_css ) ); - } - - // Register inline frontend styles for theme.json block style inheritance - wp_register_style( 'ugb-block-style-inheritance-nodep', false ); - $block_style_inline_css = apply_filters( 'stackable_block_style_inheritance_inline_styles_nodep', '' ); - if ( ! empty( $block_style_inline_css ) ) { - wp_add_inline_style( 'ugb-block-style-inheritance-nodep', $block_style_inline_css ); + // Enqueue the global CSS file in the frontend. + if ( ! is_admin() && get_option( 'stackable_use_css_files', '1' ) === '1' ) { + // The enqueue_global_css_file method now has built-in fallback to inline styles + // if CSS file generation fails + Stackable_Global_Design_System_CSS_Generator::enqueue_global_css_file(); + } else { + Stackable_Global_Design_System_CSS_Generator::enqueue_global_css_inline(); } // This is needed for the translation strings in our UI. @@ -237,19 +225,29 @@ public function load_frontend_scripts_conditionally( $block_content, $block ) { } // Enqueue the block script once. - if ( ! isset( $this->scripts_loaded[ $block['blockName'] ] ) ) { - $stackable_block = substr( $block['blockName'], 10 ); - do_action( 'stackable/' . $stackable_block . '/enqueue_scripts' ); - $this->scripts_loaded[ $block['blockName'] ] = true; + if ( did_action( 'stackable/' . $block['blockName'] . '/enqueue_scripts' ) === 0 ) { + do_action( 'stackable/' . $block['blockName'] . '/enqueue_scripts' ); } // Check whether the current block needs to enqueue some scripts. // This gets called across all the blocks. - do_action( 'stackable/enqueue_scripts', $block_content, $block ); + if ( did_action( 'stackable/enqueue_scripts' ) === 0 ) { + do_action( 'stackable/enqueue_scripts', $block_content, $block ); + } return $block_content; } + // Register inline frontend styles for theme.json block style inheritance + public function enqueue_inline_block_style_inheritance() { + // Use the new CSS generator class with built-in fallback to inline styles + if ( ! is_admin() && get_option( 'stackable_use_css_files', '1' ) === '1' ) { + Stackable_Block_Style_Inheritance_CSS_Generator::enqueue_block_inheritance_css_file(); + } else { + Stackable_Block_Style_Inheritance_CSS_Generator::enqueue_block_inheritance_css_inline(); + } + } + /** * Enqueue frontend scripts and styles. * @@ -259,7 +257,7 @@ public function block_enqueue_frontend_assets() { $this->register_frontend_assets(); wp_enqueue_style( 'ugb-style-css' ); if ( is_frontend() ) { - wp_enqueue_style( 'ugb-block-style-inheritance-nodep' ); + $this->enqueue_inline_block_style_inheritance(); } wp_enqueue_style( 'ugb-style-css-nodep' ); wp_enqueue_script( 'ugb-block-frontend-js' ); @@ -302,7 +300,8 @@ public function register_block_editor_assets() { // wp-util for wp.ajax. // wp-plugins & wp-edit-post for Gutenberg plugins. array( 'code-editor', 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-api-fetch', 'wp-util', 'wp-plugins', 'wp-i18n', 'wp-api', 'lodash' ), - STACKABLE_VERSION + STACKABLE_VERSION, + true ); // Backend editor scripts: blocks. @@ -311,7 +310,8 @@ public function register_block_editor_assets() { plugins_url( 'dist/editor_blocks.js', STACKABLE_FILE ), // Depend on the window.stk API. apply_filters( 'stackable_editor_js_dependencies', array( 'ugb-stk' ) ), - STACKABLE_VERSION + STACKABLE_VERSION, + true // Load in footer to avoid render blocking ); // Add translations. @@ -382,7 +382,7 @@ public function register_block_editor_assets() { // Ensure that block style inheritance styles comes after the editor block styles. function enqueue_style_in_editor() { wp_enqueue_style( 'ugb-block-editor-css' ); - wp_enqueue_style( 'ugb-block-style-inheritance-nodep' ); + $this->enqueue_inline_block_style_inheritance(); } /** diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index 6d7168e750..5c0ca08a89 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -21,8 +21,6 @@ function __construct() { add_action( 'body_class', array( $this, 'add_body_class_block_style_inheritance' ) ); add_filter( 'stackable_block_style_inheritance_inline_styles_nodep', array( $this, 'add_block_style_inheritance' ) ); - - add_filter( 'safecss_filter_attr_allow_css', array( $this, 'allow_css' ), 10, 2 ); } // Register the settings for block style inheritance @@ -324,6 +322,9 @@ function add_block_style_inheritance( $current_css ) { $styles[] = $declaration; } + // Allow some CSS functions like color-mix() to be included in the generated CSS. + add_filter( 'safecss_filter_attr_allow_css', array( $this, 'allow_css' ), 10, 2 ); + $generated_css = wp_style_engine_get_stylesheet_from_css_rules( $styles ); if ( $generated_css ) { $generated_css = "/* Block style inheritance */\n" . $generated_css; diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 6a8a079b06..0ce43f57ea 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -10,6 +10,7 @@ import SVGSectionIcon from './images/settings-icon-section.svg' * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n' +import apiFetch from '@wordpress/api-fetch' import { useEffect, useState, useCallback, useMemo, Suspense, Fragment, } from '@wordpress/element' @@ -145,6 +146,7 @@ const SEARCH_TREE = [ children: [ __( 'Optimize Inline CSS', i18n ), __( 'Lazy Load Images within Carousels', i18n ), + __( 'Generate CSS Files', i18n ), ], }, ], @@ -1154,6 +1156,8 @@ const Optimizations = props => { filteredSearchTree, } = props + const [ isInvalidatingCssFiles, setIsInvalidatingCssFiles ] = useState( false ) + const groups = filteredSearchTree.find( tab => tab.id === 'optimizations' ).groups const optimizations = groups.find( group => group.id === 'optimizations' ) const hasGroupMatch = groups.some( group => group.children === null || group.children.length > 0 ) @@ -1186,6 +1190,40 @@ const Optimizations = props => { } } help={ __( 'Disable this if you encounter layout or spacing issues when using images inside carousel-type blocks because of image lazy loading.', i18n ) } /> + { + handleSettingsChange( { stackable_use_css_files: value } ) // eslint-disable-line camelcase + } } + help={ + <> +

{ __( 'Generate CSS files for the global design system and block style inheritance. This can improve performance by reducing the number of inline styles and leverages browser caching. Disabling this loads the CSS inline.', i18n ) }

+ { /* Button to invalidate the CSS files */ } + + + } + /> }