Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
19 / 38
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Update_Cloud_CSS
50.00% covered (danger)
50.00%
19 / 38
0.00% covered (danger)
0.00%
0 / 5
64.12
0.00% covered (danger)
0.00%
0 / 1
 name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 request_methods
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 response
65.52% covered (warning)
65.52%
19 / 29
0.00% covered (danger)
0.00%
0 / 1
22.04
 flatten_url_errors
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 permissions
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Save generated cloud critical CSS.
4 *
5 * This endpoint is used by WP.com to push the generated CSS to the boost plugin.
6 */
7
8namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
9
10use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_State;
11use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_Storage;
12use Automattic\Jetpack_Boost\Lib\Critical_CSS\Display_Critical_CSS;
13use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
14use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
15use WP_REST_Server;
16
17/**
18 * Handler for POST cloud-css/update. Expects the following body params:
19 * - success: boolean - False if the whole Critical CSS job failed.
20 * - message: string containing an error message if success is false.
21 * - providers: Object containing one result for each provider_key:
22 *
23 * Each provider key contains:
24 * - success: boolean - False if this provider key failed.
25 * - data: Either a successful CSS block, or a CSS error.
26 *
27 * Each CSS block looks like:
28 * - css: string - containing CSS data, at most Display_Critical_CSS::MAX_CSS_BYTES.
29 * Oversized results delete any previous block and record PayloadTooLargeError;
30 * success:true acknowledges delivery, not acceptance of every CSS block.
31 *
32 * Each CSS error looks like:
33 * - urls: Object describing each URL which failed. Keys are URLs.
34 *
35 * Each URL failure looks like:
36 * - message: string - containing an error message.
37 * - type: string - machine readable error type.
38 * - meta: Object - JSON string compatible object containing extra metadata for consumption in the UI.
39 */
40class Update_Cloud_CSS implements Endpoint {
41
42    public function name() {
43        return 'cloud-css/update';
44    }
45
46    public function request_methods() {
47        return WP_REST_Server::EDITABLE;
48    }
49
50    public function response( $request ) {
51        $state          = new Critical_CSS_State();
52        $storage        = new Critical_CSS_Storage();
53        $params         = $request->get_params();
54        $providers      = empty( $params['providers'] ) || ! is_array( $params['providers'] ) ? array() : $params['providers'];
55        $api_successful = array( 'success' => true );
56
57        // If success is false, the whole Cloud CSS generation process failed.
58        if ( empty( $params['success'] ) ) {
59            if ( empty( $params['message'] ) || ! is_string( $params['message'] ) ) {
60                $error = __( 'An unknown error occurred', 'jetpack-boost' );
61            } else {
62                $error = $params['message'];
63            }
64
65            $state->set_error( $error );
66            $state->save();
67
68            return $api_successful;
69        }
70
71        // Update each provider.
72        foreach ( $providers as $provider_key => $result ) {
73            if ( ! isset( $result['data'] ) ) {
74                return new \WP_Error( 'invalid_data', __( 'Invalid request; missing data element', 'jetpack-boost' ) );
75            }
76
77            $data = $result['data'];
78
79            // Success
80            if ( ! empty( $result['success'] ) && ! empty( $data['css'] ) && is_string( $data['css'] ) ) {
81                if ( strlen( $data['css'] ) > Display_Critical_CSS::MAX_CSS_BYTES ) {
82                    $storage->delete_css( $provider_key );
83                    $state->set_provider_payload_too_large( $provider_key );
84                    continue;
85                }
86                $storage->store_css( $provider_key, $data['css'] );
87                $state->set_provider_success( $provider_key );
88                continue;
89            }
90
91            // Failures must have an array of urls.
92            if ( empty( $data['urls'] ) || ! is_array( $data['urls'] ) ) {
93                return new \WP_Error( 'invalid_data', __( 'Invalid request; missing urls element', 'jetpack-boost' ) );
94            }
95
96            $state->set_provider_errors( $provider_key, $this->flatten_url_errors( $data['urls'] ) );
97        }
98
99        // Save the state changes.
100        $state->save();
101
102        return $api_successful;
103    }
104
105    /**
106     * Errors arrive from Shield in an associative array with the URL as the key.
107     * This function flattens the array into a list of assoc arrays with the URL in each member.
108     */
109    private function flatten_url_errors( $errors ) {
110        $flat_errors = array();
111
112        foreach ( $errors as $url => $error ) {
113            $flat_errors[] = array_merge( array( 'url' => $url ), $error );
114        }
115
116        return $flat_errors;
117    }
118
119    public function permissions() {
120        return array(
121            new Signed_With_Blog_Token(),
122        );
123    }
124}