PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 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 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-stats-admin/src/class-wpcom-client.php +163 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,163 @@
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 +}