From f875c12098332cd4772bce411932fb4598b31b63 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 21 Feb 2024 17:32:54 +0400 Subject: [PATCH 01/11] Try: Add 'label' argument to the 'register_setting' method --- ...ass-gutenberg-rest-settings-controller.php | 69 +++++++++++++++++++ lib/compat/wordpress-6.6/option.php | 31 +++++++++ lib/compat/wordpress-6.6/rest-api.php | 16 +++++ lib/load.php | 5 ++ 4 files changed, 121 insertions(+) create mode 100644 lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php create mode 100644 lib/compat/wordpress-6.6/option.php create mode 100644 lib/compat/wordpress-6.6/rest-api.php diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php new file mode 100644 index 00000000000000..1f3e24ded13aaf --- /dev/null +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php @@ -0,0 +1,69 @@ + $args ) { + if ( empty( $args['show_in_rest'] ) ) { + continue; + } + + $rest_args = array(); + + if ( is_array( $args['show_in_rest'] ) ) { + $rest_args = $args['show_in_rest']; + } + + $defaults = array( + 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, + 'schema' => array(), + ); + + $rest_args = array_merge( $defaults, $rest_args ); + + $default_schema = array( + 'type' => empty( $args['type'] ) ? null : $args['type'], + 'label' => empty( $args['label'] ) ? '' : $args['label'], // Note: Only change to the method. + 'description' => empty( $args['description'] ) ? '' : $args['description'], + 'default' => isset( $args['default'] ) ? $args['default'] : null, + ); + + $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); + $rest_args['option_name'] = $name; + + // Skip over settings that don't have a defined type in the schema. + if ( empty( $rest_args['schema']['type'] ) ) { + continue; + } + + /* + * Allow the supported types for settings, as we don't want invalid types + * to be updated with arbitrary values that we can't do decent sanitizing for. + */ + if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) { + continue; + } + + $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] ); + + $rest_options[ $rest_args['name'] ] = $rest_args; + } + + // Note: The `label` is available in schema here, but is missing from the `OPTIONS` response. + + return $rest_options; + } +} diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php new file mode 100644 index 00000000000000..e3af4d76ec551d --- /dev/null +++ b/lib/compat/wordpress-6.6/option.php @@ -0,0 +1,31 @@ + __( 'Title' ), + 'blogdescription' => __( 'Tagline' ), + 'site_logo' => __( 'Logo' ), + 'site_icon' => __( 'Icon' ), + 'show_on_front' => __( 'Show on front' ), + 'page_on_front' => __( 'Page on front' ), + 'posts_per_page' => __( 'Maximum posts per page' ), + 'default_comment_status' => __( 'Allow comments on new posts' ), + ); + + if ( isset( $settings_label_map[ $option_name ] ) ) { + $args['label'] = $settings_label_map[ $option_name ]; + } + + return $args; +} +add_filter( 'register_setting_args', 'gutenberg_update_initial_settings', 10, 4 ); diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php new file mode 100644 index 00000000000000..d221054683695d --- /dev/null +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -0,0 +1,16 @@ +register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_settings_routes' ); diff --git a/lib/load.php b/lib/load.php index a9ce52385ab48f..9c0998df879c4c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -46,6 +46,10 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php'; require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php'; + // WordPress 6.6 compat. + require_once __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php'; + require_once __DIR__ . '/compat/wordpress-6.6/rest-api.php'; + // Plugin specific code. require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php'; require_once __DIR__ . '/rest-api.php'; @@ -126,6 +130,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.6 compat. require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; +require __DIR__ . '/compat/wordpress-6.6/option.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; From cffa889cc6dae6363da919b8c4d9e9ba37eeca09 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 21 Feb 2024 19:17:26 +0400 Subject: [PATCH 02/11] Use title keywork --- .../wordpress-6.6/class-gutenberg-rest-settings-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php index 1f3e24ded13aaf..39936098eee861 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php @@ -36,7 +36,7 @@ protected function get_registered_options() { $default_schema = array( 'type' => empty( $args['type'] ) ? null : $args['type'], - 'label' => empty( $args['label'] ) ? '' : $args['label'], // Note: Only change to the method. + 'title' => empty( $args['label'] ) ? '' : $args['label'], // Note: Only change to the method. 'description' => empty( $args['description'] ) ? '' : $args['description'], 'default' => isset( $args['default'] ) ? $args['default'] : null, ); From bf0b9fff3618150d95d2d5cabc6db6c78812f0e9 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 21 Feb 2024 19:50:23 +0400 Subject: [PATCH 03/11] Fix route override --- ...ass-gutenberg-rest-settings-controller.php | 32 ++++++++++++++++++- lib/compat/wordpress-6.6/option.php | 2 +- lib/compat/wordpress-6.6/rest-api.php | 5 ++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php index 39936098eee861..3704d9101226d9 100644 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php +++ b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php @@ -6,6 +6,36 @@ */ class Gutenberg_REST_Settings_Controller extends WP_REST_Settings_Controller { + /** + * Registers the routes for the site's settings. + * + * @since 4.7.0 + * + * @see register_rest_route() + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'args' => array(), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ), + true // Override. + ); + } + /** * Retrieves all of the registered options for the Settings API. * @@ -36,7 +66,7 @@ protected function get_registered_options() { $default_schema = array( 'type' => empty( $args['type'] ) ? null : $args['type'], - 'title' => empty( $args['label'] ) ? '' : $args['label'], // Note: Only change to the method. + 'title' => empty( $args['title'] ) ? '' : $args['title'], // Note: Only change to the method. 'description' => empty( $args['description'] ) ? '' : $args['description'], 'default' => isset( $args['default'] ) ? $args['default'] : null, ); diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php index e3af4d76ec551d..3be1e3bfb4cd3c 100644 --- a/lib/compat/wordpress-6.6/option.php +++ b/lib/compat/wordpress-6.6/option.php @@ -23,7 +23,7 @@ function gutenberg_update_initial_settings( $args, $defaults, $option_group, $op ); if ( isset( $settings_label_map[ $option_name ] ) ) { - $args['label'] = $settings_label_map[ $option_name ]; + $args['title'] = $settings_label_map[ $option_name ]; } return $args; diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index d221054683695d..98e5fabdced8c4 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -8,9 +8,12 @@ /** * Overrides `/settings` REST API routes. + * + * Note: Needs to be high priority than `create_initial_rest_routes` + * to override the default settings routes. */ function gutenberg_register_rest_settings_routes() { $settings = new Gutenberg_REST_Settings_Controller(); $settings->register_routes(); } -add_action( 'rest_api_init', 'gutenberg_register_rest_settings_routes' ); +add_action( 'rest_api_init', 'gutenberg_register_rest_settings_routes', 100 ); From 30ba923495a3128462bf6f672f939192e94d4f0b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 21 Feb 2024 21:11:40 +0400 Subject: [PATCH 04/11] Use just filter --- ...ass-gutenberg-rest-settings-controller.php | 99 ------------------- lib/compat/wordpress-6.6/option.php | 20 +++- lib/compat/wordpress-6.6/rest-api.php | 19 ---- lib/load.php | 4 - 4 files changed, 18 insertions(+), 124 deletions(-) delete mode 100644 lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php delete mode 100644 lib/compat/wordpress-6.6/rest-api.php diff --git a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php b/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php deleted file mode 100644 index 3704d9101226d9..00000000000000 --- a/lib/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php +++ /dev/null @@ -1,99 +0,0 @@ -namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'args' => array(), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ), - true // Override. - ); - } - - /** - * Retrieves all of the registered options for the Settings API. - * - * @since 4.7.0 - * - * @return array Array of registered options. - */ - protected function get_registered_options() { - $rest_options = array(); - - foreach ( get_registered_settings() as $name => $args ) { - if ( empty( $args['show_in_rest'] ) ) { - continue; - } - - $rest_args = array(); - - if ( is_array( $args['show_in_rest'] ) ) { - $rest_args = $args['show_in_rest']; - } - - $defaults = array( - 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name, - 'schema' => array(), - ); - - $rest_args = array_merge( $defaults, $rest_args ); - - $default_schema = array( - 'type' => empty( $args['type'] ) ? null : $args['type'], - 'title' => empty( $args['title'] ) ? '' : $args['title'], // Note: Only change to the method. - 'description' => empty( $args['description'] ) ? '' : $args['description'], - 'default' => isset( $args['default'] ) ? $args['default'] : null, - ); - - $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); - $rest_args['option_name'] = $name; - - // Skip over settings that don't have a defined type in the schema. - if ( empty( $rest_args['schema']['type'] ) ) { - continue; - } - - /* - * Allow the supported types for settings, as we don't want invalid types - * to be updated with arbitrary values that we can't do decent sanitizing for. - */ - if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) { - continue; - } - - $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] ); - - $rest_options[ $rest_args['name'] ] = $rest_args; - } - - // Note: The `label` is available in schema here, but is missing from the `OPTIONS` response. - - return $rest_options; - } -} diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php index 3be1e3bfb4cd3c..cda3583e2caf7a 100644 --- a/lib/compat/wordpress-6.6/option.php +++ b/lib/compat/wordpress-6.6/option.php @@ -22,8 +22,24 @@ function gutenberg_update_initial_settings( $args, $defaults, $option_group, $op 'default_comment_status' => __( 'Allow comments on new posts' ), ); - if ( isset( $settings_label_map[ $option_name ] ) ) { - $args['title'] = $settings_label_map[ $option_name ]; + if ( ! isset( $settings_label_map[ $option_name ] ) ) { + return $args; + } + + $args['label'] = $settings_label_map[ $option_name ]; + $schema = array( 'title' => $args['label'] ); + + if ( ! is_array( $args['show_in_rest'] ) ) { + $args['show_in_rest'] = array( + 'schema' => $schema, + ); + return $args; + } + + if ( ! empty( $args['show_in_rest']['schema'] ) ) { + $args['show_in_rest']['schema'] = array_merge( $args['show_in_rest']['schema'], $schema ); + } else { + $args['show_in_rest']['schema'] = $schema; } return $args; diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php deleted file mode 100644 index 98e5fabdced8c4..00000000000000 --- a/lib/compat/wordpress-6.6/rest-api.php +++ /dev/null @@ -1,19 +0,0 @@ -register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_rest_settings_routes', 100 ); diff --git a/lib/load.php b/lib/load.php index 9c0998df879c4c..da0b04d2f269d0 100644 --- a/lib/load.php +++ b/lib/load.php @@ -46,10 +46,6 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php'; require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php'; - // WordPress 6.6 compat. - require_once __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-settings-controller.php'; - require_once __DIR__ . '/compat/wordpress-6.6/rest-api.php'; - // Plugin specific code. require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php'; require_once __DIR__ . '/rest-api.php'; From 579dce72992dfa6cd27dd98d8fc1dc8dea9497b1 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 21 Feb 2024 21:39:45 +0400 Subject: [PATCH 05/11] Update getOrLoadEntitiesConfig --- packages/core-data/src/actions.js | 10 +-- packages/core-data/src/entities.js | 93 +++++++++++++++---------- packages/core-data/src/resolvers.js | 10 +-- packages/core-data/src/test/entities.js | 15 +++- 4 files changed, 79 insertions(+), 49 deletions(-) diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 49776d0562984f..454250e8ad13c9 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -286,7 +286,7 @@ export const deleteEntityRecord = { __unstableFetch = apiFetch, throwOnError = false } = {} ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.kind === kind && config.name === name ); @@ -503,7 +503,7 @@ export const saveEntityRecord = } = {} ) => async ( { select, resolveSelect, dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.kind === kind && config.name === name ); @@ -780,7 +780,7 @@ export const saveEditedEntityRecord = if ( ! select.hasEditsForEntityRecord( kind, name, recordId ) ) { return; } - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.kind === kind && config.name === name ); @@ -824,7 +824,7 @@ export const __experimentalSaveSpecifiedEntityEdits = setNestedValue( editsToSave, item, getNestedValue( edits, item ) ); } - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.kind === kind && config.name === name ); @@ -948,7 +948,7 @@ export function receiveDefaultTemplateId( query, templateId ) { export const receiveRevisions = ( kind, name, recordKey, records, query, invalidateCache = false, meta ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.kind === kind && config.name === name ); diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 4587f6891c505b..c722a8ff02bb6a 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -61,36 +61,6 @@ export const rootEntitiesConfig = [ syncObjectType: 'root/base', getSyncObjectId: () => 'index', }, - { - label: __( 'Site' ), - name: 'site', - kind: 'root', - baseURL: '/wp/v2/settings', - // The entity doesn't support selecting multiple records. - // The property is maintained for backward compatibility. - plural: 'sites', - getTitle: ( record ) => { - return record?.title ?? __( 'Site Title' ); - }, - syncConfig: { - fetch: async () => { - return apiFetch( { path: '/wp/v2/settings' } ); - }, - applyChangesToDoc: ( doc, changes ) => { - const document = doc.getMap( 'document' ); - Object.entries( changes ).forEach( ( [ key, value ] ) => { - if ( document.get( key ) !== value ) { - document.set( key, value ); - } - } ); - }, - fromCRDTDoc: ( doc ) => { - return doc.getMap( 'document' ).toJSON(); - }, - }, - syncObjectType: 'root/site', - getSyncObjectId: () => 'index', - }, { label: __( 'Post Type' ), name: 'postType', @@ -253,6 +223,7 @@ export const rootEntitiesConfig = [ export const additionalEntityConfigLoaders = [ { kind: 'postType', loadEntities: loadPostTypeEntities }, { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, + { kind: 'root', loadEntities: loadSiteEntity }, ]; /** @@ -409,6 +380,48 @@ async function loadTaxonomyEntities() { } ); } +/** + * Returns the Site entity. + * + * @return {Promise} Entity promise + */ +async function loadSiteEntity() { + const entity = { + label: __( 'Site' ), + name: 'site', + kind: 'root', + baseURL: '/wp/v2/settings', + syncConfig: { + fetch: async () => { + return apiFetch( { path: '/wp/v2/settings' } ); + }, + applyChangesToDoc: ( doc, changes ) => { + const document = doc.getMap( 'document' ); + Object.entries( changes ).forEach( ( [ key, value ] ) => { + if ( document.get( key ) !== value ) { + document.set( key, value ); + } + } ); + }, + fromCRDTDoc: ( doc ) => { + return doc.getMap( 'document' ).toJSON(); + }, + }, + syncObjectType: 'root/site', + getSyncObjectId: () => 'index', + meta: {}, + }; + + const site = await apiFetch( { + path: '/wp/v2/settings', + method: 'OPTIONS', + } ); + + const labels = site?.schema?.properties ?? {}; + + return [ { ...entity, meta: { labels } } ]; +} + /** * Returns the entity's getter method name given its kind and name or plural name. * @@ -446,14 +459,18 @@ function registerSyncConfigs( configs ) { * Loads the kind entities into the store. * * @param {string} kind Kind - * + * @param {string} name Name * @return {(thunkArgs: object) => Promise} Entities */ export const getOrLoadEntitiesConfig = - ( kind ) => + ( kind, name ) => async ( { select, dispatch } ) => { let configs = select.getEntitiesConfig( kind ); - if ( configs && configs.length !== 0 ) { + const hasConfig = !! configs?.find( + ( config ) => config.kind === kind && config.name === name + ); + + if ( configs?.length > 0 && hasConfig ) { if ( window.__experimentalEnableSync ) { if ( process.env.IS_GUTENBERG_PLUGIN ) { registerSyncConfigs( configs ); @@ -463,9 +480,13 @@ export const getOrLoadEntitiesConfig = return configs; } - const loader = additionalEntityConfigLoaders.find( - ( l ) => l.kind === kind - ); + const loader = additionalEntityConfigLoaders.find( ( l ) => { + if ( ! name || ! l.name ) { + return l.kind === kind; + } + + return l.kind === kind && l.name === name; + } ); if ( ! loader ) { return []; } diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 154222dc183ebf..bc9a0ce28d9e94 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -59,7 +59,7 @@ export const getCurrentUser = export const getEntityRecord = ( kind, name, key = '', query ) => async ( { select, dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind ); @@ -194,7 +194,7 @@ export const getEditedEntityRecord = forwardResolver( 'getEntityRecord' ); export const getEntityRecords = ( kind, name, query = {} ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind ); @@ -429,7 +429,7 @@ export const canUser = export const canUserEditEntityRecord = ( kind, name, recordId ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind ); @@ -726,7 +726,7 @@ export const getDefaultTemplateId = export const getRevisions = ( kind, name, recordKey, query = {} ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind ); @@ -851,7 +851,7 @@ getRevisions.shouldInvalidate = ( action, kind, name, recordKey ) => export const getRevision = ( kind, name, recordKey, revisionKey, query ) => async ( { dispatch } ) => { - const configs = await dispatch( getOrLoadEntitiesConfig( kind ) ); + const configs = await dispatch( getOrLoadEntitiesConfig( kind, name ) ); const entityConfig = configs.find( ( config ) => config.name === name && config.kind === kind ); diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index e142f9bf70c6c7..798fa93a0723cf 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -54,7 +54,10 @@ describe( 'getKindEntities', () => { getEntitiesConfig: jest.fn( () => entities ), }; const entities = [ { kind: 'postType' } ]; - await getOrLoadEntitiesConfig( 'postType' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( + 'postType', + undefined + )( { dispatch, select } ); expect( dispatch ).not.toHaveBeenCalled(); } ); @@ -63,7 +66,10 @@ describe( 'getKindEntities', () => { const select = { getEntitiesConfig: jest.fn( () => [] ), }; - await getOrLoadEntitiesConfig( 'unknownKind' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( + 'unknownKind', + undefined + )( { dispatch, select } ); expect( dispatch ).not.toHaveBeenCalled(); } ); @@ -85,7 +91,10 @@ describe( 'getKindEntities', () => { }; triggerFetch.mockImplementation( () => fetchedEntities ); - await getOrLoadEntitiesConfig( 'postType' )( { dispatch, select } ); + await getOrLoadEntitiesConfig( + 'postType', + 'post' + )( { dispatch, select } ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); expect( dispatch.mock.calls[ 0 ][ 0 ].type ).toBe( 'ADD_ENTITIES' ); expect( dispatch.mock.calls[ 0 ][ 0 ].entities.length ).toBe( 1 ); From 96ee0ea966de2f0ffe8cf3febbdab42d8bbd0b1b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 27 Feb 2024 09:47:34 +0400 Subject: [PATCH 06/11] Temp update selectors --- packages/edit-site/src/components/site-hub/index.js | 4 ++-- .../sync-state-with-url/use-init-edited-entity-from-url.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/edit-site/src/components/site-hub/index.js b/packages/edit-site/src/components/site-hub/index.js index f79df2df367090..4ebabae9a3b450 100644 --- a/packages/edit-site/src/components/site-hub/index.js +++ b/packages/edit-site/src/components/site-hub/index.js @@ -41,7 +41,7 @@ const SiteHub = memo( ( { isTransparent, className } ) => { ); const { - getSite, + getEntityRecord, getUnstableBase, // Site index. } = select( coreStore ); @@ -50,7 +50,7 @@ const SiteHub = memo( ( { isTransparent, className } ) => { dashboardLink: getSettings().__experimentalDashboardLink || 'index.php', homeUrl: getUnstableBase()?.home, - siteTitle: getSite()?.title, + siteTitle: getEntityRecord( 'root', 'site' )?.title, }; }, [] diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 442056abd0e8a2..fb142b985e11bf 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -30,9 +30,9 @@ const postTypesWithoutParentTemplate = [ function useResolveEditedEntityAndContext( { path, postId, postType } ) { const { hasLoadedAllDependencies, homepageId, url, frontPageTemplateId } = useSelect( ( select ) => { - const { getSite, getUnstableBase, getEntityRecords } = + const { getEntityRecord, getUnstableBase, getEntityRecords } = select( coreDataStore ); - const siteData = getSite(); + const siteData = getEntityRecord( 'root', 'site' ); const base = getUnstableBase(); const templates = getEntityRecords( 'postType', From 0c1a177c550fa69b473085993cf8625a793afb2c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 27 Feb 2024 10:29:41 +0400 Subject: [PATCH 07/11] Finalize first iteration --- lib/compat/wordpress-6.6/option.php | 10 +++-- packages/core-data/src/entities.js | 19 ++++++--- .../hooks/use-is-dirty.js | 40 +++++++++---------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php index cda3583e2caf7a..2d04b179fbea28 100644 --- a/lib/compat/wordpress-6.6/option.php +++ b/lib/compat/wordpress-6.6/option.php @@ -22,13 +22,15 @@ function gutenberg_update_initial_settings( $args, $defaults, $option_group, $op 'default_comment_status' => __( 'Allow comments on new posts' ), ); - if ( ! isset( $settings_label_map[ $option_name ] ) ) { - return $args; + if ( isset( $settings_label_map[ $option_name ] ) ) { + $args['label'] = $settings_label_map[ $option_name ]; } - $args['label'] = $settings_label_map[ $option_name ]; - $schema = array( 'title' => $args['label'] ); + if ( empty( $args['label'] ) ) { + return $args; + } + $schema = array( 'title' => $args['label'] ); if ( ! is_array( $args['show_in_rest'] ) ) { $args['show_in_rest'] = array( 'schema' => $schema, diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index c722a8ff02bb6a..ff9d70669f5797 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -412,12 +412,21 @@ async function loadSiteEntity() { meta: {}, }; + // @todo: Check why this isn't picking up preloaded data. const site = await apiFetch( { path: '/wp/v2/settings', method: 'OPTIONS', } ); - const labels = site?.schema?.properties ?? {}; + const labels = {}; + Object.entries( site?.schema?.properties ?? {} ).forEach( + ( [ key, value ] ) => { + // Ignore properties `title` and `type` keys. + if ( typeof value === 'object' && value.title ) { + labels[ key ] = value.title; + } + } + ); return [ { ...entity, meta: { labels } } ]; } @@ -458,17 +467,15 @@ function registerSyncConfigs( configs ) { /** * Loads the kind entities into the store. * - * @param {string} kind Kind - * @param {string} name Name + * @param {string} kind Kind + * @param {?string} name Name * @return {(thunkArgs: object) => Promise} Entities */ export const getOrLoadEntitiesConfig = ( kind, name ) => async ( { select, dispatch } ) => { let configs = select.getEntitiesConfig( kind ); - const hasConfig = !! configs?.find( - ( config ) => config.kind === kind && config.name === name - ); + const hasConfig = !! select.getEntityConfig( kind, name ); if ( configs?.length > 0 && hasConfig ) { if ( window.__experimentalEnableSync ) { diff --git a/packages/editor/src/components/entities-saved-states/hooks/use-is-dirty.js b/packages/editor/src/components/entities-saved-states/hooks/use-is-dirty.js index e630c60b8c633d..1103dcabe201ae 100644 --- a/packages/editor/src/components/entities-saved-states/hooks/use-is-dirty.js +++ b/packages/editor/src/components/entities-saved-states/hooks/use-is-dirty.js @@ -4,29 +4,24 @@ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { useMemo, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -const TRANSLATED_SITE_PROPERTIES = { - title: __( 'Title' ), - description: __( 'Tagline' ), - site_logo: __( 'Logo' ), - site_icon: __( 'Icon' ), - show_on_front: __( 'Show on front' ), - page_on_front: __( 'Page on front' ), - posts_per_page: __( 'Maximum posts per page' ), - default_comment_status: __( 'Allow comments on new posts' ), -}; export const useIsDirty = () => { - const { editedEntities, siteEdits } = useSelect( ( select ) => { - const { __experimentalGetDirtyEntityRecords, getEntityRecordEdits } = - select( coreStore ); + const { editedEntities, siteEdits, siteEntityConfig } = useSelect( + ( select ) => { + const { + __experimentalGetDirtyEntityRecords, + getEntityRecordEdits, + getEntityConfig, + } = select( coreStore ); - return { - editedEntities: __experimentalGetDirtyEntityRecords(), - siteEdits: getEntityRecordEdits( 'root', 'site' ), - }; - }, [] ); + return { + editedEntities: __experimentalGetDirtyEntityRecords(), + siteEdits: getEntityRecordEdits( 'root', 'site' ), + siteEntityConfig: getEntityConfig( 'root', 'site' ), + }; + }, + [] + ); const dirtyEntityRecords = useMemo( () => { // Remove site object and decouple into its edited pieces. @@ -34,18 +29,19 @@ export const useIsDirty = () => { ( record ) => ! ( record.kind === 'root' && record.name === 'site' ) ); + const siteEntityLabels = siteEntityConfig?.meta?.labels ?? {}; const editedSiteEntities = []; for ( const property in siteEdits ) { editedSiteEntities.push( { kind: 'root', name: 'site', - title: TRANSLATED_SITE_PROPERTIES[ property ] || property, + title: siteEntityLabels[ property ] || property, property, } ); } return [ ...editedEntitiesWithoutSite, ...editedSiteEntities ]; - }, [ editedEntities, siteEdits ] ); + }, [ editedEntities, siteEdits, siteEntityConfig ] ); // Unchecked entities to be ignored by save function. const [ unselectedEntities, _setUnselectedEntities ] = useState( [] ); From 3f2d0f193692765cb8e84bdcb3550e5d2a1cf9f0 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 27 Feb 2024 10:45:11 +0400 Subject: [PATCH 08/11] Fix unit test --- packages/core-data/src/test/entities.js | 8 +++++++- .../components/entities-saved-states/test/use-is-dirty.js | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index 798fa93a0723cf..27b3d56e76865d 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -52,11 +52,15 @@ describe( 'getKindEntities', () => { const dispatch = jest.fn(); const select = { getEntitiesConfig: jest.fn( () => entities ), + getEntityConfig: jest.fn( () => ( { + kind: 'postType', + name: 'post', + } ) ), }; const entities = [ { kind: 'postType' } ]; await getOrLoadEntitiesConfig( 'postType', - undefined + 'post' )( { dispatch, select } ); expect( dispatch ).not.toHaveBeenCalled(); } ); @@ -65,6 +69,7 @@ describe( 'getKindEntities', () => { const dispatch = jest.fn(); const select = { getEntitiesConfig: jest.fn( () => [] ), + getEntityConfig: jest.fn( () => undefined ), }; await getOrLoadEntitiesConfig( 'unknownKind', @@ -88,6 +93,7 @@ describe( 'getKindEntities', () => { const dispatch = jest.fn(); const select = { getEntitiesConfig: jest.fn( () => [] ), + getEntityConfig: jest.fn( () => undefined ), }; triggerFetch.mockImplementation( () => fetchedEntities ); diff --git a/packages/editor/src/components/entities-saved-states/test/use-is-dirty.js b/packages/editor/src/components/entities-saved-states/test/use-is-dirty.js index 04b6b4e566ef1f..287e990a14f92b 100644 --- a/packages/editor/src/components/entities-saved-states/test/use-is-dirty.js +++ b/packages/editor/src/components/entities-saved-states/test/use-is-dirty.js @@ -32,6 +32,9 @@ jest.mock( '@wordpress/data', () => { getEntityRecordEdits: jest.fn().mockReturnValue( { title: 'My Site', } ), + getEntityConfig: jest.fn().mockReturnValue( { + meta: { labels: { title: 'Title' } }, + } ), }; }; return fn( select ); From 3ad2c56f7cd36ee5818ab124ad808f40c75041fa Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 14 Mar 2024 17:20:42 +0400 Subject: [PATCH 09/11] Feedback --- lib/compat/wordpress-6.6/option.php | 3 ++- packages/core-data/src/entities.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php index 2d04b179fbea28..71907fa62dd09c 100644 --- a/lib/compat/wordpress-6.6/option.php +++ b/lib/compat/wordpress-6.6/option.php @@ -26,7 +26,8 @@ function gutenberg_update_initial_settings( $args, $defaults, $option_group, $op $args['label'] = $settings_label_map[ $option_name ]; } - if ( empty( $args['label'] ) ) { + // Don't update schema when label isn't provided. + if ( ! isset( $args['label'] ) ) { return $args; } diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index ff9d70669f5797..3aa217aa9e6a8e 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -412,9 +412,8 @@ async function loadSiteEntity() { meta: {}, }; - // @todo: Check why this isn't picking up preloaded data. const site = await apiFetch( { - path: '/wp/v2/settings', + path: entity.baseURL, method: 'OPTIONS', } ); From f95e7afe85b7774baab26d486ffc6368e8481b69 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 18 Mar 2024 17:23:47 +0400 Subject: [PATCH 10/11] Restore site entity shortcuts --- packages/core-data/src/entities.js | 7 ++++++- packages/core-data/src/index.js | 16 ++++++++++++---- .../edit-site/src/components/site-hub/index.js | 4 ++-- .../use-init-edited-entity-from-url.js | 4 ++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 3aa217aa9e6a8e..f0148da5ceebb4 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -223,7 +223,12 @@ export const rootEntitiesConfig = [ export const additionalEntityConfigLoaders = [ { kind: 'postType', loadEntities: loadPostTypeEntities }, { kind: 'taxonomy', loadEntities: loadTaxonomyEntities }, - { kind: 'root', loadEntities: loadSiteEntity }, + { + kind: 'root', + name: 'site', + plural: 'sites', + loadEntities: loadSiteEntity, + }, ]; /** diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 484622c6e40302..bd25fa8de9902b 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -12,7 +12,11 @@ import * as privateSelectors from './private-selectors'; import * as actions from './actions'; import * as resolvers from './resolvers'; import createLocksActions from './locks/actions'; -import { rootEntitiesConfig, getMethodName } from './entities'; +import { + rootEntitiesConfig, + additionalEntityConfigLoaders, + getMethodName, +} from './entities'; import { STORE_NAME } from './name'; import { unlock } from './private-apis'; @@ -20,8 +24,12 @@ import { unlock } from './private-apis'; // (getEntityRecord, getEntityRecords, updateEntityRecord, updateEntityRecords) // Instead of getEntityRecord, the consumer could use more user-friendly named selector: getPostType, getTaxonomy... // The "kind" and the "name" of the entity are combined to generate these shortcuts. +const entitiesConfig = [ + ...rootEntitiesConfig, + ...additionalEntityConfigLoaders.filter( ( config ) => !! config.name ), +]; -const entitySelectors = rootEntitiesConfig.reduce( ( result, entity ) => { +const entitySelectors = entitiesConfig.reduce( ( result, entity ) => { const { kind, name, plural } = entity; result[ getMethodName( kind, name ) ] = ( state, key, query ) => selectors.getEntityRecord( state, kind, name, key, query ); @@ -33,7 +41,7 @@ const entitySelectors = rootEntitiesConfig.reduce( ( result, entity ) => { return result; }, {} ); -const entityResolvers = rootEntitiesConfig.reduce( ( result, entity ) => { +const entityResolvers = entitiesConfig.reduce( ( result, entity ) => { const { kind, name, plural } = entity; result[ getMethodName( kind, name ) ] = ( key, query ) => resolvers.getEntityRecord( kind, name, key, query ); @@ -48,7 +56,7 @@ const entityResolvers = rootEntitiesConfig.reduce( ( result, entity ) => { return result; }, {} ); -const entityActions = rootEntitiesConfig.reduce( ( result, entity ) => { +const entityActions = entitiesConfig.reduce( ( result, entity ) => { const { kind, name } = entity; result[ getMethodName( kind, name, 'save' ) ] = ( record, options ) => actions.saveEntityRecord( kind, name, record, options ); diff --git a/packages/edit-site/src/components/site-hub/index.js b/packages/edit-site/src/components/site-hub/index.js index 4ebabae9a3b450..f79df2df367090 100644 --- a/packages/edit-site/src/components/site-hub/index.js +++ b/packages/edit-site/src/components/site-hub/index.js @@ -41,7 +41,7 @@ const SiteHub = memo( ( { isTransparent, className } ) => { ); const { - getEntityRecord, + getSite, getUnstableBase, // Site index. } = select( coreStore ); @@ -50,7 +50,7 @@ const SiteHub = memo( ( { isTransparent, className } ) => { dashboardLink: getSettings().__experimentalDashboardLink || 'index.php', homeUrl: getUnstableBase()?.home, - siteTitle: getEntityRecord( 'root', 'site' )?.title, + siteTitle: getSite()?.title, }; }, [] diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index fb142b985e11bf..442056abd0e8a2 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -30,9 +30,9 @@ const postTypesWithoutParentTemplate = [ function useResolveEditedEntityAndContext( { path, postId, postType } ) { const { hasLoadedAllDependencies, homepageId, url, frontPageTemplateId } = useSelect( ( select ) => { - const { getEntityRecord, getUnstableBase, getEntityRecords } = + const { getSite, getUnstableBase, getEntityRecords } = select( coreDataStore ); - const siteData = getEntityRecord( 'root', 'site' ); + const siteData = getSite(); const base = getUnstableBase(); const templates = getEntityRecords( 'postType', From fb77e6b607df36f2ea1d6fae863f5215e6768e33 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 18 Mar 2024 17:45:36 +0400 Subject: [PATCH 11/11] Update 'getOrLoadEntitiesConfig' doc-block --- packages/core-data/src/entities.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index f0148da5ceebb4..e91744110faf32 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -469,10 +469,12 @@ function registerSyncConfigs( configs ) { } /** - * Loads the kind entities into the store. + * Loads the entities into the store. * - * @param {string} kind Kind - * @param {?string} name Name + * Note: The `name` argument is used for `root` entities requiring additional server data. + * + * @param {string} kind Kind + * @param {string} name Name * @return {(thunkArgs: object) => Promise} Entities */ export const getOrLoadEntitiesConfig =