PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src / class-wpcom-client.php

class-wpcom-client.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.1, at jetpack_vendor/automattic/jetpack-stats-admin/src/class-wpcom-client.php

164 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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
8 namespace Automattic\Jetpack\Stats_Admin;
9
10 use Automattic\Jetpack\Connection\Client;
11 use 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 */
18 class 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 ??= 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 // `Client` fails before sending anything when the site holds no blog token, and that
85 // error carries no status, which the REST API renders as a 500. Say what actually
86 // happened so callers can tell an unconnected site from a broken one. Newer connection
87 // packages name the reason (`no_possible_tokens`); older ones return `missing_token`.
88 // `malformed_token` means the stored token has no secret half and cannot sign anything.
89 if ( in_array( $response->get_error_code(), array( 'missing_token', 'no_possible_tokens', 'malformed_token' ), true ) ) {
90 return new WP_Error(
91 'site_not_connected',
92 __( 'This site is not connected to WordPress.com.', 'jetpack-stats-admin' ),
93 array( 'status' => 400 )
94 );
95 }
96
97 return $response;
98 }
99
100 $response_code = wp_remote_retrieve_response_code( $response );
101 $response_body_content = wp_remote_retrieve_body( $response );
102 $response_body = json_decode( $response_body_content, true );
103
104 $error = static::get_wp_error( $response_body, (int) $response_code );
105 if ( is_wp_error( $error ) ) {
106 // Unknown token keys and incorrect secrets also mean the site cannot authenticate.
107 // Expose these rejections like a missing token so callers can identify the broken connection.
108 if ( in_array( $error->get_error_code(), array( 'invalid_token', 'unknown_token', 'signature_mismatch' ), true ) ) {
109 return new WP_Error(
110 'site_not_connected',
111 __( 'This site is not connected to WordPress.com.', 'jetpack-stats-admin' ),
112 array( 'status' => 400 )
113 );
114 }
115 return $error;
116 }
117
118 return $response_body;
119 }
120
121 /**
122 * Build error object from remote response body and status code.
123 *
124 * @param array $response_body Remote response body.
125 * @param int $response_code Http response code.
126 * @return WP_Error
127 */
128 protected static function get_wp_error( $response_body, $response_code = 200 ) {
129 $error_code = null;
130 foreach ( array( 'code', 'error' ) as $error_code_key ) {
131 if ( isset( $response_body[ $error_code_key ] ) ) {
132 $error_code = $response_body[ $error_code_key ];
133 break;
134 }
135 }
136
137 // Sometimes the response code could be 200 but the response body still contains an error.
138 if ( $error_code !== null || $response_code !== 200 ) {
139 return new WP_Error(
140 $error_code,
141 $response_body['message'] ?? 'unknown remote error',
142 array( 'status' => $response_code )
143 );
144 }
145
146 // No error.
147 return null;
148 }
149
150 /**
151 * Check if the cache should be bypassed.
152 *
153 * @return bool
154 */
155 protected static function should_bypass_cache() {
156 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
157 return isset( $_GET['force_refresh'] ) || isset( $_GET['statsPurchaseSuccess'] ) ||
158 // phpcs:ignore WordPress.Arrays.ArrayKeySpacingRestrictions.SpacesAroundArrayKeys, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
159 ( isset( $_SERVER[ 'HTTP_REFERER' ] ) && false !== strpos( $_SERVER[ 'HTTP_REFERER' ], 'force_refresh' ) ) ||
160 // phpcs:ignore WordPress.Arrays.ArrayKeySpacingRestrictions.SpacesAroundArrayKeys, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
161 ( isset( $_SERVER[ 'HTTP_REFERER' ] ) && false !== strpos( $_SERVER[ 'HTTP_REFERER' ], 'statsPurchaseSuccess' ) );
162 }
163 }
164