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
',
- ),
- );
- }
-}