<?php
/**
* Dashboard Layout: server-side defaults.
*
* Allows plugins and themes to register a default dashboard layout
* that surfaces transparently through the `@wordpress/preferences`
* store for users who have not customized theirs.
*
* @package gutenberg
*/
/**
* Preferences scope under which the dashboard layout is stored.
* Mirrors the scope read by the JS surface.
*/
const GUTENBERG_DASHBOARD_LAYOUT_SCOPE = 'core/dashboard';
/**
* Preferences key under `GUTENBERG_DASHBOARD_LAYOUT_SCOPE` that holds
* the layout array.
*/
const GUTENBERG_DASHBOARD_LAYOUT_KEY = 'dashboardLayout';
/**
* Identifier of the bundled dashboard surface, formatted as
* `<plugin>_<page>` to match the underscore form produced by the
* wp-build pipeline (mirrors the `{{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}`
* pair used in generated page templates).
*
* Passed as context to `gutenberg_dashboard_default_layout` and used
* as the `{name}` segment of the REST default-layout route.
*/
const GUTENBERG_DASHBOARD_NAME = 'gutenberg_dashboard';
/**
* Injects a registered default dashboard layout into the user's
* `persisted_preferences` read when the stored layout is empty.
*
* Hooks into `get_user_metadata` so the default propagates through
* the same persistence layer the dashboard's JS surface reads from.
* The JS side stays oblivious: a default and a user-saved layout
* look identical at the preferences-store boundary.
*
* @param mixed $value The pre-fetched value, or null to let the
* meta API resolve normally.
* @param int $user_id User ID.
* @param string $meta_key Meta key being read.
* @return mixed The original value, or a single-element array
* containing the extended persisted preferences.
*/
function gutenberg_inject_dashboard_default_layout( $value, $user_id, $meta_key ) {
global $wpdb;
$expected_key = $wpdb->get_blog_prefix() . 'persisted_preferences';
if ( $meta_key !== $expected_key ) {
return $value;
}
// Avoid recursion when reading the user meta.
remove_filter( 'get_user_metadata', __FUNCTION__, 99 );
$base = get_user_meta( $user_id, $meta_key, true );
add_filter( 'get_user_metadata', __FUNCTION__, 99, 3 );
if ( ! is_array( $base ) ) {
$base = array();
}
$committed = isset( $base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ][ GUTENBERG_DASHBOARD_LAYOUT_KEY ] )
? $base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ][ GUTENBERG_DASHBOARD_LAYOUT_KEY ]
: array();
if ( ! empty( $committed ) ) {
return $value;
}
/**
* Filters the default dashboard layout served to users who have
* not customized theirs.
*
* Each entry should match the dashboard's widget instance shape:
* `uuid`, `type`, optional `attributes`, optional `placement`.
*
* @param array $default_layout Default array of widget instances.
* @param string $dashboard_name Identifier of the dashboard surface
* receiving the default. Callbacks
* targeting a specific dashboard
* should switch on this value.
*/
$default = apply_filters( 'gutenberg_dashboard_default_layout', array(), GUTENBERG_DASHBOARD_NAME );
if ( empty( $default ) || ! is_array( $default ) ) {
return $value;
}
if ( ! isset( $base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ] ) || ! is_array( $base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ] ) ) {
$base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ] = array();
}
$base[ GUTENBERG_DASHBOARD_LAYOUT_SCOPE ][ GUTENBERG_DASHBOARD_LAYOUT_KEY ] = $default;
return array( $base );
}
add_filter( 'get_user_metadata', 'gutenberg_inject_dashboard_default_layout', 99, 3 );
/**
* Returns the default layout registered for a dashboard surface.
*
* Resolves `gutenberg_dashboard_default_layout` for the supplied
* dashboard name, returning a fresh evaluation of the filter chain
* each call. Used by the JS surface to back a "reset to default"
* action without depending on the user-meta hydration path.
*
* @param WP_REST_Request $request REST request carrying the
* dashboard name segment.
* @return WP_REST_Response Response wrapping the default layout
* array.
*/
function gutenberg_get_dashboard_default_layout( $request ) {
$name = $request['name'];
$default = apply_filters( 'gutenberg_dashboard_default_layout', array(), $name );
if ( ! is_array( $default ) ) {
$default = array();
}
return rest_ensure_response( array_values( $default ) );
}
/**
* Registers the REST route that exposes per-dashboard default layouts.
*/
function gutenberg_register_dashboard_default_layout_route() {
register_rest_route(
'wp/v2',
'/dashboards/(?P<name>[a-z][a-z0-9]*(?:_[a-z0-9]+)+)/default-layout',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'gutenberg_get_dashboard_default_layout',
'permission_callback' => function () {
return current_user_can( 'read' );
},
'args' => array(
'name' => array(
'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'gutenberg' ),
'type' => 'string',
),
),
)
);
}
add_action( 'rest_api_init', 'gutenberg_register_dashboard_default_layout_route' );