Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.80% covered (warning)
89.80%
44 / 49
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Schedule_Bridge
93.62% covered (success)
93.62%
44 / 47
50.00% covered (danger)
50.00%
1 / 2
9.02
0.00% covered (danger)
0.00%
0 / 1
 register_routes
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
 update_schedule
89.66% covered (warning)
89.66%
26 / 29
0.00% covered (danger)
0.00%
0 / 1
8.07
1<?php
2/**
3 * Schedule REST bridge — changes the hour WordPress.com backs the site up.
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_Request;
13use WP_REST_Server;
14
15if ( ! defined( 'ABSPATH' ) ) {
16    exit( 0 );
17}
18
19/**
20 * Writes the daily backup time. The read is the legacy `GET` on the same path.
21 */
22class Schedule_Bridge {
23
24    /**
25     * Register the POST /jetpack/v4/site/backup/schedule route.
26     *
27     * @return void
28     */
29    public static function register_routes() {
30        register_rest_route(
31            'jetpack/v4',
32            '/site/backup/schedule',
33            array(
34                'methods'             => WP_REST_Server::CREATABLE,
35                'callback'            => array( __CLASS__, 'update_schedule' ),
36                'permission_callback' => array( Rest_Controller::class, 'permission_check' ),
37                'args'                => array(
38                    'schedule_hour' => array(
39                        'description' => __( 'Hour of the day, in UTC, the daily backup starts.', 'jetpack-backup-pkg' ),
40                        'type'        => 'integer',
41                        'required'    => true,
42                        'minimum'     => 0,
43                        'maximum'     => 23,
44                    ),
45                ),
46            )
47        );
48    }
49
50    /**
51     * Proxy `POST /sites/{id}/rewind/scheduled` (v2). It refuses blog tokens, so this signs as the user.
52     *
53     * @param WP_REST_Request $request The request.
54     * @return \WP_REST_Response|WP_Error The new schedule, or WP_Error on failure.
55     */
56    public static function update_schedule( WP_REST_Request $request ) {
57        $blog_id = Rest_Controller::get_blog_id_or_error();
58        if ( is_wp_error( $blog_id ) ) {
59            return $blog_id;
60        }
61
62        $hour = (int) $request->get_param( 'schedule_hour' );
63
64        $response = Client::wpcom_json_api_request_as_user(
65            sprintf( '/sites/%d/rewind/scheduled', $blog_id ),
66            'v2',
67            array( 'method' => 'POST' ),
68            array( 'schedule_hour' => $hour ),
69            'wpcom'
70        );
71
72        if ( is_wp_error( $response ) ) {
73            return Rest_Controller::transport_error( $response, 'schedule_update_failed' );
74        }
75
76        $message = __( 'Could not change the backup time.', 'jetpack-backup-pkg' );
77
78        if ( 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
79            return Rest_Controller::upstream_error( $response, 'schedule_update_failed', $message );
80        }
81
82        $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
83
84        // WordPress.com's own success flag rides inside the 200.
85        if ( ! is_array( $decoded ) || empty( $decoded['ok'] ) ) {
86            $data   = array( 'status' => 500 );
87            $reason = Rest_Controller::upstream_reason( is_array( $decoded ) ? $decoded : array() );
88            if ( ! empty( $reason ) ) {
89                $data['wpcom'] = $reason;
90            }
91
92            return new WP_Error( 'schedule_update_failed', $message, $data );
93        }
94
95        return rest_ensure_response(
96            array(
97                'ok'             => true,
98                'scheduled_hour' => $hour,
99            )
100        );
101    }
102}