diff --git a/lib/class-wp-rest-block-editor-settings-controller.php b/lib/class-wp-rest-block-editor-settings-controller.php index 8207d1b6bacbc6..717e2e7e56270a 100644 --- a/lib/class-wp-rest-block-editor-settings-controller.php +++ b/lib/class-wp-rest-block-editor-settings-controller.php @@ -134,12 +134,6 @@ public function get_item_schema() { 'context' => array( 'post-editor', 'site-editor', 'widgets-editor', 'mobile' ), ), - '__experimentalGlobalStylesUserEntityId' => array( - 'description' => __( 'Global styles user entity ID.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'site-editor' ), - ), - '__experimentalGlobalStylesBaseConfig' => array( 'description' => __( 'Settings and styles consolidated from core and theme origins.', 'gutenberg' ), 'type' => 'object', diff --git a/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php b/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php new file mode 100644 index 00000000000000..7405af5e384946 --- /dev/null +++ b/lib/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php @@ -0,0 +1,341 @@ +namespace = 'wp/v2'; + $this->rest_base = 'global-styles'; + } + + /** + * Registers the controllers routes. + * + * @return void + */ + public function register_routes() { + // Lists/updates a single gloval style variation based on the given id. + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\/\w-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The id of a template', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Checks if the user has permissions to make the request. + * + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + protected function permissions_check() { + // Verify if the current user has edit_theme_options capability. + // This capability is required to edit/view/delete templates. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_cannot_manage_global_styles', + __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + + return true; + } + + /** + * Checks if a given request has access to read a single global styles config. + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + return $this->permissions_check( $request ); + } + + /** + * Returns the given global styles config. + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_item( $request ) { + $post = get_post( $request['id'] ); + if ( ! $post || 'wp_global_styles' !== $post->post_type ) { + return new WP_Error( 'rest_global_styles_not_found', __( 'No global styles config exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); + } + + return $this->prepare_item_for_response( $post, $request ); + } + + /** + * Checks if a given request has access to write a single global styles config. + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. + */ + public function update_item_permissions_check( $request ) { + return $this->permissions_check( $request ); + } + + /** + * Updates a single global style config. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function update_item( $request ) { + $post = get_post( $request['id'] ); + if ( ! $post || 'wp_global_styles' !== $post->post_type ) { + return new WP_Error( 'rest_global_styles_not_found', __( 'No global styles config exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); + } + + $changes = $this->prepare_item_for_database( $request ); + $result = wp_update_post( wp_slash( (array) $changes ), true ); + if ( is_wp_error( $result ) ) { + return $result; + } + + $post = get_post( $request['id'] ); + $fields_update = $this->update_additional_fields_for_object( $post, $request ); + if ( is_wp_error( $fields_update ) ) { + return $fields_update; + } + + return $this->prepare_item_for_response( + get_post( $request['id'] ), + $request + ); + } + + /** + * Prepares a single global styles config for update. + * + * @param WP_REST_Request $request Request object. + * @return stdClass Changes to pass to wp_update_post. + */ + protected function prepare_item_for_database( $request ) { + $changes = new stdClass(); + $changes->ID = $request['id']; + + $post = get_post( $request['id'] ); + $empty_config = array( + 'settings' => new stdClass(), + 'styles' => new stdClass(), + ); + if ( $post ) { + $existing_config = json_decode( $post->post_content, true ); + if ( ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) || + ! $existing_config['isGlobalStylesUserThemeJSON'] ) { + $existing_config = $empty_config; + } + } else { + $existing_config = $empty_config; + } + + if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) { + $config = array(); + if ( isset( $request['styles'] ) ) { + $config['styles'] = $request['styles']; + } else { + $config['styles'] = $existing_config['styles']; + } + if ( isset( $request['settings'] ) ) { + $config['settings'] = $request['settings']; + } else { + $config['settings'] = $existing_config['settings']; + } + $config['isGlobalStylesUserThemeJSON'] = true; + $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA; + $changes->post_content = wp_json_encode( $config ); + } + + // Post title. + if ( isset( $request['title'] ) ) { + if ( is_string( $request['title'] ) ) { + $changes->post_title = $request['title']; + } elseif ( ! empty( $request['title']['raw'] ) ) { + $changes->post_title = $request['title']['raw']; + } + } + + return $changes; + } + + /** + * Prepare a global styles config output for response. + * + * @param WP_Post $post Global Styles post object. + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response $data + */ + public function prepare_item_for_response( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $config = json_decode( $post->post_content, true ); + $is_global_styles_user_theme_json = isset( $config['isGlobalStylesUserThemeJSON'] ) && true === $config['isGlobalStylesUserThemeJSON']; + $result = array( + 'id' => $post->ID, + 'settings' => ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass(), + 'styles' => ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass(), + 'title' => array( + 'raw' => $post->post_title, + 'rendered' => get_the_title( $post ), + ), + ); + $result = $this->add_additional_fields_to_object( $result, $request ); + $response = rest_ensure_response( $result ); + $links = $this->prepare_links( $post->id ); + $response->add_links( $links ); + if ( ! empty( $links['self']['href'] ) ) { + $actions = $this->get_available_actions(); + $self = $links['self']['href']; + foreach ( $actions as $rel ) { + $response->add_link( $rel, $self ); + } + } + + return $response; + } + + + /** + * Prepares links for the request. + * + * @param integer $id ID. + * @return array Links for the given post. + */ + protected function prepare_links( $id ) { + $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); + + $links = array( + 'self' => array( + 'href' => rest_url( trailingslashit( $base ) . $id ), + ), + 'collection' => array( + 'href' => rest_url( $base ), + ), + ); + + return $links; + } + + /** + * Get the link relations available for the post and current user. + * + * @return array List of link relations. + */ + protected function get_available_actions() { + $rels = array(); + + $post_type = get_post_type_object( 'wp_global_styles' ); + if ( current_user_can( $post_type->cap->publish_posts ) ) { + $rels[] = 'https://api.w.org/action-publish'; + } + + return $rels; + } + + /** + * Retrieves the query params for the global styles collection. + * + * @return array Collection parameters. + */ + public function get_collection_params() { + return array(); + } + + /** + * Retrieves the global styles type' schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'wp_global_styles', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'ID of global styles config.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'styles' => array( + 'description' => __( 'Global styles.', 'gutenberg' ), + 'type' => array( 'object' ), + 'context' => array( 'view', 'edit' ), + ), + 'settings' => array( + 'description' => __( 'Global settings.', 'gutenberg' ), + 'type' => array( 'object' ), + 'context' => array( 'view', 'edit' ), + ), + 'title' => array( + 'description' => __( 'Title of the global styles variation.', 'gutenberg' ), + 'type' => array( 'object', 'string' ), + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'properties' => array( + 'raw' => array( + 'description' => __( 'Title for the global styles variation, as it exists in the database.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'rendered' => array( + 'description' => __( 'HTML title for the post, transformed for display.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + ), + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/lib/compat/wordpress-5.9/rest-active-global-styles.php b/lib/compat/wordpress-5.9/rest-active-global-styles.php new file mode 100644 index 00000000000000..b0aa325f8cf483 --- /dev/null +++ b/lib/compat/wordpress-5.9/rest-active-global-styles.php @@ -0,0 +1,47 @@ +get_stylesheet() === wp_get_theme()->get_stylesheet() ) { + // This creates a record for the current theme if not existant. + $id = WP_Theme_JSON_Resolver_Gutenberg::get_user_custom_post_type_id(); + } else { + + $wp_query_args = array( + 'post_status' => array( 'publish' ), + 'post_type' => 'wp_global_styles', + 'posts_per_page' => 1, + 'no_found_rows' => true, + 'tax_query' => array( + array( + 'taxonomy' => 'wp_theme', + 'field' => 'name', + 'terms' => $theme->get_stylesheet(), + ), + ), + ); + $global_styles_query = new WP_Query( $wp_query_args ); + $id = count( $global_styles_query->posts ) ? $global_styles_query->posts[0]->ID : null; + } + + if ( $id ) { + $response->add_link( + 'https://api.w.org/user-global-styles', + rest_url( 'wp/v2/global-styles/' . $id ) + ); + } + + return $response; +} + +add_filter( 'rest_prepare_theme', 'gutenberg_add_active_global_styles_link', 10, 2 ); diff --git a/lib/global-styles.php b/lib/global-styles.php index e33f79bb758b8a..acbd8c1123d9d2 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -122,10 +122,8 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } if ( 'site-editor' === $context && gutenberg_experimental_is_site_editor_available() ) { - $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings, 'theme' ); - $user_cpt_id = WP_Theme_JSON_Resolver_Gutenberg::get_user_custom_post_type_id(); + $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $settings, 'theme' ); - $settings['__experimentalGlobalStylesUserEntityId'] = $user_cpt_id; $settings['__experimentalGlobalStylesBaseConfig']['styles'] = $theme->get_raw_data()['styles']; $settings['__experimentalGlobalStylesBaseConfig']['settings'] = $theme->get_settings(); } diff --git a/lib/load.php b/lib/load.php index 14dac73e96e6f3..511ac6f1568f79 100644 --- a/lib/load.php +++ b/lib/load.php @@ -109,6 +109,8 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/full-site-editing/edit-site-page.php'; require __DIR__ . '/full-site-editing/edit-site-export.php'; require __DIR__ . '/compat/wordpress-5.9/default-theme-supports.php'; +require __DIR__ . '/compat/wordpress-5.9/class-gutenberg-rest-global-styles-controller.php'; +require __DIR__ . '/compat/wordpress-5.9/rest-active-global-styles.php'; require __DIR__ . '/blocks.php'; require __DIR__ . '/block-patterns.php'; diff --git a/lib/rest-api.php b/lib/rest-api.php index aa74c373f8b5b1..4829b6367711d8 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -224,3 +224,12 @@ function gutenberg_rest_user_collection_params_has_published_posts( $query_param return $query_params; } add_filter( 'rest_user_collection_params', 'gutenberg_rest_user_collection_params_has_published_posts' ); + +/** + * Registers the Global Styles REST API routes. + */ +function gutenberg_register_global_styles_endpoints() { + $editor_settings = new Gutenberg_REST_Global_Styles_Controller(); + $editor_settings->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' ); diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index ba70a25395a0f0..0b184f8fec4e7f 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -117,6 +117,22 @@ export function receiveCurrentTheme( currentTheme ) { }; } +/** + * Returns an action object used in signalling that the current global styles id has been received. + * + * @param {string} currentGlobalStylesId The current global styles id. + * + * @return {Object} Action object. + */ +export function __experimentalReceiveCurrentGlobalStylesId( + currentGlobalStylesId +) { + return { + type: 'RECEIVE_CURRENT_GLOBAL_STYLES_ID', + id: currentGlobalStylesId, + }; +} + /** * Returns an action object used in signalling that the index has been received. * diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 03fafbe099bcc4..4fd73ea2b86912 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -127,6 +127,15 @@ export const defaultEntities = [ label: __( 'Menu Location' ), key: 'name', }, + { + label: __( 'Global Styles' ), + name: 'globalStyles', + kind: 'root', + baseURL: '/wp/v2/global-styles', + baseURLParams: { context: 'edit' }, + plural: 'globalStylesVariations', // should be different than name + getTitle: ( record ) => record?.title?.rendered || record?.title, + }, ]; export const kinds = [ diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index f78e6fd76eff9d..8404b59a5a59cd 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -119,6 +119,23 @@ export function currentTheme( state = undefined, action ) { return state; } +/** + * Reducer managing the current global styles id. + * + * @param {string} state Current state. + * @param {Object} action Dispatched action. + * + * @return {string} Updated state. + */ +export function currentGlobalStylesId( state = undefined, action ) { + switch ( action.type ) { + case 'RECEIVE_CURRENT_GLOBAL_STYLES_ID': + return action.id; + } + + return state; +} + /** * Reducer managing installed themes. * @@ -570,6 +587,7 @@ export default combineReducers( { terms, users, currentTheme, + currentGlobalStylesId, currentUser, taxonomies, themes, diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 7ff03976a8b4b0..1b66ae40751765 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -418,3 +418,26 @@ __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { action.name === 'wp_template' ); }; + +export const __experimentalGetCurrentGlobalStylesId = () => async ( { + dispatch, +} ) => { + const activeThemes = await apiFetch( { + path: '/wp/v2/themes?status=active', + } ); + const globalStylesURL = get( activeThemes, [ + 0, + '_links', + 'wp:user-global-styles', + 0, + 'href', + ] ); + if ( globalStylesURL ) { + const globalStylesObject = await apiFetch( { + url: globalStylesURL, + } ); + dispatch.__experimentalReceiveCurrentGlobalStylesId( + globalStylesObject.id + ); + } +}; diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index c0c6de522a4fe7..46f3bf8043e899 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -687,6 +687,17 @@ export function getCurrentTheme( state ) { return state.themes[ state.currentTheme ]; } +/** + * Return the ID of the current global styles object. + * + * @param {Object} state Data state. + * + * @return {string} The current global styles ID. + */ +export function __experimentalGetCurrentGlobalStylesId( state ) { + return state.currentGlobalStylesId; +} + /** * Return theme supports data in the index. * diff --git a/packages/edit-site/src/components/global-styles/global-styles-provider.js b/packages/edit-site/src/components/global-styles/global-styles-provider.js index f83e0804167e61..79b911067bf570 100644 --- a/packages/edit-site/src/components/global-styles/global-styles-provider.js +++ b/packages/edit-site/src/components/global-styles/global-styles-provider.js @@ -1,7 +1,17 @@ /** * External dependencies */ -import { get, cloneDeep, set, mergeWith } from 'lodash'; +import { + get, + cloneDeep, + set, + mergeWith, + pickBy, + isEmpty, + isObject, + identity, + mapValues, +} from 'lodash'; /** * WordPress dependencies @@ -53,69 +63,63 @@ function removeUserOriginFromSettings( settingsToRemove ) { } ); return newSettings; } +const cleanEmptyObject = ( object ) => { + if ( ! isObject( object ) || Array.isArray( object ) ) { + return object; + } + const cleanedNestedObjects = pickBy( + mapValues( object, cleanEmptyObject ), + identity + ); + return isEmpty( cleanedNestedObjects ) ? undefined : cleanedNestedObjects; +}; function useGlobalStylesUserConfig() { - const { globalStylesId, content } = useSelect( ( select ) => { - const _globalStylesId = select( editSiteStore ).getSettings() - .__experimentalGlobalStylesUserEntityId; + const { globalStylesId, settings, styles } = useSelect( ( select ) => { + const _globalStylesId = select( + coreStore + ).__experimentalGetCurrentGlobalStylesId(); + const record = _globalStylesId + ? select( coreStore ).getEditedEntityRecord( + 'root', + 'globalStyles', + _globalStylesId + ) + : undefined; return { globalStylesId: _globalStylesId, - content: select( coreStore ).getEditedEntityRecord( - 'postType', - 'wp_global_styles', - _globalStylesId - )?.content, + settings: record?.settings, + styles: record?.styles, }; }, [] ); const { getEditedEntityRecord } = useSelect( coreStore ); const { editEntityRecord } = useDispatch( coreStore ); - const parseContent = ( contentToParse ) => { - let parsedConfig; - try { - parsedConfig = contentToParse ? JSON.parse( contentToParse ) : {}; - // It is very important to verify if the flag isGlobalStylesUserThemeJSON is true. - // If it is not true the content was not escaped and is not safe. - if ( ! parsedConfig.isGlobalStylesUserThemeJSON ) { - parsedConfig = {}; - } else { - parsedConfig = { - ...parsedConfig, - settings: addUserOriginToSettings( parsedConfig.settings ), - }; - } - } catch ( e ) { - /* eslint-disable no-console */ - console.error( 'Global Styles User data is not valid' ); - console.error( e ); - /* eslint-enable no-console */ - parsedConfig = {}; - } - - return parsedConfig; - }; - const config = useMemo( () => { - return parseContent( content ); - }, [ content ] ); + return { + settings: addUserOriginToSettings( settings ?? {} ), + styles: styles ?? {}, + }; + }, [ settings, styles ] ); const setConfig = useCallback( ( callback ) => { - const currentConfig = parseContent( - getEditedEntityRecord( - 'postType', - 'wp_global_styles', - globalStylesId - )?.content + const record = getEditedEntityRecord( + 'root', + 'globalStyles', + globalStylesId ); + const currentConfig = { + styles: record?.styles ?? {}, + settings: addUserOriginToSettings( record?.settings ?? {} ), + }; const updatedConfig = callback( currentConfig ); - editEntityRecord( 'postType', 'wp_global_styles', globalStylesId, { - content: JSON.stringify( { - ...updatedConfig, - settings: removeUserOriginFromSettings( - updatedConfig.settings - ), - } ), + editEntityRecord( 'root', 'globalStyles', globalStylesId, { + styles: cleanEmptyObject( updatedConfig.styles ) || {}, + settings: + cleanEmptyObject( + removeUserOriginFromSettings( updatedConfig.settings ) + ) || {}, } ); }, [ globalStylesId ] 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 03cd44e35f2af6..c9235140c01f13 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -95,7 +95,6 @@ function useBlockEditorSettings( settings, hasTemplate ) { '__experimentalBlockPatterns', '__experimentalFeatures', '__experimentalGlobalStylesBaseConfig', - '__experimentalGlobalStylesUserEntityId', '__experimentalPreferredStyleVariations', '__experimentalSetIsInserterOpened', '__unstableGalleryWithImageBlocks', diff --git a/phpunit/class-gutenberg-rest-global-styles-controller-test.php b/phpunit/class-gutenberg-rest-global-styles-controller-test.php new file mode 100644 index 00000000000000..e933bfbba98e07 --- /dev/null +++ b/phpunit/class-gutenberg-rest-global-styles-controller-test.php @@ -0,0 +1,119 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + // This creates the global styles for the current theme. + self::$global_styles_id = wp_insert_post( + array( + 'post_content' => '{"version": ' . WP_Theme_JSON_Gutenberg::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', + 'post_status' => 'publish', + 'post_title' => __( 'Custom Styles', 'default' ), + 'post_type' => 'wp_global_styles', + 'post_name' => 'wp-global-styles-tt1-blocks', + 'tax_input' => array( + 'wp_theme' => 'tt1-blocks', + ), + ), + true + ); + } + + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( '/wp/v2/global-styles/(?P[\/\w-]+)', $routes ); + } + + public function test_context_param() { + // TODO: Implement test_context_param() method. + $this->markTestIncomplete(); + } + + public function test_get_items() { + $this->markTestIncomplete(); + } + + public function test_get_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + unset( $data['_links'] ); + + $this->assertEquals( + array( + 'id' => self::$global_styles_id, + 'title' => array( + 'raw' => 'Custom Styles', + 'rendered' => 'Custom Styles', + ), + 'settings' => new stdClass(), + 'styles' => new stdClass(), + ), + $data + ); + } + + public function test_create_item() { + $this->markTestIncomplete(); + } + + public function test_update_item() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id ); + $request->set_body_params( + array( + 'title' => 'My new global styles title', + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 'My new global styles title', $data['title']['raw'] ); + } + + public function test_delete_item() { + $this->markTestIncomplete(); + } + + public function test_prepare_item() { + // TODO: Implement test_prepare_item() method. + $this->markTestIncomplete(); + } + + public function test_get_item_schema() { + // TODO: Implement test_get_item_schema() method. + $this->markTestIncomplete(); + } +}