diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php
index 8e3c06159a1201..0ca4f81465c0ce 100644
--- a/lib/block-supports/background.php
+++ b/lib/block-supports/background.php
@@ -30,6 +30,30 @@ function gutenberg_register_background_support( $block_type ) {
}
}
+/**
+ * Given a theme.json or block background styles, returns the background styles for a block.
+ *
+ * @since 6.6.0
+ *
+ * @param array $background_styles Background style properties.
+ * @return array Style engine array of CSS string and style declarations.
+ */
+function gutenberg_get_background_support_styles( $background_styles = array() ) {
+ /*
+ * Styles backgrounds.
+ * Where a URL is not absolute (has no host fragment), it is assumed to be relative to the theme directory.
+ * Blocks, elements, and block variations are not yet supported.
+ */
+ if (
+ isset( $background_styles['backgroundImage']['url'] ) &&
+ is_string( $background_styles['backgroundImage']['url'] ) &&
+ ! isset( wp_parse_url( $background_styles['backgroundImage']['url'] )['host'] ) ) {
+ $background_styles['backgroundImage']['url'] = esc_url( get_theme_file_uri( $background_styles['backgroundImage']['url'] ) );
+ }
+ return gutenberg_style_engine_get_styles( array( 'background' => $background_styles ) );
+}
+
+
/**
* Renders the background styles to the block wrapper.
* This block support uses the `render_block` hook to ensure that
@@ -66,7 +90,7 @@ function gutenberg_render_background_support( $block_content, $block ) {
}
}
- $styles = gutenberg_style_engine_get_styles( array( 'background' => $background_styles ) );
+ $styles = gutenberg_get_background_support_styles( $background_styles );
if ( ! empty( $styles['css'] ) ) {
// Inject background styles to the first element, presuming it's the wrapper, if it exists.
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 77004253bea868..84b650c917929a 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -488,10 +488,10 @@ class WP_Theme_JSON_Gutenberg {
*/
const VALID_STYLES = array(
'background' => array(
- 'backgroundImage' => 'top',
- 'backgroundPosition' => 'top',
- 'backgroundRepeat' => 'top',
- 'backgroundSize' => 'top',
+ 'backgroundImage' => null,
+ 'backgroundPosition' => null,
+ 'backgroundRepeat' => null,
+ 'backgroundSize' => null,
),
'border' => array(
'color' => null,
@@ -2198,7 +2198,7 @@ protected static function compute_style_properties( $styles, $settings = array()
// Processes background styles.
if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
- $background_styles = gutenberg_style_engine_get_styles( array( 'background' => $styles['background'] ) );
+ $background_styles = gutenberg_get_background_support_styles( $styles['background'] );
$value = $background_styles['declarations'][ $css_property ] ?? $value;
}
diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php
index 8526093dc99ddb..1bbbc4f3ff5ee5 100644
--- a/lib/compat/wordpress-6.6/rest-api.php
+++ b/lib/compat/wordpress-6.6/rest-api.php
@@ -76,3 +76,75 @@ function gutenberg_add_class_list_to_public_post_types() {
}
}
add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' );
+
+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 f45c787e45589b..76fd5b7609ac70 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 );
@@ -293,7 +311,7 @@ function BackgroundImageToolsPanelItem( {
>
}
variant="secondary"
diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js
index e0de34cf2280e2..0ac01d08ecf574 100644
--- a/packages/block-editor/src/components/global-styles/hooks.js
+++ b/packages/block-editor/src/components/global-styles/hooks.js
@@ -368,6 +368,15 @@ export function useSettingsForBlockElement(
}
} );
+ [ 'backgroundImage', 'backgroundSize' ].forEach( ( key ) => {
+ if ( ! supportedStyles.includes( key ) ) {
+ updatedSettings.background = {
+ ...updatedSettings.background,
+ [ key ]: false,
+ };
+ }
+ } );
+
updatedSettings.shadow = supportedStyles.includes( 'shadow' )
? updatedSettings.shadow
: false;
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 95de3d4007079c..ec24a8e08c1e9d 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 23bb88efc9991e..637f5607196d3c 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
@@ -12,6 +12,7 @@ import { useSelect } from '@wordpress/data';
import { useContext, useMemo } from '@wordpress/element';
import { getCSSRules } from '@wordpress/style-engine';
import { privateApis as componentsPrivateApis } from '@wordpress/components';
+import { isURL } from '@wordpress/url';
/**
* Internal dependencies
@@ -35,6 +36,7 @@ import { LAYOUT_DEFINITIONS } from '../../layouts/definitions';
import { getValueFromObjectPath, setImmutably } from '../../utils/object';
import BlockContext from '../block-context';
import { unlock } from '../../lock-unlock';
+import useResolveThemeFileURIs from './use-resolve-theme-file-uris';
// 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 +316,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(
@@ -947,19 +950,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(
@@ -1217,11 +1220,15 @@ export function processCSSNesting( css, blockSelector ) {
*/
export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
const [ blockGap ] = useGlobalSetting( 'spacing.blockGap' );
+ const resolvedConfig = useResolveThemeFileURIs( mergedConfig );
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 );
@@ -1231,10 +1238,10 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
const { getBlockStyles } = useSelect( blocksStore );
return useMemo( () => {
- if ( ! mergedConfig?.styles || ! mergedConfig?.settings ) {
+ if ( ! resolvedConfig?.styles || ! resolvedConfig?.settings ) {
return [];
}
- const updatedConfig = updateConfigWithSeparator( mergedConfig );
+ const updatedConfig = updateConfigWithSeparator( resolvedConfig );
const blockSelectors = getBlockSelectors(
getBlockTypes(),
@@ -1297,7 +1304,7 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
}, [
hasBlockGapSupport,
hasFallbackGapSupport,
- mergedConfig,
+ resolvedConfig,
disableLayoutStyles,
isTemplate,
getBlockStyles,
diff --git a/packages/block-editor/src/components/global-styles/use-resolve-theme-file-uris.js b/packages/block-editor/src/components/global-styles/use-resolve-theme-file-uris.js
new file mode 100644
index 00000000000000..f235cf3548b709
--- /dev/null
+++ b/packages/block-editor/src/components/global-styles/use-resolve-theme-file-uris.js
@@ -0,0 +1,271 @@
+/**
+ * WordPress dependencies
+ */
+import { isURL, isValidPath } from '@wordpress/url';
+import { useSelect } from '@wordpress/data';
+import { useEffect, useState, useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../../lock-unlock';
+import { store as blockEditorStore } from '../../store';
+
+const resolvedURIsCache = new Map();
+
+function isRelativePath( url ) {
+ return isValidPath( url ) && ! isURL( url );
+}
+
+function getThemeFileURIs( file, stylesheetURI, templateURI ) {
+ file = file.trim();
+ file = file.startsWith( '/' ) ? file : `/${ file }`;
+ return [ `${ stylesheetURI }${ file }`, `${ templateURI }${ file }` ];
+}
+
+function fetchThemeFileURI( url, { onSuccess, onError, onFinally } ) {
+ if ( ! isURL( url ) || ! window?.fetch ) {
+ return null;
+ }
+ return window
+ .fetch( url, { method: 'HEAD', mode: 'same-origin' } )
+ .then( onSuccess )
+ .catch( onError )
+ .finally( onFinally );
+}
+
+async function resolveThemeJSONFileURIs(
+ relativePaths,
+ stylesheetURI,
+ templateURI,
+ themeStylesheet
+) {
+ const fetchPromises = [];
+ const unresolvedPaths = [ ...relativePaths ];
+ if ( relativePaths?.length ) {
+ relativePaths.forEach( ( path ) => {
+ const cacheKey = `${ themeStylesheet }-${ path }`;
+ if ( ! resolvedURIsCache.has( cacheKey ) ) {
+ const [ activeThemeFileURL, defaultThemeFileURL ] =
+ getThemeFileURIs( path, stylesheetURI, templateURI );
+ fetchPromises.push(
+ fetchThemeFileURI( activeThemeFileURL, {
+ onSuccess: () => {
+ resolvedURIsCache.set(
+ cacheKey,
+ activeThemeFileURL
+ );
+ },
+ onError: () => {
+ resolvedURIsCache.set(
+ cacheKey,
+ defaultThemeFileURL
+ );
+ },
+ onFinally: () => {
+ const index = unresolvedPaths.indexOf( path );
+ unresolvedPaths.splice( index, 1 );
+ },
+ } )
+ );
+ }
+ } );
+ }
+ /*
+
+ // Top-level styles.
+ if ( isRelativePath( config?.styles?.background?.backgroundImage?.url ) ) {
+ const cacheKey = `${ themeStylesheet }-${ config.styles.background.backgroundImage.url }`;
+ if ( ! resolvedURIsCache.has( cacheKey ) ) {
+ const [ activeThemeFileURL, defaultThemeFileURL ] =
+ getThemeFileURIs(
+ config.styles.background.backgroundImage.url,
+ stylesheetURI,
+ templateURI
+ );
+
+ fetchPromises.push(
+ fetchThemeFileURI( activeThemeFileURL, {
+ onSuccess: () => {
+ resolvedURIsCache.set( cacheKey, activeThemeFileURL );
+ },
+ onError: () => {
+ resolvedURIsCache.set( cacheKey, defaultThemeFileURL );
+ },
+ } )
+ );
+ }
+ }
+
+ // Block styles.
+ if (
+ config?.styles?.blocks &&
+ Object.keys( config.styles.blocks ).length
+ ) {
+ Object.entries( config.styles.blocks ).forEach(
+ ( [ , blockStyles ] ) => {
+ if (
+ isRelativePath(
+ blockStyles?.background?.backgroundImage?.url
+ )
+ ) {
+ const cacheKey = `${ themeStylesheet }-${ blockStyles?.background?.backgroundImage?.url }`;
+ if ( ! resolvedURIsCache.has( cacheKey ) ) {
+ const [ activeThemeFileURL, defaultThemeFileURL ] =
+ getThemeFileURIs(
+ blockStyles.background.backgroundImage.url,
+ stylesheetURI,
+ templateURI
+ );
+ fetchPromises.push(
+ fetchThemeFileURI( activeThemeFileURL, {
+ onSuccess: () => {
+ resolvedURIsCache.set(
+ cacheKey,
+ activeThemeFileURL
+ );
+ },
+ onError: () => {
+ resolvedURIsCache.set(
+ cacheKey,
+ defaultThemeFileURL
+ );
+ },
+ } )
+ );
+ }
+ }
+ }
+ );
+ }
+*/
+
+ await Promise.allSettled( fetchPromises );
+ return unresolvedPaths;
+}
+
+function getUnresolvedThemeFilePaths( config, themeStylesheet ) {
+ const paths = [];
+ if ( isRelativePath( config?.styles?.background?.backgroundImage?.url ) ) {
+ const cacheKey = `${ themeStylesheet }-${ config.styles.background.backgroundImage.url }`;
+ if ( ! resolvedURIsCache.has( cacheKey ) ) {
+ paths.push( config.styles.background.backgroundImage.url );
+ }
+ }
+
+ if (
+ config?.styles?.blocks &&
+ Object.keys( config.styles.blocks ).length
+ ) {
+ Object.entries( config.styles.blocks ).forEach(
+ ( [ , blockStyles ] ) => {
+ if (
+ isRelativePath(
+ blockStyles?.background?.backgroundImage?.url
+ )
+ ) {
+ const cacheKey = `${ themeStylesheet }-${ blockStyles?.background?.backgroundImage?.url }`;
+ if ( ! resolvedURIsCache.has( cacheKey ) ) {
+ paths.push( blockStyles.background.backgroundImage.url );
+ }
+ }
+ }
+ );
+ }
+
+ return paths;
+}
+
+function setResolvedThemeFilePaths( config, themeStylesheet ) {
+ if ( isRelativePath( config?.styles?.background?.backgroundImage?.url ) ) {
+ const cacheKey = `${ themeStylesheet }-${ config.styles.background.backgroundImage.url }`;
+ if ( resolvedURIsCache.has( cacheKey ) ) {
+ config.styles.background.backgroundImage.url =
+ resolvedURIsCache.get( cacheKey );
+ }
+ }
+
+ if (
+ config?.styles?.blocks &&
+ Object.keys( config.styles.blocks ).length
+ ) {
+ Object.entries( config.styles.blocks ).forEach(
+ ( [ blockName, blockStyles ] ) => {
+ if (
+ isRelativePath(
+ blockStyles?.background?.backgroundImage?.url
+ )
+ ) {
+ const cacheKey = `${ themeStylesheet }-${ blockStyles?.background?.backgroundImage?.url }`;
+ if ( resolvedURIsCache.has( cacheKey ) ) {
+ config.styles.blocks[
+ blockName
+ ].background.backgroundImage.url =
+ resolvedURIsCache.get( cacheKey );
+ }
+ }
+ }
+ );
+ }
+
+ return config;
+}
+
+/*
+ Ideas:
+ Check that this needs to be run before running it by parsing out URLs early?
+ Can a function returning an array be a useEffect dependency?
+ */
+export default function useResolveThemeFileURIs( mergedConfig ) {
+ const { stylesheetURI, templateURI, themeStylesheet } = useSelect(
+ ( select ) => {
+ const {
+ stylesheet_uri: _stylesheetURI,
+ template_uri: _templateURI,
+ stylesheet: _themeStylesheet,
+ } = unlock( select( blockEditorStore ) ).getCurrentTheme();
+ return {
+ stylesheetURI: _stylesheetURI,
+ templateURI: _templateURI,
+ themeStylesheet: _themeStylesheet,
+ };
+ }
+ );
+
+ const [ unresolvedPaths, setUnresolvedPaths ] = useState(
+ getUnresolvedThemeFilePaths( mergedConfig, themeStylesheet )
+ );
+
+ useEffect( () => {
+ if (
+ ! mergedConfig?.styles ||
+ ! unresolvedPaths?.length ||
+ ! stylesheetURI ||
+ ! templateURI ||
+ ! themeStylesheet
+ ) {
+ return;
+ }
+
+ ( async () => {
+ try {
+ const _unresolvedPaths = await resolveThemeJSONFileURIs(
+ unresolvedPaths,
+ stylesheetURI,
+ templateURI,
+ themeStylesheet
+ );
+ setUnresolvedPaths( _unresolvedPaths );
+ } catch ( err ) {}
+ } )();
+ }, [
+ mergedConfig,
+ unresolvedPaths,
+ setUnresolvedPaths,
+ stylesheetURI,
+ templateURI,
+ themeStylesheet,
+ ] );
+
+ return setResolvedThemeFilePaths( mergedConfig, themeStylesheet );
+}
diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js
index 3e23efcbf5fc96..b3bd15b53c8dbd 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, isURL } 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,56 +53,6 @@ export function hasBackgroundSupport( blockName, feature = 'any' ) {
return !! support?.[ feature ];
}
-export function setBackgroundStyleDefaults( backgroundStyle ) {
- if ( ! backgroundStyle ) {
- return;
- }
-
- const backgroundImage = backgroundStyle?.backgroundImage;
- let backgroundStylesWithDefaults;
-
- // Set block background defaults.
- if ( !! backgroundImage?.url ) {
- if ( ! backgroundStyle?.backgroundSize ) {
- backgroundStylesWithDefaults = {
- backgroundSize: 'cover',
- };
- }
-
- if (
- 'contain' === backgroundStyle?.backgroundSize &&
- ! backgroundStyle?.backgroundPosition
- ) {
- backgroundStylesWithDefaults = {
- 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,
- },
- };
-}
-
/**
* Generates a CSS class name if an background image is set.
*
@@ -181,8 +134,55 @@ 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 && ! isURL( style?.background?.backgroundImage?.url )
+ ) {
+ 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 ec5cf29b49c5a6..d2f4f5fb5ed1ce 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/private-apis.js b/packages/block-editor/src/private-apis.js
index f10fcc4df2c726..b42f1390c27927 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,
+ getCurrentThemeKey,
} 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,
+ getCurrentThemeKey,
} );
diff --git a/packages/block-editor/src/store/private-keys.js b/packages/block-editor/src/store/private-keys.js
index f48612e7491c9c..7a48d59753d67d 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 getCurrentThemeKey = Symbol( 'getCurrentTheme' );
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index bb7f8fed189549..71cf90c1de860c 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,
+ getCurrentThemeKey,
} from './private-keys';
export { getBlockSettings } from './get-block-settings';
@@ -496,3 +497,13 @@ export function getTemporarilyEditingAsBlocks( state ) {
export function getTemporarilyEditingFocusModeToRevert( state ) {
return state.temporarilyEditingFocusModeRevert;
}
+
+const EMPTY_OBJECT = {};
+export const getCurrentTheme = createRegistrySelector(
+ ( select ) => ( state, file ) => {
+ const getCurrentTheme = state.settings[ getCurrentThemeKey ];
+ return getCurrentTheme
+ ? getCurrentTheme( select )
+ : EMPTY_OBJECT;
+ }
+);
diff --git a/packages/core-data/src/entity-types/theme.ts b/packages/core-data/src/entity-types/theme.ts
index 761c6461d4aca8..ed78906220723d 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/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js
index bcd08856141831..6e16650a752956 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,
@@ -290,6 +294,10 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) {
// Check these two properties: they were not present in the site editor.
__experimentalCreatePageEntity: createPageEntity,
__experimentalUserCanCreatePages: userCanCreatePages,
+ [ unlock( privateApis ).getCurrentThemeKey ]: ( select ) =>
+ unlock( select( coreStore ) ).getCurrentTheme(
+ postType
+ ),
pageOnFront,
pageForPosts,
__experimentalPreferPatternsOnRoot: postType === 'wp_template',
@@ -308,6 +316,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) {
}, [
allowedBlockTypes,
allowRightClickOverrides,
+ currentTheme,
focusMode,
forceDisableFocusMode,
hasFixedToolbar,
diff --git a/phpunit/block-supports/background-test.php b/phpunit/block-supports/background-test.php
index 165a65204793d1..865b698f283d00 100644
--- a/phpunit/block-supports/background-test.php
+++ b/phpunit/block-supports/background-test.php
@@ -157,8 +157,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
),
'backgroundRepeat' => 'no-repeat',
'backgroundSize' => 'contain',
@@ -189,8 +188,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => '
Content
',
@@ -204,8 +202,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
- 'url' => 'https://example.com/image.jpg',
- 'source' => 'file',
+ 'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => 'Content
',
@@ -213,4 +210,57 @@ public function data_background_block_support() {
),
);
}
+
+ /**
+ * Tests generating background styles.
+ *
+ * @covers ::gutenberg_get_background_support_styles
+ *
+ * @dataProvider data_get_background_support_styles
+ *
+ * @param mixed $background_style The background styles within the block attributes.
+ * @param string $expected_css Expected markup for the block wrapper.
+ */
+ public function test_get_background_support_styles( $background_style, $expected_css ) {
+ switch_theme( 'block-theme' );
+ $actual = gutenberg_get_background_support_styles( $background_style )['css'] ?? null;
+
+ $this->assertEquals(
+ $expected_css,
+ $actual,
+ 'Background CSS should be correct.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array
+ */
+ public function data_get_background_support_styles() {
+ return array(
+ 'no css generated with no block styles' => array(
+ 'background_style' => array(
+ 'backgroundImage' => array(),
+ ),
+ 'expected_css' => null,
+ ),
+ 'css generated with theme background image path' => array(
+ 'background_style' => array(
+ 'backgroundImage' => array(
+ 'url' => '/assets/image/not_there.png',
+ ),
+ ),
+ 'expected_css' => "background-image:url('http://localhost:8889/wp-content/plugins/gutenberg/phpunit/data/themedir1/block-theme/assets/image/not_there.png');",
+ ),
+ 'css generated with theme background image path and no preceding slash' => array(
+ 'background_style' => array(
+ 'backgroundImage' => array(
+ 'url' => 'assets/image/not_there.png',
+ ),
+ ),
+ 'expected_css' => "background-image:url('http://localhost:8889/wp-content/plugins/gutenberg/phpunit/data/themedir1/block-theme/assets/image/not_there.png');",
+ ),
+ );
+ }
}
diff --git a/test/emptytheme/styles/variation.json b/test/emptytheme/styles/variation.json
index 06f672f6fd25d7..64cbf060755792 100644
--- a/test/emptytheme/styles/variation.json
+++ b/test/emptytheme/styles/variation.json
@@ -1,5 +1,6 @@
{
"version": 3,
+ "title": "hello",
"settings": {
"color": {
"palette": [
@@ -12,10 +13,22 @@
}
},
"styles": {
+ "background": {
+ "backgroundImage": {
+ "url": "img/2.jpg"
+ },
+ "backgroundSize": "contain"
+ },
"blocks": {
- "core/post-title": {
- "typography": {
- "fontWeight": "700"
+ "core/group": {
+ "color": {
+ "text": "yellow"
+ },
+ "background": {
+ "backgroundImage": {
+ "url": "img/3.jpg"
+ },
+ "backgroundSize": "auto"
}
}
}
diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json
index 09f11eeeff8cfb..295ee4c8f0ff59 100644
--- a/test/emptytheme/theme.json
+++ b/test/emptytheme/theme.json
@@ -1,19 +1,15 @@
{
- "$schema": "../../schemas/json/theme.json",
- "version": 3,
+ "$schema": "https://schemas.wp.org/trunk/theme.json",
+ "version": 2,
"settings": {
- "appearanceTools": true,
- "layout": {
- "contentSize": "840px",
- "wideSize": "1100px"
- }
+ "appearanceTools": true
},
- "customTemplates": [
- {
- "name": "custom-template",
- "title": "Custom",
- "postTypes": [ "post" ]
+ "styles": {
+ "background": {
+ "backgroundImage": {
+ "url": "img/1.jpg"
+ },
+ "backgroundSize": "cover"
}
- ],
- "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ]
+ }
}