Completed
Push — fix/backups-scan-status ( 58ae00...537384 )
by
unknown
10:58
created

sal/class.json-api-links.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
0 ignored issues
show
There is no parameter named $args. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

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