Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
21.43% covered (danger)
21.43%
9 / 42
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Capabilities_Bridge
22.50% covered (danger)
22.50%
9 / 40
50.00% covered (danger)
50.00%
1 / 2
56.55
0.00% covered (danger)
0.00%
0 / 1
 register_routes
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 get_capabilities
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2/**
3 * Capabilities REST bridge — proxies WPCOM's site rewind state.
4 *
5 * @package automattic/jetpack-backup-plugin
6 */
7
8namespace Automattic\Jetpack\Backup\V0005\REST;
9
10use Automattic\Jetpack\Connection\Client;
11use WP_Error;
12use WP_REST_Server;
13
14if ( ! defined( 'ABSPATH' ) ) {
15    exit( 0 );
16}
17
18/**
19 * Returns the site's backup capabilities (plan slug, hasBackupPlan,
20 * hasScan). Backs the `<Gates>` decision tree.
21 */
22class Capabilities_Bridge {
23
24    /**
25     * Register the GET /jetpack/v4/site/capabilities route.
26     *
27     * @return void
28     */
29    public static function register_routes() {
30        register_rest_route(
31            'jetpack/v4',
32            '/site/capabilities',
33            array(
34                'methods'             => WP_REST_Server::READABLE,
35                'callback'            => array( __CLASS__, 'get_capabilities' ),
36                'permission_callback' => array( Rest_Controller::class, 'permission_check' ),
37            )
38        );
39    }
40
41    /**
42     * Proxy `/sites/{id}/rewind/capabilities` (v2, as_user) and project
43     * the response into the shape the React layer expects.
44     *
45     * This is the same endpoint the legacy `/jetpack/v4/backup-capabilities`
46     * route hits — it returns a flat `{ capabilities: [...] }` envelope.
47     * The earlier `/rewind?force=wpcom` variant returns site *state*, not
48     * a capabilities list, and on some plan shapes (e.g. Jetpack Complete)
49     * the `capabilities` key is missing entirely, which produced a false
50     * "no plan" gate for plans that do include Backup.
51     *
52     * @return \WP_REST_Response|WP_Error The decoded capabilities, or WP_Error on failure.
53     */
54    public static function get_capabilities() {
55        $blog_id = Rest_Controller::get_blog_id_or_error();
56        if ( is_wp_error( $blog_id ) ) {
57            return $blog_id;
58        }
59
60        $response = Client::wpcom_json_api_request_as_user(
61            sprintf( '/sites/%d/rewind/capabilities', $blog_id ),
62            'v2',
63            array(),
64            null,
65            'wpcom'
66        );
67
68        if ( is_wp_error( $response ) ) {
69            return $response;
70        }
71
72        $status_code = wp_remote_retrieve_response_code( $response );
73        if ( 200 !== $status_code ) {
74            return new WP_Error(
75                'capabilities_fetch_failed',
76                __( 'Could not fetch site capabilities.', 'jetpack-backup-pkg' ),
77                array( 'status' => is_int( $status_code ) && $status_code > 0 ? $status_code : 500 )
78            );
79        }
80
81        $body = json_decode( wp_remote_retrieve_body( $response ), true );
82        if ( ! is_array( $body ) ) {
83            $body = array();
84        }
85
86        $capabilities = isset( $body['capabilities'] ) && is_array( $body['capabilities'] )
87            ? $body['capabilities']
88            : array();
89
90        return rest_ensure_response(
91            array(
92                'hasBackupPlan' => in_array( 'backup', $capabilities, true ),
93                'hasScan'       => in_array( 'scan', $capabilities, true ),
94            )
95        );
96    }
97}