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 | sal/class.json-api-links.php +451 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,451 @@
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 +}