PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
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 14.1.1 14.2.2 All 502 releases
jetpack / sal / class.json-api-links.php

class.json-api-links.php in Jetpack – WP Security, Backup, Speed, & Growth 16.2, at sal/class.json-api-links.php

452 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * WPCOM_JSON_API_Links class.
4 *
5 * @package automattic/jetpack
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit( 0 );
10 }
11
12 require_once __DIR__ . '/../class.json-api.php';
13
14 /**
15 * Base class for WPCOM_JSON_API_Links.
16 */
17 class WPCOM_JSON_API_Links {
18
19 /**
20 * An instance of the WPCOM_JSON_API.
21 *
22 * @var WPCOM_JSON_API
23 */
24 private $api;
25
26 /**
27 * A WPCOM_JSON_API_Links instance.
28 *
29 * @var WPCOM_JSON_API_Links
30 */
31 private static $instance;
32
33 /**
34 * An array of the closest supported version of an endpoint to the current endpoint.
35 *
36 * @var array
37 */
38 private $closest_endpoint_cache_by_version = array();
39
40 /**
41 * An array including the current api endpoint as well as the max versions found if that endpoint doesn't exist.
42 *
43 * @var array
44 */
45 private $matches_by_version = array();
46
47 /**
48 * An array including the cached endpoint path versions.
49 *
50 * @var array
51 */
52 private $cache_result = null;
53
54 /**
55 * Creates a new instance of the WPCOM_JSON_API_Links class.
56 *
57 * @return WPCOM_JSON_API_Links
58 */
59 public static function getInstance() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
60 if ( null === self::$instance ) {
61 self::$instance = new self();
62 }
63
64 return self::$instance;
65 }
66
67 /**
68 * WPCOM_JSON_API_Links constructor.
69 *
70 * Method protected for singleton.
71 */
72 protected function __construct() {
73 $this->api = WPCOM_JSON_API::init();
74 }
75
76 /**
77 * An empty, private __clone method to prohibit cloning of this instance.
78 */
79 private function __clone() { }
80
81 /**
82 * Overriding PHP's default __wakeup method to prvent unserializing of the instance, and return an error message.
83 *
84 * @return never
85 */
86 public function __wakeup() {
87 die( "Please don't __wakeup WPCOM_JSON_API_Links" );
88 }
89
90 /**
91 * Generate a URL to an endpoint
92 *
93 * Used to construct meta links in API responses
94 *
95 * @param mixed ...$args Optional arguments to be appended to URL.
96 * @return string Endpoint URL
97 **/
98 public function get_link( ...$args ) {
99 $format = array_shift( $args );
100 $base = WPCOM_JSON_API__BASE;
101
102 $path = array_pop( $args );
103
104 if ( $path ) {
105 $path = '/' . ltrim( $path, '/' );
106 // tack the path onto the end of the format string.
107 // have to escape %'s in the path as %% because
108 // we're about to pass it through sprintf and we don't
109 // want it to see the % as a placeholder.
110 $format .= str_replace( '%', '%%', $path );
111 }
112
113 // Escape any % in args before using sprintf.
114 $escaped_args = array();
115 foreach ( $args as $arg_key => $arg_value ) {
116 $escaped_args[ $arg_key ] = str_replace( '%', '%%', $arg_value );
117 }
118
119 $relative_path = vsprintf( $format, $escaped_args );
120
121 if ( ! wp_startswith( $relative_path, '.' ) ) {
122 // Generic version. Match the requested version as best we can.
123 $api_version = $this->get_closest_version_of_endpoint( $format, $relative_path );
124 $base = substr( $base, 0, - 1 ) . $api_version;
125 }
126
127 // escape any % in the relative path before running it through sprintf again.
128 $relative_path = str_replace( '%', '%%', $relative_path );
129 // http, WPCOM_JSON_API__BASE, ... , path.
130 // %s , %s , $format, %s.
131 return esc_url_raw( sprintf( "https://%s$relative_path", $base ) );
132 }
133
134 /**
135 * Generate the /me prefixed endpoint URL
136 *
137 * Used to construct meta links in API responses, specific to WordPress.com user account pages.
138 *
139 * @param string $path Optional path to be appended to the URL.
140 * @return string /me endpoint URL
141 **/
142 public function get_me_link( $path = '' ) {
143 return $this->get_link( '/me', $path );
144 }
145
146 /**
147 * Generate the endpoint URL for taxonomies
148 *
149 * Used to construct meta links in API responses, specific to taxonomies.
150 *
151 * @param int $blog_id The site's Jetpack blog ID.
152 * @param int $taxonomy_id The taxonomy ID (for example of the category, tag).
153 * @param string $taxonomy_type The taxonomy type (for example category, tag).
154 * @param string $path Optional path to be appended to the URL.
155 * @return string Endpoint URL including taxonomy information.
156 **/
157 public function get_taxonomy_link( $blog_id, $taxonomy_id, $taxonomy_type, $path = '' ) {
158 switch ( $taxonomy_type ) {
159 case 'category':
160 return $this->get_link( '/sites/%d/categories/slug:%s', $blog_id, $taxonomy_id, $path );
161
162 case 'post_tag':
163 return $this->get_link( '/sites/%d/tags/slug:%s', $blog_id, $taxonomy_id, $path );
164
165 default:
166 return $this->get_link( '/sites/%d/taxonomies/%s/terms/slug:%s', $blog_id, $taxonomy_type, $taxonomy_id, $path );
167 }
168 }
169
170 /**
171 * Generate the endpoint URL for media links
172 *
173 * Used to construct meta links in API responses, specific to media links.
174 *
175 * @param int $blog_id The site's Jetpack blog ID.
176 * @param int $media_id The media item ID.
177 * @param string $path Optional path to be appended to the URL.
178 * @return string Endpoint URL including media information.
179 **/
180 public function get_media_link( $blog_id, $media_id, $path = '' ) {
181 return $this->get_link( '/sites/%d/media/%d', $blog_id, $media_id, $path );
182 }
183
184 /**
185 * Generate the site link endpoint URL
186 *
187 * Used to construct meta links in API responses, specific to /site links.
188 *
189 * @param int $blog_id The site's Jetpack blog ID.
190 * @param string $path Optional path to be appended to the URL.
191 * @return string Endpoint URL including site information.
192 **/
193 public function get_site_link( $blog_id, $path = '' ) {
194 return $this->get_link( '/sites/%d', $blog_id, $path );
195 }
196
197 /**
198 * Generate the posts endpoint URL
199 *
200 * Used to construct meta links in API responses, specific to posts links.
201 *
202 * @param int $blog_id The site's Jetpack blog ID.
203 * @param int $post_id The post ID.
204 * @param string $path Optional path to be appended to the URL.
205 * @return string Endpoint URL including post information.
206 **/
207 public function get_post_link( $blog_id, $post_id, $path = '' ) {
208 return $this->get_link( '/sites/%d/posts/%d', $blog_id, $post_id, $path );
209 }
210
211 /**
212 * Generate the comments endpoint URL
213 *
214 * Used to construct meta links in API responses, specific to comments links.
215 *
216 * @param int $blog_id The site's Jetpack blog ID.
217 * @param int $comment_id The comment ID.
218 * @param string $path Optional path to be appended to the URL.
219 * @return string Endpoint URL including comment information.
220 **/
221 public function get_comment_link( $blog_id, $comment_id, $path = '' ) {
222 return $this->get_link( '/sites/%d/comments/%d', $blog_id, $comment_id, $path );
223 }
224
225 /**
226 * Generate the endpoint URL for Publicize connections
227 *
228 * Used to construct meta links in API responses, specific to Publicize connections.
229 *
230 * @param int $blog_id The site's Jetpack blog ID.
231 * @param int $publicize_connection_id The ID of the Publicize connection.
232 * @param string $path Optional path to be appended to the URL.
233 * @return string Endpoint URL including Publicize connection information.
234 **/
235 public function get_publicize_connection_link( $blog_id, $publicize_connection_id, $path = '' ) {
236 return $this->get_link( '.1/sites/%d/publicize-connections/%d', $blog_id, $publicize_connection_id, $path );
237 }
238
239 /**
240 * Generate the endpoint URL for a single Publicize connection including a Keyring connection
241 *
242 * Used to construct meta links in API responses, specific to a single Publicize and Keyring connection.
243 *
244 * @param int $keyring_token_id The ID of the Keyring connection.
245 * @param string $path Optional path to be appended to the URL.
246 * @return string Endpoint URL including specific Keyring connection information for a specific Publicize connection.
247 **/
248 public function get_publicize_connections_link( $keyring_token_id, $path = '' ) {
249 return $this->get_link( '.1/me/publicize-connections/?keyring_connection_ID=%d', $keyring_token_id, $path );
250 }
251
252 /**
253 * Generate the endpoint URL for a single Keyring connection
254 *
255 * Used to construct meta links in API responses, specific to a Keyring connections.
256 *
257 * @param int $keyring_token_id The ID of the Keyring connection.
258 * @param string $path Optional path to be appended to the URL.
259 * @return string Endpoint URL including specific Keyring connection.
260 **/
261 public function get_keyring_connection_link( $keyring_token_id, $path = '' ) {
262 return $this->get_link( '.1/me/keyring-connections/%d', $keyring_token_id, $path );
263 }
264
265 /**
266 * Generate the endpoint URL for an external service that can be integrated with via Keyring
267 *
268 * Used to construct meta links in API responses, specific to an external service.
269 *
270 * @param int $external_service The ID of the external service.
271 * @param string $path Optional path to be appended to the URL.
272 * @return string Endpoint URL including information about an external service that WordPress.com or Jetpack sites can integrate with via keyring.
273 **/
274 public function get_external_service_link( $external_service, $path = '' ) {
275 return $this->get_link( '.1/meta/external-services/%s', $external_service, $path );
276 }
277
278 /**
279 * Try to find the closest supported version of an endpoint to the current endpoint
280 *
281 * For example, if we were looking at the path /animals/panda:
282 * - if the current endpoint is v1.3 and there is a v1.3 of /animals/%s available, we return 1.3
283 * - if the current endpoint is v1.3 and there is no v1.3 of /animals/%s known, we fall back to the
284 * maximum available version of /animals/%s, e.g. 1.1
285 *
286 * This method is used in get_link() to construct meta links for API responses.
287 *
288 * @param string $template_path The generic endpoint path, e.g. /sites/%s .
289 * @param string $path The current endpoint path, relative to the version, e.g. /sites/12345 .
290 * @param string $request_method Request method used to access the endpoint path .
291 * @return string The current version, or otherwise the maximum version available
292 */
293 public function get_closest_version_of_endpoint( $template_path, $path, $request_method = 'GET' ) {
294 $closest_endpoint_cache_by_version = & $this->closest_endpoint_cache_by_version;
295
296 $api_version = $this->api->version ?? '';
297 $closest_endpoint_cache = & $closest_endpoint_cache_by_version[ $api_version ];
298 if ( ! $closest_endpoint_cache ) {
299 $closest_endpoint_cache_by_version[ $api_version ] = array();
300 $closest_endpoint_cache = & $closest_endpoint_cache_by_version[ $api_version ];
301 }
302
303 if ( ! isset( $closest_endpoint_cache[ $template_path ] ) ) {
304 $closest_endpoint_cache[ $template_path ] = array();
305 } elseif ( isset( $closest_endpoint_cache[ $template_path ][ $request_method ] ) ) {
306 return $closest_endpoint_cache[ $template_path ][ $request_method ];
307 }
308
309 $path = untrailingslashit( $path );
310
311 // /help is a special case - always use the current request version
312 if ( wp_endswith( $path, '/help' ) ) {
313 $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version;
314 return $this->api->version;
315 }
316
317 $matches_by_version = & $this->matches_by_version;
318
319 // try to match out of saved matches.
320 if ( ! isset( $matches_by_version[ $api_version ] ) ) {
321 $matches_by_version[ $api_version ] = array();
322 }
323 foreach ( $matches_by_version[ $api_version ] as $match ) {
324 $regex = $match->regex;
325 if ( preg_match( "#^$regex\$#", $path ) ) {
326 $closest_endpoint_cache[ $template_path ][ $request_method ] = $match->version;
327 return $match->version;
328 }
329 }
330
331 $endpoint_path_versions = $this->get_endpoint_path_versions();
332 $last_path_segment = $this->get_last_segment_of_relative_path( $path );
333 $max_version_found = null;
334
335 foreach ( $endpoint_path_versions as $endpoint_last_path_segment => $endpoints ) {
336
337 // Does the last part of the path match the path key? (e.g. 'posts')
338 // If the last part contains a placeholder (e.g. %s), we want to carry on.
339 // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
340 if ( $last_path_segment != $endpoint_last_path_segment && ! strstr( $endpoint_last_path_segment, '%' ) ) {
341 continue;
342 }
343
344 foreach ( $endpoints as $endpoint ) {
345 // Does the request method match?
346 if ( ! in_array( $request_method, $endpoint['request_methods'], true ) ) {
347 continue;
348 }
349
350 $endpoint_path = untrailingslashit( (string) $endpoint['path'] );
351 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
352
353 if ( ! preg_match( "#^$endpoint_path_regex\$#", $path ) ) {
354 continue;
355 }
356
357 // Make sure the endpoint exists at the same version.
358 if ( null !== $this->api->version &&
359 version_compare( $this->api->version, $endpoint['min_version'], '>=' ) &&
360 version_compare( $this->api->version, $endpoint['max_version'], '<=' )
361 ) {
362 array_push(
363 $matches_by_version[ $this->api->version ],
364 (object) array(
365 'version' => $this->api->version,
366 'regex' => $endpoint_path_regex,
367 )
368 );
369 $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version;
370 return $this->api->version;
371 }
372
373 // If the endpoint doesn't exist at the same version, record the max version we found.
374 if ( empty( $max_version_found ) || version_compare( $max_version_found['version'], $endpoint['max_version'], '<' ) ) {
375 $max_version_found = array(
376 'version' => $endpoint['max_version'],
377 'regex' => $endpoint_path_regex,
378 );
379 }
380 }
381 }
382
383 // If the endpoint version is less than the requested endpoint version, return the max version found.
384 if ( ! empty( $max_version_found ) ) {
385 array_push(
386 $matches_by_version[ $api_version ],
387 (object) $max_version_found
388 );
389 $closest_endpoint_cache[ $template_path ][ $request_method ] = $max_version_found['version'];
390 return $max_version_found['version'];
391 }
392
393 // Otherwise, use the API version of the current request.
394 return $this->api->version;
395 }
396
397 /**
398 * Get an array of endpoint paths with their associated versions
399 *
400 * @return array Array of endpoint paths, min_versions and max_versions, keyed by last segment of path
401 **/
402 protected function get_endpoint_path_versions() {
403
404 if ( ! empty( $this->cache_result ) ) {
405 return $this->cache_result;
406 }
407
408 /*
409 * Create a map of endpoints and their min/max versions keyed by the last segment of the path (e.g. 'posts')
410 * This reduces the search space when finding endpoint matches in get_closest_version_of_endpoint()
411 */
412 $endpoint_path_versions = array();
413
414 foreach ( $this->api->endpoints as $key => $endpoint_objects ) {
415
416 // @todo As with the todo in class.json-api.php, we need to determine if anything depends on this being serialized and hence unserialized, rather than e.g. JSON.
417 // The key contains a serialized path, min_version and max_version.
418 list( $path, $min_version, $max_version ) = unserialize( $key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Legacy, see serialization at class.json-api.php.
419
420 // Grab the last component of the relative path to use as the top-level key.
421 $last_path_segment = $this->get_last_segment_of_relative_path( $path ) ?? '';
422
423 $endpoint_path_versions[ $last_path_segment ][] = array(
424 'path' => $path,
425 'min_version' => $min_version,
426 'max_version' => $max_version,
427 'request_methods' => array_keys( $endpoint_objects ),
428 );
429 }
430
431 $this->cache_result = $endpoint_path_versions;
432
433 return $endpoint_path_versions;
434 }
435
436 /**
437 * Grab the last segment of a relative path
438 *
439 * @param string $path Path.
440 * @return string Last path segment
441 */
442 protected function get_last_segment_of_relative_path( $path ) {
443 $path_parts = array_filter( explode( '/', $path ) );
444
445 if ( empty( $path_parts ) ) {
446 return null;
447 }
448
449 return end( $path_parts );
450 }
451 }
452