Skip to content

Commit

Permalink
Navigation Area block (#36178)
Browse files Browse the repository at this point in the history
* try nav-area-block

* Resolve conflicts with trunk

* Rely on contexts also in PHP navigation block's render callback

* Lint and typo fixes

* Add a description for the navigation area block

* Remove navigation area and unused functions

* Update navigation area example

* Remove unused attribute

* Minor copy update

* Try passing the correct menu id into the template

* Only build template when menu id is known and add loading state

* Show navigation block placeholder when in an unassigned navigation area

* Support deleting menus in navigation areas

* Add location keyword

* Remove unused stylesheet

* Replace references to menu locations with navigation areas

* Remove links from the first version

* Add gutenberg_register_navigation_areas that enables overriding the default navigation areas.

* Rename "menu" property returned by the REST API to "navigation"

* Replace a call to get_item() in update_item() with an encapsulation (common get_navigation_area_object function)

* Remove @Validate comment and follow the menu-items endpoint in not validating the db object ID.

It would be great to add one at some point, but right now the way
gutenberg saves data is by issuing multiple concurrent http requests, so
there is a huge potential for race conditions.

* Remove @todos related to changing permissions checks. The checks are fine.

* Restore the // Experimental blocks. comment

* Preload navigation areas in the site editor

* Use templateLock: 'all' for navigation area inner blocks

* Add missing fixtures

* Add file doc comment

* Remove navigation menu bugfix so that it can be extracted into another PR

* Adjust fixtures

* Adjust fixtures

* Adjust fixturs

* Update lib/class-wp-rest-block-navigation-areas-controller.php

Co-authored-by: Anton Vlasenko <[email protected]>

* Remove div from save content

* Fix REST API registration variable nameing

* Revert "Update lib/class-wp-rest-block-navigation-areas-controller.php"

This reverts commit e31fdfb.

* Avoid trying to preload post with 0 id, which is default for a navigation area

Co-authored-by: Daniel Richards <[email protected]>
Co-authored-by: andrei draganescu <[email protected]>
Co-authored-by: Anton Vlasenko <[email protected]>
  • Loading branch information
4 people authored Nov 5, 2021
1 parent 5d933bc commit 5fc66ef
Show file tree
Hide file tree
Showing 22 changed files with 696 additions and 58 deletions.
2 changes: 2 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function gutenberg_reregister_core_block_types() {
'media-text',
'missing',
'more',
'navigation-area',
'navigation-link',
'navigation-submenu',
'nextpage',
Expand Down Expand Up @@ -69,6 +70,7 @@ function gutenberg_reregister_core_block_types() {
'latest-posts.php' => 'core/latest-posts',
'loginout.php' => 'core/loginout',
'navigation.php' => 'core/navigation',
'navigation-area.php' => 'core/navigation-area',
'navigation-link.php' => 'core/navigation-link',
'navigation-submenu.php' => 'core/navigation-submenu',
'page-list.php' => 'core/page-list',
Expand Down
284 changes: 284 additions & 0 deletions lib/class-wp-rest-block-navigation-areas-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
<?php
/**
* REST API: WP_REST_Block_Navigation_Areas_Controller class
*
* @subpackage REST_API
* @package WordPress
*/

/**
* Core class used to access block navigation areas via the REST API.
*
* @see WP_REST_Controller
*/
class WP_REST_Block_Navigation_Areas_Controller extends WP_REST_Controller {

/**
* Constructor.
*/
public function __construct() {
$this->namespace = '__experimental';
$this->rest_base = 'block-navigation-areas';
}

/**
* Registers the routes for the objects of the controller.
*
* @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_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);

register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<area>[\w-]+)',
array(
'args' => array(
'area' => array(
'description' => __( 'An alphanumeric identifier for the navigation area.', 'gutenberg' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
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 whether a given request has permission to read navigation areas.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_view',
__( 'Sorry, you are not allowed to view navigation areas.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}

/**
* Retrieves all navigation areas, depending on user context.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
$data = array();
foreach ( gutenberg_get_navigation_areas() as $name => $description ) {
$area = $this->get_navigation_area_object( $name );
$area = $this->prepare_item_for_response( $area, $request );
$data[ $name ] = $this->prepare_response_for_collection( $area );
}
return rest_ensure_response( $data );
}

/**
* Checks if a given request has access to read a navigation area.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_view',
__( 'Sorry, you are not allowed to view navigation areas.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}
if ( ! array_key_exists( $request['area'], gutenberg_get_navigation_areas() ) ) {
return new WP_Error( 'rest_navigation_area_invalid', __( 'Invalid navigation area.', 'gutenberg' ), array( 'status' => 404 ) );
}

return true;
}

/**
* Checks if a request has access to update the specified term.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return bool|WP_Error True if the request has access to update the item, false or WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
return $this->get_item_permissions_check( $request );
}

/**
* Retrieves a specific navigation area.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$name = $request['area'];
$area = $this->get_navigation_area_object( $name );
$data = $this->prepare_item_for_response( $area, $request );

return rest_ensure_response( $data );
}

/**
* Updates a specific navigation area.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function update_item( $request ) {
$name = $request['area'];

$mapping = get_option( 'fse_navigation_areas', array() );
$mapping[ $name ] = $request['navigation'];
update_option( 'fse_navigation_areas', $mapping );

$area = $this->get_navigation_area_object( $name );
$data = $this->prepare_item_for_response( $area, $request );
return rest_ensure_response( $data );
}

/**
* Converts navigation area name to a convenient object that this endpoint can reason about.
*
* @param string $name Navigation area name.
* @return stdClass An object representation of the navigation area.
*/
private function get_navigation_area_object( $name ) {
$available_areas = gutenberg_get_navigation_areas();
$mapping = get_option( 'fse_navigation_areas', array() );
$area = new stdClass();
$area->name = $name;
$area->navigation = ! empty( $mapping[ $name ] ) ? $mapping[ $name ] : null;
$area->description = $available_areas[ $name ];
return $area;
}

/**
* Prepares a navigation area object for serialization.
*
* @param stdClass $area Post status data.
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response Post status data.
*/
public function prepare_item_for_response( $area, $request ) {
$areas = gutenberg_get_navigation_areas();
$navigation = ( isset( $areas[ $area->name ] ) ) ? $area->navigation : 0;

$fields = $this->get_fields_for_response( $request );
$data = array();

if ( rest_is_field_included( 'name', $fields ) ) {
$data['name'] = $area->name;
}

if ( rest_is_field_included( 'description', $fields ) ) {
$data['description'] = $area->description;
}

if ( rest_is_field_included( 'navigation', $fields ) ) {
$data['navigation'] = (int) $navigation;
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );

$response = rest_ensure_response( $data );

/**
* Filters a navigation area returned from the REST API.
*
* Allows modification of the navigation area data right before it is
* returned.
*
* @param WP_REST_Response $response The response object.
* @param object $area The original status object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_navigation_area', $response, $area, $request );
}

/**
* Retrieves the navigation area's schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'navigation-area',
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'The name of the navigation area.', 'gutenberg' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'The description of the navigation area.', 'gutenberg' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'navigation' => array(
'description' => __( 'The ID of the assigned navigation.', 'gutenberg' ),
'type' => 'integer',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
),
);

return $this->add_additional_fields_schema( $schema );
}

/**
* Retrieves the query params for collections.
*
* @return array Collection parameters.
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}

}
35 changes: 19 additions & 16 deletions lib/full-site-editing/edit-site-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,22 +106,25 @@ function gutenberg_edit_site_init( $hook ) {
'edit_site_editor',
'edit-site',
array(
'preload_paths' => array(
array( '/wp/v2/media', 'OPTIONS' ),
'/',
'/wp/v2/types?context=edit',
'/wp/v2/taxonomies?context=edit',
'/wp/v2/pages?context=edit',
'/wp/v2/categories?context=edit',
'/wp/v2/posts?context=edit',
'/wp/v2/tags?context=edit',
'/wp/v2/templates?context=edit',
'/wp/v2/template-parts?context=edit',
'/wp/v2/settings',
'/wp/v2/themes?context=edit&status=active',
'/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit',
'/wp/v2/global-styles/' . $active_global_styles_id,
'/wp/v2/themes/' . $active_theme . '/global-styles',
'preload_paths' => array_merge(
gutenberg_get_navigation_areas_paths_to_preload(),
array(
array( '/wp/v2/media', 'OPTIONS' ),
'/',
'/wp/v2/types?context=edit',
'/wp/v2/taxonomies?context=edit',
'/wp/v2/pages?context=edit',
'/wp/v2/categories?context=edit',
'/wp/v2/posts?context=edit',
'/wp/v2/tags?context=edit',
'/wp/v2/templates?context=edit',
'/wp/v2/template-parts?context=edit',
'/wp/v2/settings',
'/wp/v2/themes?context=edit&status=active',
'/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit',
'/wp/v2/global-styles/' . $active_global_styles_id,
'/wp/v2/themes/' . $active_theme . '/global-styles',
)
),
'initializer_name' => 'initialize',
'editor_settings' => $settings,
Expand Down
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( ! class_exists( 'WP_REST_Menu_Items_Controller' ) ) {
require_once __DIR__ . '/class-wp-rest-menu-items-controller.php';
}
if ( ! class_exists( 'WP_REST_Block_Navigation_Areas_Controller' ) ) {
require_once __DIR__ . '/class-wp-rest-block-navigation-areas-controller.php';
}
if ( ! class_exists( 'WP_REST_Menu_Locations_Controller' ) ) {
require_once __DIR__ . '/class-wp-rest-menu-locations-controller.php';
}
Expand Down
Loading

0 comments on commit 5fc66ef

Please sign in to comment.