diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index d73e7aa8566c8..cb2c7279f38df 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -2196,7 +2196,7 @@ protected static function compute_style_properties( $styles, $settings = array() } } - // Processes background styles. + // Processes background styles in order to build any `url()` functions. if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) { $background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) ); $value = $background_styles['declarations'][ $css_property ] ?? $value; @@ -4085,4 +4085,21 @@ protected static function get_valid_block_style_variations() { return $valid_variations; } + + // @TODO abstract and test this. + public function resolve_relative_paths() { + // Styles backgrounds. + /* + * "theme" source implies relative path to the theme directory + */ + if ( ! empty( $this->theme_json['styles']['background']['backgroundImage']['url'] ) && is_string( $this->theme_json['styles']['background']['backgroundImage']['url'] ) ) { + $background_image_source = ! empty( $this->theme_json['styles']['background']['backgroundImage']['source'] ) ? $this->theme_json['styles']['background']['backgroundImage']['source'] : null; + if ( 'theme' === $background_image_source ) { + $this->theme_json['styles']['background']['backgroundImage']['url'] = esc_url( get_theme_file_uri( $this->theme_json['styles']['background']['backgroundImage']['url'] ) ); + } + } + // Elements... (backgrounds not yet supported) + + // Block variations... (backgrounds not yet supported) + } } diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index dcc0bf8b099c3..62266d863f62f 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -355,6 +355,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_support_data['settings']['appearanceTools'] = true; } } + $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data ); $with_theme_supports->merge( static::$theme ); return $with_theme_supports; @@ -615,11 +616,13 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { $result->set_spacing_sizes(); + $result->resolve_relative_paths(); return $result; } $result->merge( static::get_user_data() ); $result->set_spacing_sizes(); + $result->resolve_relative_paths(); return $result; } diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index bf462cd11ca4b..b1d287723a465 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -29,3 +29,76 @@ function wp_api_template_access_controller( $args, $post_type ) { } } add_filter( 'register_post_type_args', 'wp_api_template_access_controller', 10, 2 ); + + +if ( ! function_exists( 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ) ) { + /** + * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class. + * Core ticket: https://core.trac.wordpress.org/ticket/61021 + */ + function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() { + register_rest_field( + 'theme', + 'stylesheet_uri', + array( + 'get_callback' => function ( $item ) { + if ( ! empty( $item['stylesheet'] ) ) { + $theme = wp_get_theme( $item['stylesheet'] ); + $current_theme = wp_get_theme(); + if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { + return get_stylesheet_directory_uri(); + } else { + return $theme->get_stylesheet_directory_uri(); + } + } + + return null; + }, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ), + 'format' => 'uri', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); + } +} +add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ); + +if ( ! function_exists( 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ) ) { + /** + * Adds `template_uri` fields to WP_REST_Themes_Controller class. + * Core ticket: https://core.trac.wordpress.org/ticket/61021 + */ + function gutenberg_register_wp_rest_themes_template_directory_uri_field() { + register_rest_field( + 'theme', + 'template_uri', + array( + 'get_callback' => function ( $item ) { + if ( ! empty( $item['stylesheet'] ) ) { + $theme = wp_get_theme( $item['stylesheet'] ); + $current_theme = wp_get_theme(); + if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { + return get_template_directory_uri(); + } else { + return $theme->get_template_directory_uri(); + } + } + + return null; + }, + 'schema' => array( + 'type' => 'string', + 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ), + 'format' => 'uri', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ) + ); + } +} +add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ); diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index ef1f9673d601f..a3637308ae260 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -38,6 +38,7 @@ import { TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { setImmutably } from '../../utils/object'; import MediaReplaceFlow from '../media-replace-flow'; import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; const IMAGE_BACKGROUND_TYPE = 'image'; const DEFAULT_CONTROLS = { @@ -201,6 +202,23 @@ function BackgroundImageToolsPanelItem( { ...inheritedValue?.background?.backgroundImage, }; + const { backgroundImageURL } = useSelect( + ( select ) => { + const { getThemeFileURI } = unlock( select( blockEditorStore ) ); + let file = url; + if ( + !! style?.background?.backgroundImage?.url && + style?.background?.backgroundImage?.source === 'theme' + ) { + file = getThemeFileURI( style.background.backgroundImage.url ); + } + return { + backgroundImageURL: file, + }; + }, + [ url ] + ); + const replaceContainerRef = useRef(); const { createErrorNotice } = useDispatch( noticesStore ); @@ -295,7 +313,7 @@ function BackgroundImageToolsPanelItem( { > } variant="secondary" diff --git a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js index d835eb0d9a6bb..3a2b789008da0 100644 --- a/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/test/use-global-styles-output.js @@ -990,6 +990,34 @@ describe( 'global styles renderer', () => { 'letter-spacing: 2px', ] ); } ); + + it( 'should process styles and set CSS default values', () => { + const styles = { + background: { + backgroundImage: { + url: 'image.jpg', + source: 'theme', + }, + }, + }; + const editorSettings = { + themeDirURI: 'http://example.com/theme', + }; + + expect( + getStylesDeclarations( + styles, + '.wp-block', + false, + {}, + false, + editorSettings + ) + ).toEqual( [ + "background-image: url( 'http://example.com/theme/image.jpg' )", + 'background-size: cover', + ] ); + } ); } ); describe( 'processCSSNesting', () => { diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index e321ea9744e87..57d09157fbf3e 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -35,6 +35,7 @@ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; import BlockContext from '../block-context'; import { unlock } from '../../lock-unlock'; +import { setBackgroundStyleDefaults } from '../../hooks/background'; // List of block support features that can have their related styles // generated under their own feature level selector rather than the block's. @@ -314,6 +315,7 @@ const getFeatureDeclarations = ( selectors, styles ) => { * @param {Object} tree A theme.json tree containing layout definitions. * * @param {boolean} isTemplate Whether the entity being edited is a full template or a pattern. + * @param {Object} editorSettings Current editor settings. * @return {Array} An array of style declarations. */ export function getStylesDeclarations( @@ -391,6 +393,17 @@ export function getStylesDeclarations( [] ); + /* + * Set styles defaults. + * Applies default values to the style object based on the block settings. + * Only applies to background styles for now. + */ + if ( !! blockStyles?.background ) { + blockStyles = setBackgroundStyleDefaults( blockStyles, { + selector, + } ); + } + // The goal is to move everything to server side generated engine styles // This is temporary as we absorb more and more styles into the engine. const extraRules = getCSSRules( blockStyles ); @@ -947,19 +960,19 @@ export const toStyles = ( } ); } - // Process the remaining block styles (they use either normal block class or __experimentalSelector). - const declarations = getStylesDeclarations( - styles, - selector, - useRootPaddingAlign, - tree, - isTemplate - ); - if ( declarations?.length ) { - ruleset += `:where(${ selector }){${ declarations.join( - ';' - ) };}`; - } + // Process the remaining block styles (they use either normal block class or __experimentalSelector). + const declarations = getStylesDeclarations( + styles, + selector, + useRootPaddingAlign, + tree, + isTemplate + ); + if ( declarations?.length ) { + ruleset += `:where(${ selector }){${ declarations.join( + ';' + ) };}`; + } // Check for pseudo selector in `styles` and handle separately. const pseudoSelectorStyles = Object.entries( styles ).filter( @@ -1219,9 +1232,12 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) { const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' ); const hasBlockGapSupport = blockGap !== null; const hasFallbackGapSupport = ! hasBlockGapSupport; // This setting isn't useful yet: it exists as a placeholder for a future explicit fallback styles support. - const disableLayoutStyles = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); - return !! getSettings().disableLayoutStyles; + + const { disableLayoutStyles } = useSelect( ( select ) => { + const _settings = select( blockEditorStore ).getSettings(); + return { + disableLayoutStyles: !! _settings.disableLayoutStyles, + }; } ); const blockContext = useContext( BlockContext ); diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index 3d47f42645afc..a96cd520f9c69 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -4,6 +4,7 @@ import { getBlockSupport } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { useCallback } from '@wordpress/element'; +import { safeDecodeURI } from '@wordpress/url'; /** * Internal dependencies @@ -16,6 +17,8 @@ import { useHasBackgroundPanel, hasBackgroundImageValue, } from '../components/global-styles/background-panel'; +import { ROOT_BLOCK_SELECTOR } from '../components/global-styles/utils'; +import { unlock } from '../lock-unlock'; export const BACKGROUND_SUPPORT_KEY = 'background'; @@ -50,52 +53,45 @@ export function hasBackgroundSupport( blockName, feature = 'any' ) { return !! support?.[ feature ]; } -export function setBackgroundStyleDefaults( backgroundStyle ) { - if ( ! backgroundStyle ) { - return; +/** + * Updates a styles object with default background values. + * + * @param {Object} blockStyles A styles object. + * @param {Object} options Optional settings. + * @param {string} options.stylesheetURI The URI of the current theme directory. + * @param {string} options.selector The block selector. + * @return {Object} Updated styles. + */ +export function setBackgroundStyleDefaults( blockStyles, options ) { + if ( + typeof blockStyles?.background !== 'object' || + Object.keys( blockStyles?.background ).length === 0 + ) { + return blockStyles; } - const backgroundImage = backgroundStyle?.backgroundImage; - let backgroundStylesWithDefaults; + const backgroundImage = blockStyles?.background?.backgroundImage; + const newBackgroundStyles = {}; // Set block background defaults. - if ( backgroundImage?.source === 'file' && !! backgroundImage?.url ) { - if ( ! backgroundStyle?.backgroundSize ) { - backgroundStylesWithDefaults = { - backgroundSize: 'cover', - }; + if ( options?.selector !== ROOT_BLOCK_SELECTOR && !! backgroundImage ) { + if ( ! blockStyles?.background?.backgroundSize ) { + newBackgroundStyles.backgroundSize = 'cover'; } if ( - 'contain' === backgroundStyle?.backgroundSize && - ! backgroundStyle?.backgroundPosition + 'contain' === blockStyles?.background?.backgroundSize && + ! blockStyles?.background?.backgroundPosition ) { - backgroundStylesWithDefaults = { - backgroundPosition: 'center', - }; + newBackgroundStyles.backgroundPosition = 'center'; } } - return backgroundStylesWithDefaults; -} - -function useBlockProps( { name, style } ) { - if ( - ! hasBackgroundSupport( name ) || - ! style?.background?.backgroundImage - ) { - return; - } - - const backgroundStyles = setBackgroundStyleDefaults( style?.background ); - - if ( ! backgroundStyles ) { - return; - } - return { - style: { - ...backgroundStyles, + ...blockStyles, + background: { + ...blockStyles.background, + ...newBackgroundStyles, }, }; } @@ -181,8 +177,56 @@ export function BackgroundImagePanel( { ); } +function useBlockProps( { name, style } ) { + const { backgroundImageURL } = useSelect( + ( select ) => { + const { getThemeFileURI } = unlock( select( blockEditorStore ) ); + let file = style?.background?.backgroundImage?.url; + if ( + !! style?.background?.backgroundImage?.url && + style?.background?.backgroundImage?.source === 'theme' + ) { + file = getThemeFileURI( style.background.backgroundImage.url ); + } + return { + backgroundImageURL: file, + }; + }, + [ style?.background?.backgroundImage ] + ); + + if ( + ! hasBackgroundSupport( name, 'backgroundImage' ) || + ! backgroundImageURL + ) { + return; + } + const newBackgroundStyles = {}; + if ( ! style?.background?.backgroundSize ) { + newBackgroundStyles.backgroundSize = 'cover'; + } + + if ( + 'contain' === style?.background?.backgroundSize && + ! style?.background?.backgroundPosition + ) { + newBackgroundStyles.backgroundPosition = 'center'; + } + + return { + style: { + // @TODO this should be backgroundImage. How to do that? + // Also, maybe consider reinstating https://github.com/WordPress/gutenberg/blob/fc98542a7dbba194bb4096d49cd0bd093b63f43e/packages/block-editor/src/hooks/background.js#L82 + backgroundImage: `url( '${ encodeURI( + safeDecodeURI( backgroundImageURL ) + ) }' )`, + ...newBackgroundStyles, + }, + }; +} + export default { - useBlockProps, attributeKeys: [ 'style' ], hasSupport: hasBackgroundSupport, + useBlockProps, }; diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index ec5cf29b49c5a..d2f4f5fb5ed1c 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -50,7 +50,6 @@ createBlockEditFilter( createBlockListBlockFilter( [ align, textAlign, - background, style, color, dimensions, @@ -60,6 +59,7 @@ createBlockListBlockFilter( [ border, position, childLayout, + background, ] ); createBlockSaveFilter( [ align, diff --git a/packages/block-editor/src/hooks/test/background.js b/packages/block-editor/src/hooks/test/background.js new file mode 100644 index 0000000000000..79be30d3eefb7 --- /dev/null +++ b/packages/block-editor/src/hooks/test/background.js @@ -0,0 +1,83 @@ +/** + * Internal dependencies + */ +import { setBackgroundStyleDefaults } from '../background'; +import { ROOT_BLOCK_SELECTOR } from '../../components/global-styles/utils'; + +describe( 'background', () => { + describe( 'setBackgroundStyleDefaults', () => { + it( 'should return the block styles if the block styles do not have a background', () => { + const blockStyles = { color: 'red' }; + expect( setBackgroundStyleDefaults( blockStyles ) ).toEqual( + blockStyles + ); + } ); + + it( 'should return the block styles if `background` is not an object', () => { + const blockStyles = { background: 'red' }; + expect( setBackgroundStyleDefaults( blockStyles ) ).toEqual( + blockStyles + ); + } ); + + it( 'should return the background size defaults', () => { + const blockStyles = { + background: { backgroundImage: 'some-image.jpg' }, + }; + expect( setBackgroundStyleDefaults( blockStyles ) ).toEqual( { + background: { + backgroundImage: 'some-image.jpg', + backgroundSize: 'cover', + }, + } ); + } ); + + it( 'should return an absolute theme URL path', () => { + const blockStyles = { + background: { + backgroundImage: { + source: 'theme', + url: '/some-image.jpg', + }, + }, + }; + const options = { themeDirURI: 'http://example.com' }; + expect( + setBackgroundStyleDefaults( blockStyles, options ) + ).toEqual( { + background: { + backgroundImage: { + source: 'theme', + url: 'http://example.com/some-image.jpg', + }, + backgroundSize: 'cover', + }, + } ); + } ); + + it( 'should not add background size defaults for root selector', () => { + const blockStyles = { + background: { + backgroundImage: { + source: 'theme', + url: '/some-image.jpg', + }, + }, + }; + const options = { + themeDirURI: 'http://example.com', + selector: ROOT_BLOCK_SELECTOR, + }; + expect( + setBackgroundStyleDefaults( blockStyles, options ) + ).toEqual( { + background: { + backgroundImage: { + source: 'theme', + url: 'http://example.com/some-image.jpg', + }, + }, + } ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index f10fcc4df2c72..d3c3e35c1f69d 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -35,6 +35,7 @@ import { useFlashEditableBlocks } from './components/use-flash-editable-blocks'; import { selectBlockPatternsKey, reusableBlocksSelectKey, + getThemeFileURIKey, } from './store/private-keys'; import { requiresWrapperOnCopy } from './components/writing-flow/utils'; import { PrivateRichText } from './components/rich-text/'; @@ -76,4 +77,5 @@ lock( privateApis, { requiresWrapperOnCopy, PrivateRichText, reusableBlocksSelectKey, + getThemeFileURIKey, } ); diff --git a/packages/block-editor/src/store/private-keys.js b/packages/block-editor/src/store/private-keys.js index f48612e7491c9..ec29e20a5c91d 100644 --- a/packages/block-editor/src/store/private-keys.js +++ b/packages/block-editor/src/store/private-keys.js @@ -1,2 +1,3 @@ export const selectBlockPatternsKey = Symbol( 'selectBlockPatternsKey' ); export const reusableBlocksSelectKey = Symbol( 'reusableBlocksSelect' ); +export const getThemeFileURIKey = Symbol( 'getThemeFileURI' ); diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index bb7f8fed18954..ddea83e4b611d 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -27,6 +27,7 @@ import { unlock } from '../lock-unlock'; import { selectBlockPatternsKey, reusableBlocksSelectKey, + getThemeFileURIKey, } from './private-keys'; export { getBlockSettings } from './get-block-settings'; @@ -412,6 +413,15 @@ export const getReusableBlocks = createRegistrySelector( } ); +export const getThemeFileURI = createRegistrySelector( + ( select ) => ( state, file ) => { + const getThemeFileURISelect = state.settings[ getThemeFileURIKey ]; + return getThemeFileURISelect + ? getThemeFileURISelect( select, file ) + : ''; + } +); + /** * Returns the element of the last element that had focus when focus left the editor canvas. * diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts index 761c6461d4aca..ed78906220723 100644 --- a/packages/core-data/src/entity-types/theme.ts +++ b/packages/core-data/src/entity-types/theme.ts @@ -12,10 +12,18 @@ declare module './base-entity-records' { * The theme's stylesheet. This uniquely identifies the theme. */ stylesheet: string; + /** + * The uri for the theme's stylesheet directory + */ + stylesheet_uri: string; /** * The theme's template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme's stylesheet. */ template: string; + /** + * The uri for the theme's template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme's stylesheet directory. + */ + temnplate_uri: string; /** * The theme author. */ diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts index 6280bb9631963..b5b6b14ebf2a3 100644 --- a/packages/core-data/src/private-selectors.ts +++ b/packages/core-data/src/private-selectors.ts @@ -50,3 +50,7 @@ export const getBlockPatternsForPostType = createRegistrySelector( () => [ select( STORE_NAME ).getBlockPatterns() ] ) ); + +export function getThemeFileURI( state: State, file: string ) { + return state.themeFileURIs?.[ file ]; +} diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 8e6be42524468..e2dd830b09247 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -623,6 +623,18 @@ export function defaultTemplates( state = {}, action ) { return state; } +export function themeFileURIs( state = {}, action ) { + switch ( action.type ) { + case 'RECEIVE_THEME_FILE_URI': + return { + ...state, + [ action.file ]: action.url, + }; + } + + return state; +} + export default combineReducers( { terms, users, @@ -644,4 +656,5 @@ export default combineReducers( { userPatternCategories, navigationFallbackId, defaultTemplates, + themeFileURIs, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 7de2c354ba2bf..ef953a1cfc981 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -896,3 +896,34 @@ export const getRevision = dispatch.receiveRevisions( kind, name, recordKey, record, query ); } }; + +export const getThemeFileURI = + ( file ) => + async ( { resolveSelect, dispatch } ) => { + if ( typeof file !== 'string' ) { + return; + } + + let trimmedFile = file.trim(); + trimmedFile = trimmedFile.startsWith( '/' ) ? trimmedFile : `/${ trimmedFile }`; + + const { stylesheet_uri, template_uri } = + await resolveSelect.getCurrentTheme(); + const url = `${ stylesheet_uri }${ trimmedFile }`; + + apiFetch( { url, method: 'HEAD', parse: false } ) + .then( () => { + dispatch( { + type: 'RECEIVE_THEME_FILE_URI', + file, + url, + } ); + } ) + .catch( () => { + dispatch( { + type: 'RECEIVE_THEME_FILE_URI', + file, + url: `${ template_uri }${ trimmedFile }`, + } ); + } ); + }; diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 242d516170d27..462227acd8b9f 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -46,6 +46,7 @@ export interface State { navigationFallbackId: EntityRecordKey; userPatternCategories: Array< UserPatternCategory >; defaultTemplates: Record< string, string >; + themeFileURIs: Record< string, string >; } type EntityRecordKey = string | number; diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index bcd0885614183..34cb647fbe45b 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -101,6 +101,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { const { allowRightClickOverrides, blockTypes, + currentTheme, focusMode, hasFixedToolbar, isDistractionFree, @@ -122,7 +123,9 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { getEntityRecord, getUserPatternCategories, getBlockPatternCategories, + getCurrentTheme, } = select( coreStore ); + const _currentTheme = getCurrentTheme(); const { get } = select( preferencesStore ); const { getBlockTypes } = select( blocksStore ); const { getBlocksByName, getBlockAttributes } = @@ -155,6 +158,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { postType, postId )?._links?.hasOwnProperty( 'wp:action-unfiltered-html' ), + currentTheme: _currentTheme, focusMode: get( 'core', 'focusMode' ), hasFixedToolbar: get( 'core', 'fixedToolbar' ) || ! isLargeViewport, @@ -273,6 +277,8 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { ), [ unlock( privateApis ).reusableBlocksSelectKey ]: __experimentalReusableBlocksSelect, + [ unlock( privateApis ).getThemeFileURIKey ]: ( select, file ) => + unlock( select( coreStore ) ).getThemeFileURI( file ), __experimentalBlockPatternCategories: blockPatternCategories, __experimentalUserPatternCategories: userPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => @@ -290,6 +296,10 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { // Check these two properties: they were not present in the site editor. __experimentalCreatePageEntity: createPageEntity, __experimentalUserCanCreatePages: userCanCreatePages, + __experimentalCurrentTheme: { + stylesheetURI: currentTheme?.stylesheet_uri, + templateURI: currentTheme?.template_uri, + }, pageOnFront, pageForPosts, __experimentalPreferPatternsOnRoot: postType === 'wp_template', @@ -308,6 +318,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { }, [ allowedBlockTypes, allowRightClickOverrides, + currentTheme, focusMode, forceDisableFocusMode, hasFixedToolbar, diff --git a/packages/style-engine/src/styles/background/index.ts b/packages/style-engine/src/styles/background/index.ts index 748cb15b4f309..a8c8679888e15 100644 --- a/packages/style-engine/src/styles/background/index.ts +++ b/packages/style-engine/src/styles/background/index.ts @@ -8,11 +8,7 @@ const backgroundImage = { name: 'backgroundImage', generate: ( style: Style, options: StyleOptions ) => { const _backgroundImage = style?.background?.backgroundImage; - if ( - typeof _backgroundImage === 'object' && - _backgroundImage?.source === 'file' && - _backgroundImage?.url - ) { + if ( typeof _backgroundImage === 'object' && _backgroundImage?.url ) { return [ { selector: options.selector, diff --git a/packages/style-engine/src/test/index.js b/packages/style-engine/src/test/index.js index 5869c4a349baa..fbccc8c48bc92 100644 --- a/packages/style-engine/src/test/index.js +++ b/packages/style-engine/src/test/index.js @@ -226,7 +226,6 @@ describe( 'getCSSRules', () => { { background: { backgroundImage: { - source: 'file', url: 'https://example.com/image.jpg', }, backgroundPosition: '50% 50%', diff --git a/phpunit/block-supports/background-test.php b/phpunit/block-supports/background-test.php deleted file mode 100644 index 165a65204793d..0000000000000 --- a/phpunit/block-supports/background-test.php +++ /dev/null @@ -1,216 +0,0 @@ -test_block_name = null; - $this->theme_root = realpath( __DIR__ . '/../data/themedir1' ); - $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; - - // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. - $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); - - add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); - - // Clear caches. - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - WP_Style_Engine_CSS_Rules_Store::remove_all_stores(); - } - - public function tear_down() { - $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; - - // Clear up the filters to modify the theme root. - remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); - remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); - remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); - - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - WP_Style_Engine_CSS_Rules_Store::remove_all_stores(); - unregister_block_type( $this->test_block_name ); - $this->test_block_name = null; - parent::tear_down(); - } - - public function filter_set_theme_root() { - return $this->theme_root; - } - - /** - * Tests that background image block support works as expected. - * - * @covers ::gutenberg_render_background_support - * - * @dataProvider data_background_block_support - * - * @param string $theme_name The theme to switch to. - * @param string $block_name The test block name to register. - * @param mixed $background_settings The background block support settings. - * @param mixed $background_style The background styles within the block attributes. - * @param string $expected_wrapper Expected markup for the block wrapper. - * @param string $wrapper Existing markup for the block wrapper. - */ - public function test_background_block_support( $theme_name, $block_name, $background_settings, $background_style, $expected_wrapper, $wrapper ) { - switch_theme( $theme_name ); - $this->test_block_name = $block_name; - - register_block_type( - $this->test_block_name, - array( - 'api_version' => 2, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => array( - 'background' => $background_settings, - ), - ) - ); - - $block = array( - 'blockName' => $block_name, - 'attrs' => array( - 'style' => array( - 'background' => $background_style, - ), - ), - ); - - $actual = gutenberg_render_background_support( $wrapper, $block ); - - $this->assertEquals( - $expected_wrapper, - $actual, - 'Background block wrapper markup should be correct' - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_background_block_support() { - return array( - 'background image style is applied' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-output', - 'background_settings' => array( - 'backgroundImage' => true, - ), - 'background_style' => array( - 'backgroundImage' => array( - 'url' => 'https://example.com/image.jpg', - 'source' => 'file', - ), - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - 'background image style is applied when backgroundImage is a string' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-output', - 'background_settings' => array( - 'backgroundImage' => true, - ), - 'background_style' => array( - 'backgroundImage' => "url('https://example.com/image.jpg')", - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - 'background image style with contain, position, and repeat is applied' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-output', - 'background_settings' => array( - 'backgroundImage' => true, - ), - 'background_style' => array( - 'backgroundImage' => array( - 'url' => 'https://example.com/image.jpg', - 'source' => 'file', - ), - 'backgroundRepeat' => 'no-repeat', - 'backgroundSize' => 'contain', - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - 'background image style is appended if a style attribute already exists' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-output', - 'background_settings' => array( - 'backgroundImage' => true, - ), - 'background_style' => array( - 'backgroundImage' => array( - 'url' => 'https://example.com/image.jpg', - 'source' => 'file', - ), - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - 'background image style is appended if a style attribute containing multiple styles already exists' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-output', - 'background_settings' => array( - 'backgroundImage' => true, - ), - 'background_style' => array( - 'backgroundImage' => array( - 'url' => 'https://example.com/image.jpg', - 'source' => 'file', - ), - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - 'background image style is not applied if the block does not support background image' => array( - 'theme_name' => 'block-theme-child-with-fluid-typography', - 'block_name' => 'test/background-rules-are-not-output', - 'background_settings' => array( - 'backgroundImage' => false, - ), - 'background_style' => array( - 'backgroundImage' => array( - 'url' => 'https://example.com/image.jpg', - 'source' => 'file', - ), - ), - 'expected_wrapper' => '
Content
', - 'wrapper' => '
Content
', - ), - ); - } -}