Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.64% covered (danger)
18.64%
11 / 59
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Endpoint_Admin_Bar
19.64% covered (danger)
19.64%
11 / 56
16.67% covered (danger)
16.67%
1 / 6
227.56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_routes
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 get_item_permissions_check
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 get_item
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
 get_nodes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 filter_nodes
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * REST API endpoint for admin bar.
4 *
5 * @package automattic/jetpack
6 */
7
8if ( ! defined( 'ABSPATH' ) ) {
9    exit( 0 );
10}
11
12/**
13 * Class WPCOM_REST_API_V2_Endpoint_Admin_Bar
14 */
15class WPCOM_REST_API_V2_Endpoint_Admin_Bar extends WP_REST_Controller {
16
17    /**
18     * Namespace prefix.
19     *
20     * @var string
21     */
22    public $namespace = 'wpcom/v2';
23
24    /**
25     * Endpoint base route.
26     *
27     * @var string
28     */
29    public $rest_base = 'admin-bar';
30
31    /**
32     * Top-level admin bar node IDs that are considered safe to show.
33     *
34     * @var string[]
35     */
36    const ALLOWED_TOP_LEVEL_NODES = array( 'wp-logo', 'site-name', 'updates', 'command-palette', 'comments', 'new-content', 'my-account', 'agents-manager', 'agents-manager-ai-chat' );
37
38    /**
39     * WPCOM_REST_API_V2_Endpoint_Admin_Bar constructor.
40     */
41    public function __construct() {
42        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
43    }
44
45    /**
46     * Register routes.
47     */
48    public function register_routes() {
49        register_rest_route(
50            $this->namespace,
51            $this->rest_base . '/',
52            array(
53                array(
54                    'methods'             => WP_REST_Server::READABLE,
55                    'callback'            => array( $this, 'get_item' ),
56                    'permission_callback' => array( $this, 'get_item_permissions_check' ),
57                ),
58            )
59        );
60    }
61
62    /**
63     * Checks if a given request has access to the admin bar.
64     *
65     * @param WP_REST_Request $request Full details about the request.
66     * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
67     */
68    public function get_item_permissions_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
69        if ( ! current_user_can( 'manage_options' ) ) {
70            return new WP_Error(
71                'rest_forbidden',
72                __( 'Sorry, you are not allowed to view the admin bar on this site.', 'jetpack' ),
73                array( 'status' => rest_authorization_required_code() )
74            );
75        }
76
77        return true;
78    }
79
80    /**
81     * Retrieves the admin bar registered for the current site, filtered to
82     * the allowed top-level nodes and their descendants.
83     *
84     * @param WP_REST_Request $request Full details about the request.
85     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
86     */
87    public function get_item( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
88        if ( ! class_exists( 'WP_Screen' ) ) {
89            require_once ABSPATH . 'wp-admin/includes/class-wp-screen.php';
90        }
91
92        if ( ! function_exists( 'set_current_screen' ) ) {
93            require_once ABSPATH . 'wp-admin/includes/screen.php';
94        }
95
96        $switched_locale = false;
97        if ( 'user' === $request->get_param( '_locale' ) ) {
98            $user_locale = get_user_locale();
99            if ( $user_locale ) {
100                $switched_locale = switch_to_locale( $user_locale );
101            }
102        }
103
104        // Simulate a wp-admin context.
105        set_current_screen( 'dashboard' );
106
107        // Core only adds the command palette node when its assets are enqueued,
108        // which normally happens on admin_enqueue_scripts.
109        if ( function_exists( 'wp_enqueue_command_palette_assets' ) ) {
110            wp_enqueue_command_palette_assets();
111        }
112
113        $nodes          = $this->get_nodes();
114        $filtered_nodes = $this->filter_nodes( $nodes, self::ALLOWED_TOP_LEVEL_NODES );
115
116        $response = rest_ensure_response( array( 'nodes' => array_values( $filtered_nodes ) ) );
117
118        if ( $switched_locale ) {
119            restore_previous_locale();
120        }
121
122        return $response;
123    }
124
125    /**
126     * Builds the admin bar for the current request and returns its nodes.
127     *
128     * @return array Admin bar nodes.
129     */
130    private function get_nodes() {
131        global $wp_admin_bar;
132
133        add_filter( 'show_admin_bar', '__return_true', 999 );
134        if ( ! _wp_admin_bar_init() || ! $wp_admin_bar instanceof WP_Admin_Bar ) {
135            return array();
136        }
137
138        ob_start();
139        do_action_ref_array( 'admin_bar_menu', array( &$wp_admin_bar ) );
140        ob_end_clean();
141
142        return $wp_admin_bar->get_nodes() ?? array();
143    }
144
145    /**
146     * Filters admin bar nodes to only include allowed top-level items and
147     * their descendants.
148     *
149     * @param array $nodes       All admin bar nodes keyed by ID.
150     * @param array $allowed_ids Top-level node IDs to keep.
151     * @return array Filtered nodes.
152     */
153    private function filter_nodes( array $nodes, array $allowed_ids ) {
154        $allowed = array();
155
156        foreach ( $nodes as $id => $node ) {
157            if ( in_array( $id, $allowed_ids, true ) ) {
158                $allowed[ $id ] = $node;
159                continue;
160            }
161
162            $current = $node;
163            while ( ! empty( $current->parent ) && isset( $nodes[ $current->parent ] ) ) {
164                if ( in_array( $current->parent, $allowed_ids, true ) ) {
165                    $allowed[ $id ] = $node;
166                    break;
167                }
168                $current = $nodes[ $current->parent ];
169            }
170        }
171
172        return $allowed;
173    }
174}
175
176wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Admin_Bar' );