Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.18% covered (success)
93.18%
41 / 44
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_Client
93.18% covered (success)
93.18%
41 / 44
50.00% covered (danger)
50.00%
2 / 4
23.17
0.00% covered (danger)
0.00%
0 / 1
 request_as_blog_cached
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
8.05
 request_as_blog
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
3.02
 get_wp_error
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 should_bypass_cache
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2/**
3 * A class that wraps `Automattic\Jetpack\Connection\Client` and handles cache and errors.
4 *
5 * @package automattic/jetpack-stats-admin
6 */
7
8namespace Automattic\Jetpack\Stats_Admin;
9
10use Automattic\Jetpack\Connection\Client;
11use WP_Error;
12
13/**
14 * A class that wraps `Automattic\Jetpack\Connection\Client` and handles cache and errors.
15 *
16 * @package Automattic\Jetpack\Stats_Admin
17 */
18class WPCOM_Client {
19    /**
20     * Transient prefix for caching REST API responses.
21     *
22     * @var string
23     */
24    const CACHE_TRANSIENT_PREFIX = 'STATS_REST_RESP_';
25
26    /**
27     * Query the WordPress.com REST API using the blog token cached.
28     *
29     * @param String $path The API endpoint relative path.
30     * @param String $version The API version.
31     * @param array  $args Request arguments.
32     * @param String $body Request body.
33     * @param String $base_api_path (optional) the API base path override, defaults to 'rest'.
34     * @param bool   $use_cache (optional) default to true.
35     * @param string $cache_key (optional) default to null meaning the function auto generates cache key.
36     * @return array|WP_Error $response Data.
37     */
38    public static function request_as_blog_cached( $path, $version = '1.1', $args = array(), $body = null, $base_api_path = 'rest', $use_cache = true, $cache_key = null ) {
39        // Only allow caching GET requests.
40        $use_cache = $use_cache && ! ( isset( $args['method'] ) && strtoupper( $args['method'] ) !== 'GET' ) && ! static::should_bypass_cache();
41
42        // Arrays are serialized without considering the order of objects, but it's okay atm.
43        $cache_key = $cache_key !== null ? $cache_key : self::CACHE_TRANSIENT_PREFIX . md5( implode( '|', array( $path, $version, wp_json_encode( $args, JSON_UNESCAPED_SLASHES ), wp_json_encode( $body, JSON_UNESCAPED_SLASHES ), $base_api_path ) ) );
44
45        if ( $use_cache ) {
46            $response_body_content = get_transient( $cache_key );
47            if ( false !== $response_body_content ) {
48                return json_decode( $response_body_content, true );
49            }
50        }
51
52        $response_body = static::request_as_blog( $path, $version, $args, $body, $base_api_path );
53
54        if ( is_wp_error( $response_body ) ) {
55            return $response_body;
56        }
57
58        // Cache the response for 5 minutes.
59        set_transient( $cache_key, wp_json_encode( $response_body, JSON_UNESCAPED_SLASHES ), 5 * MINUTE_IN_SECONDS );
60
61        return $response_body;
62    }
63
64    /**
65     * Query the WordPress.com REST API using the blog token
66     *
67     * @param String $path The API endpoint relative path.
68     * @param String $version The API version.
69     * @param array  $args Request arguments.
70     * @param String $body Request body.
71     * @param String $base_api_path (optional) the API base path override, defaults to 'rest'.
72     * @return array|WP_Error $response Data.
73     */
74    public static function request_as_blog( $path, $version = '1.1', $args = array(), $body = null, $base_api_path = 'rest' ) {
75        $response = Client::wpcom_json_api_request_as_blog(
76            $path,
77            $version,
78            $args,
79            $body,
80            $base_api_path
81        );
82
83        if ( is_wp_error( $response ) ) {
84            return $response;
85        }
86
87        $response_code         = wp_remote_retrieve_response_code( $response );
88        $response_body_content = wp_remote_retrieve_body( $response );
89        $response_body         = json_decode( $response_body_content, true );
90
91        $error = static::get_wp_error( $response_body, (int) $response_code );
92        if ( is_wp_error( $error ) ) {
93            return $error;
94        }
95
96        return $response_body;
97    }
98
99    /**
100     * Build error object from remote response body and status code.
101     *
102     * @param array $response_body Remote response body.
103     * @param int   $response_code Http response code.
104     * @return WP_Error
105     */
106    protected static function get_wp_error( $response_body, $response_code = 200 ) {
107        $error_code = null;
108        foreach ( array( 'code', 'error' ) as $error_code_key ) {
109            if ( isset( $response_body[ $error_code_key ] ) ) {
110                $error_code = $response_body[ $error_code_key ];
111                break;
112            }
113        }
114
115        // Sometimes the response code could be 200 but the response body still contains an error.
116        if ( $error_code !== null || $response_code !== 200 ) {
117            return new WP_Error(
118                $error_code,
119                isset( $response_body['message'] ) ? $response_body['message'] : 'unknown remote error',
120                array( 'status' => $response_code )
121            );
122        }
123
124        // No error.
125        return null;
126    }
127
128    /**
129     * Check if the cache should be bypassed.
130     *
131     * @return bool
132     */
133    protected static function should_bypass_cache() {
134        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
135        return isset( $_GET['force_refresh'] ) || isset( $_GET['statsPurchaseSuccess'] ) ||
136            // phpcs:ignore WordPress.Arrays.ArrayKeySpacingRestrictions.SpacesAroundArrayKeys, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
137            ( isset( $_SERVER[ 'HTTP_REFERER' ] ) && false !== strpos( $_SERVER[ 'HTTP_REFERER' ], 'force_refresh' ) ) ||
138            // phpcs:ignore WordPress.Arrays.ArrayKeySpacingRestrictions.SpacesAroundArrayKeys, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
139            ( isset( $_SERVER[ 'HTTP_REFERER' ] ) && false !== strpos( $_SERVER[ 'HTTP_REFERER' ], 'statsPurchaseSuccess' ) );
140    }
141}