PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 8.2.6
Jetpack – WP Security, Backup, Speed, & Growth v8.2.6
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 14.3.1 All 501 releases
jetpack / sal / class.json-api-links.php
class.json-api-links.php
283 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once dirname( __FILE__ ) . '/../class.json-api.php';
4
5 class WPCOM_JSON_API_Links {
6 private $api;
7 private static $instance;
8 private $closest_endpoint_cache_by_version = array();
9 private $matches_by_version = array();
10 private $cache_result = null;
11
12 public static function getInstance() {
13 if ( null === self::$instance ) {
14 self::$instance = new self();
15 }
16
17 return self::$instance;
18 }
19
20 // protect these methods for singleton
21 protected function __construct() {
22 $this->api = WPCOM_JSON_API::init();
23 }
24 private function __clone() { }
25 private function __wakeup() { }
26
27 /**
28 * Generate a URL to an endpoint
29 *
30 * Used to construct meta links in API responses
31 *
32 * @param mixed ...$args Optional arguments to be appended to URL
33 * @return string Endpoint URL
34 **/
35 function get_link( ...$args ) {
36 $format = array_shift( $args );
37 $base = WPCOM_JSON_API__BASE;
38
39 $path = array_pop( $args );
40
41 if ( $path ) {
42 $path = '/' . ltrim( $path, '/' );
43 // tack the path onto the end of the format string
44 // have to escape %'s in the path as %% because
45 // we're about to pass it through sprintf and we don't
46 // want it to see the % as a placeholder
47 $format .= str_replace( '%', '%%', $path );
48 }
49
50 // Escape any % in args before using sprintf
51 $escaped_args = array();
52 foreach ( $args as $arg_key => $arg_value ) {
53 $escaped_args[ $arg_key ] = str_replace( '%', '%%', $arg_value );
54 }
55
56 $relative_path = vsprintf( $format, $escaped_args );
57
58 if ( ! wp_startswith( $relative_path, '.' ) ) {
59 // Generic version. Match the requested version as best we can
60 $api_version = $this->get_closest_version_of_endpoint( $format, $relative_path );
61 $base = substr( $base, 0, - 1 ) . $api_version;
62 }
63
64 // escape any % in the relative path before running it through sprintf again
65 $relative_path = str_replace( '%', '%%', $relative_path );
66 // http, WPCOM_JSON_API__BASE, ... , path
67 // %s , %s , $format, %s
68 return esc_url_raw( sprintf( "https://%s$relative_path", $base ) );
69 }
70
71 function get_me_link( $path = '' ) {
72 return $this->get_link( '/me', $path );
73 }
74
75 function get_taxonomy_link( $blog_id, $taxonomy_id, $taxonomy_type, $path = '' ) {
76 switch ( $taxonomy_type ) {
77 case 'category':
78 return $this->get_link( '/sites/%d/categories/slug:%s', $blog_id, $taxonomy_id, $path );
79
80 case 'post_tag':
81 return $this->get_link( '/sites/%d/tags/slug:%s', $blog_id, $taxonomy_id, $path );
82
83 default:
84 return $this->get_link( '/sites/%d/taxonomies/%s/terms/slug:%s', $blog_id, $taxonomy_type, $taxonomy_id, $path );
85 }
86 }
87
88 function get_media_link( $blog_id, $media_id, $path = '' ) {
89 return $this->get_link( '/sites/%d/media/%d', $blog_id, $media_id, $path );
90 }
91
92 function get_site_link( $blog_id, $path = '' ) {
93 return $this->get_link( '/sites/%d', $blog_id, $path );
94 }
95
96 function get_post_link( $blog_id, $post_id, $path = '' ) {
97 return $this->get_link( '/sites/%d/posts/%d', $blog_id, $post_id, $path );
98 }
99
100 function get_comment_link( $blog_id, $comment_id, $path = '' ) {
101 return $this->get_link( '/sites/%d/comments/%d', $blog_id, $comment_id, $path );
102 }
103
104 function get_publicize_connection_link( $blog_id, $publicize_connection_id, $path = '' ) {
105 return $this->get_link( '.1/sites/%d/publicize-connections/%d', $blog_id, $publicize_connection_id, $path );
106 }
107
108 function get_publicize_connections_link( $keyring_token_id, $path = '' ) {
109 return $this->get_link( '.1/me/publicize-connections/?keyring_connection_ID=%d', $keyring_token_id, $path );
110 }
111
112 function get_keyring_connection_link( $keyring_token_id, $path = '' ) {
113 return $this->get_link( '.1/me/keyring-connections/%d', $keyring_token_id, $path );
114 }
115
116 function get_external_service_link( $external_service, $path = '' ) {
117 return $this->get_link( '.1/meta/external-services/%s', $external_service, $path );
118 }
119
120 /**
121 * Try to find the closest supported version of an endpoint to the current endpoint
122 *
123 * For example, if we were looking at the path /animals/panda:
124 * - if the current endpoint is v1.3 and there is a v1.3 of /animals/%s available, we return 1.3
125 * - if the current endpoint is v1.3 and there is no v1.3 of /animals/%s known, we fall back to the
126 * maximum available version of /animals/%s, e.g. 1.1
127 *
128 * This method is used in get_link() to construct meta links for API responses.
129 *
130 * @param $template_path string The generic endpoint path, e.g. /sites/%s
131 * @param $path string The current endpoint path, relative to the version, e.g. /sites/12345
132 * @param $request_method string Request method used to access the endpoint path
133 * @return string The current version, or otherwise the maximum version available
134 */
135 function get_closest_version_of_endpoint( $template_path, $path, $request_method = 'GET' ) {
136 $closest_endpoint_cache_by_version = & $this->closest_endpoint_cache_by_version;
137
138 $closest_endpoint_cache = & $closest_endpoint_cache_by_version[ $this->api->version ];
139 if ( !$closest_endpoint_cache ) {
140 $closest_endpoint_cache_by_version[ $this->api->version ] = array();
141 $closest_endpoint_cache = & $closest_endpoint_cache_by_version[ $this->api->version ];
142 }
143
144 if ( ! isset( $closest_endpoint_cache[ $template_path ] ) ) {
145 $closest_endpoint_cache[ $template_path ] = array();
146 } elseif ( isset( $closest_endpoint_cache[ $template_path ][ $request_method ] ) ) {
147 return $closest_endpoint_cache[ $template_path ][ $request_method ];
148 }
149
150 $path = untrailingslashit( $path );
151
152 // /help is a special case - always use the current request version
153 if ( wp_endswith( $path, '/help' ) ) {
154 $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version;
155 return $this->api->version;
156 }
157
158 $matches_by_version = & $this->matches_by_version;
159
160 // try to match out of saved matches
161 if ( ! isset( $matches_by_version[ $this->api->version ] ) ) {
162 $matches_by_version[ $this->api->version ] = array();
163 }
164 foreach ( $matches_by_version[ $this->api->version ] as $match ) {
165 $regex = $match->regex;
166 if ( preg_match( "#^$regex\$#", $path ) ) {
167 $closest_endpoint_cache[ $template_path ][ $request_method ] = $match->version;
168 return $match->version;
169 }
170 }
171
172 $endpoint_path_versions = $this->get_endpoint_path_versions();
173 $last_path_segment = $this->get_last_segment_of_relative_path( $path );
174 $max_version_found = null;
175
176 foreach ( $endpoint_path_versions as $endpoint_last_path_segment => $endpoints ) {
177
178 // Does the last part of the path match the path key? (e.g. 'posts')
179 // If the last part contains a placeholder (e.g. %s), we want to carry on
180 if ( $last_path_segment != $endpoint_last_path_segment && ! strstr( $endpoint_last_path_segment, '%' ) ) {
181 continue;
182 }
183
184 foreach ( $endpoints as $endpoint ) {
185 // Does the request method match?
186 if ( ! in_array( $request_method, $endpoint['request_methods'] ) ) {
187 continue;
188 }
189
190 $endpoint_path = untrailingslashit( $endpoint['path'] );
191 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
192
193 if ( ! preg_match( "#^$endpoint_path_regex\$#", $path ) ) {
194 continue;
195 }
196
197 // Make sure the endpoint exists at the same version
198 if ( version_compare( $this->api->version, $endpoint['min_version'], '>=') &&
199 version_compare( $this->api->version, $endpoint['max_version'], '<=') ) {
200 array_push(
201 $matches_by_version[ $this->api->version ],
202 (object) array( 'version' => $this->api->version, 'regex' => $endpoint_path_regex )
203 );
204 $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version;
205 return $this->api->version;
206 }
207
208 // If the endpoint doesn't exist at the same version, record the max version we found
209 if ( empty( $max_version_found ) || version_compare( $max_version_found['version'], $endpoint['max_version'], '<' ) ) {
210 $max_version_found = array( 'version' => $endpoint['max_version'], 'regex' => $endpoint_path_regex );
211 }
212 }
213 }
214
215 // If the endpoint version is less than the requested endpoint version, return the max version found
216 if ( ! empty( $max_version_found ) ) {
217 array_push(
218 $matches_by_version[ $this->api->version ],
219 (object) $max_version_found
220 );
221 $closest_endpoint_cache[ $template_path ][ $request_method ] = $max_version_found['version'];
222 return $max_version_found['version'];
223 }
224
225 // Otherwise, use the API version of the current request
226 return $this->api->version;
227 }
228
229 /**
230 * Get an array of endpoint paths with their associated versions
231 *
232 * @return array Array of endpoint paths, min_versions and max_versions, keyed by last segment of path
233 **/
234 protected function get_endpoint_path_versions() {
235
236 if ( ! empty ( $this->cache_result ) ) {
237 return $this->cache_result;
238 }
239
240 /*
241 * Create a map of endpoints and their min/max versions keyed by the last segment of the path (e.g. 'posts')
242 * This reduces the search space when finding endpoint matches in get_closest_version_of_endpoint()
243 */
244 $endpoint_path_versions = array();
245
246 foreach ( $this->api->endpoints as $key => $endpoint_objects ) {
247
248 // The key contains a serialized path, min_version and max_version
249 list( $path, $min_version, $max_version ) = unserialize( $key );
250
251 // Grab the last component of the relative path to use as the top-level key
252 $last_path_segment = $this->get_last_segment_of_relative_path( $path );
253
254 $endpoint_path_versions[ $last_path_segment ][] = array(
255 'path' => $path,
256 'min_version' => $min_version,
257 'max_version' => $max_version,
258 'request_methods' => array_keys( $endpoint_objects )
259 );
260 }
261
262 $this->cache_result = $endpoint_path_versions;
263
264 return $endpoint_path_versions;
265 }
266
267 /**
268 * Grab the last segment of a relative path
269 *
270 * @param string $path Path
271 * @return string Last path segment
272 */
273 protected function get_last_segment_of_relative_path( $path) {
274 $path_parts = array_filter( explode( '/', $path ) );
275
276 if ( empty( $path_parts ) ) {
277 return null;
278 }
279
280 return end( $path_parts );
281 }
282 }
283