Automattic /
jetpack
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 | |||
| 9 | public static function getInstance() { |
||
| 10 | if (null === static::$instance) { |
||
|
0 ignored issues
–
show
|
|||
| 11 | static::$instance = new static(); |
||
|
0 ignored issues
–
show
Since
$instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.
Let’s assume you have a class which uses late-static binding: class YourClass
{
private static $someVariable;
public static function getSomeVariable()
{
return static::$someVariable;
}
}
The code above will run fine in your PHP runtime. However, if you now create a
sub-class and call the class YourSubClass extends YourClass { }
YourSubClass::getSomeVariable(); // Will cause an access error.
In the case above, it makes sense to update class SomeClass
{
private static $someVariable;
public static function getSomeVariable()
{
return self::$someVariable; // self works fine with private.
}
}
Loading history...
|
|||
| 12 | } |
||
| 13 | |||
| 14 | return static::$instance; |
||
|
0 ignored issues
–
show
Since
$instance is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $instance to at least protected.
Let’s assume you have a class which uses late-static binding: class YourClass
{
private static $someVariable;
public static function getSomeVariable()
{
return static::$someVariable;
}
}
The code above will run fine in your PHP runtime. However, if you now create a
sub-class and call the class YourSubClass extends YourClass { }
YourSubClass::getSomeVariable(); // Will cause an access error.
In the case above, it makes sense to update class SomeClass
{
private static $someVariable;
public static function getSomeVariable()
{
return self::$someVariable; // self works fine with private.
}
}
Loading history...
|
|||
| 15 | } |
||
| 16 | |||
| 17 | // protect these methods for singleton |
||
| 18 | protected function __construct() { |
||
| 19 | $this->api = WPCOM_JSON_API::init(); |
||
| 20 | } |
||
| 21 | private function __clone() { } |
||
| 22 | private function __wakeup() { } |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Generate a URL to an endpoint |
||
| 26 | * |
||
| 27 | * Used to construct meta links in API responses |
||
| 28 | * |
||
| 29 | * @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 /**
* @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...
|
|||
| 30 | * @return string Endpoint URL |
||
| 31 | **/ |
||
| 32 | function get_link() { |
||
| 33 | $args = func_get_args(); |
||
| 34 | $format = array_shift( $args ); |
||
| 35 | $base = WPCOM_JSON_API__BASE; |
||
| 36 | |||
| 37 | $path = array_pop( $args ); |
||
| 38 | |||
| 39 | if ( $path ) { |
||
| 40 | $path = '/' . ltrim( $path, '/' ); |
||
| 41 | } |
||
| 42 | |||
| 43 | $args[] = $path; |
||
| 44 | |||
| 45 | // Escape any % in args before using sprintf |
||
| 46 | $escaped_args = array(); |
||
| 47 | foreach ( $args as $arg_key => $arg_value ) { |
||
| 48 | $escaped_args[ $arg_key ] = str_replace( '%', '%%', $arg_value ); |
||
| 49 | } |
||
| 50 | |||
| 51 | $relative_path = vsprintf( "$format%s", $escaped_args ); |
||
| 52 | |||
| 53 | if ( ! wp_startswith( $relative_path, '.' ) ) { |
||
| 54 | // Generic version. Match the requested version as best we can |
||
| 55 | $api_version = $this->get_closest_version_of_endpoint( $format, $relative_path ); |
||
| 56 | $base = substr( $base, 0, - 1 ) . $api_version; |
||
| 57 | } |
||
| 58 | |||
| 59 | // escape any % in the relative path before running it through sprintf again |
||
| 60 | $relative_path = str_replace( '%', '%%', $relative_path ); |
||
| 61 | // http, WPCOM_JSON_API__BASE, ... , path |
||
| 62 | // %s , %s , $format, %s |
||
| 63 | return esc_url_raw( sprintf( "https://%s$relative_path", $base ) ); |
||
| 64 | } |
||
| 65 | |||
| 66 | function get_me_link( $path = '' ) { |
||
| 67 | return $this->get_link( '/me', $path ); |
||
| 68 | } |
||
| 69 | |||
| 70 | function get_taxonomy_link( $blog_id, $taxonomy_id, $taxonomy_type, $path = '' ) { |
||
| 71 | if ( 'category' === $taxonomy_type ) |
||
| 72 | return $this->get_link( '/sites/%d/categories/slug:%s', $blog_id, $taxonomy_id, $path ); |
||
| 73 | else |
||
| 74 | return $this->get_link( '/sites/%d/tags/slug:%s', $blog_id, $taxonomy_id, $path ); |
||
| 75 | } |
||
| 76 | |||
| 77 | function get_media_link( $blog_id, $media_id, $path = '' ) { |
||
| 78 | return $this->get_link( '/sites/%d/media/%d', $blog_id, $media_id, $path ); |
||
| 79 | } |
||
| 80 | |||
| 81 | function get_site_link( $blog_id, $path = '' ) { |
||
| 82 | return $this->get_link( '/sites/%d', $blog_id, $path ); |
||
| 83 | } |
||
| 84 | |||
| 85 | function get_post_link( $blog_id, $post_id, $path = '' ) { |
||
| 86 | return $this->get_link( '/sites/%d/posts/%d', $blog_id, $post_id, $path ); |
||
| 87 | } |
||
| 88 | |||
| 89 | function get_comment_link( $blog_id, $comment_id, $path = '' ) { |
||
| 90 | return $this->get_link( '/sites/%d/comments/%d', $blog_id, $comment_id, $path ); |
||
| 91 | } |
||
| 92 | |||
| 93 | function get_publicize_connection_link( $blog_id, $publicize_connection_id, $path = '' ) { |
||
| 94 | return $this->get_link( '.1/sites/%d/publicize-connections/%d', $blog_id, $publicize_connection_id, $path ); |
||
| 95 | } |
||
| 96 | |||
| 97 | function get_publicize_connections_link( $keyring_token_id, $path = '' ) { |
||
| 98 | return $this->get_link( '.1/me/publicize-connections/?keyring_connection_ID=%d', $keyring_token_id, $path ); |
||
| 99 | } |
||
| 100 | |||
| 101 | function get_keyring_connection_link( $keyring_token_id, $path = '' ) { |
||
| 102 | return $this->get_link( '.1/me/keyring-connections/%d', $keyring_token_id, $path ); |
||
| 103 | } |
||
| 104 | |||
| 105 | function get_external_service_link( $external_service, $path = '' ) { |
||
| 106 | return $this->get_link( '.1/meta/external-services/%s', $external_service, $path ); |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Try to find the closest supported version of an endpoint to the current endpoint |
||
| 111 | * |
||
| 112 | * For example, if we were looking at the path /animals/panda: |
||
| 113 | * - if the current endpoint is v1.3 and there is a v1.3 of /animals/%s available, we return 1.3 |
||
| 114 | * - if the current endpoint is v1.3 and there is no v1.3 of /animals/%s known, we fall back to the |
||
| 115 | * maximum available version of /animals/%s, e.g. 1.1 |
||
| 116 | * |
||
| 117 | * This method is used in get_link() to construct meta links for API responses. |
||
| 118 | * |
||
| 119 | * @param $template_path The generic endpoint path, e.g. /sites/%s |
||
| 120 | * @param $path string The current endpoint path, relative to the version, e.g. /sites/12345 |
||
| 121 | * @param $method string Request method used to access the endpoint path |
||
| 122 | * @return string The current version, or otherwise the maximum version available |
||
| 123 | */ |
||
| 124 | function get_closest_version_of_endpoint( $template_path, $path, $request_method = 'GET' ) { |
||
| 125 | static $closest_endpoint_cache; |
||
| 126 | |||
| 127 | if ( ! $closest_endpoint_cache ) { |
||
| 128 | $closest_endpoint_cache = array(); |
||
| 129 | } |
||
| 130 | |||
| 131 | if ( ! isset( $closest_endpoint_cache[ $template_path ] ) ) { |
||
| 132 | $closest_endpoint_cache[ $template_path ] = array(); |
||
| 133 | } elseif ( isset( $closest_endpoint_cache[ $template_path ][ $request_method ] ) ) { |
||
| 134 | return $closest_endpoint_cache[ $template_path ][ $request_method ]; |
||
| 135 | } |
||
| 136 | |||
| 137 | $path = untrailingslashit( $path ); |
||
| 138 | |||
| 139 | // /help is a special case - always use the current request version |
||
| 140 | if ( wp_endswith( $path, '/help' ) ) { |
||
| 141 | return $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version; |
||
| 142 | } |
||
| 143 | |||
| 144 | static $matches; |
||
| 145 | if ( empty( $matches ) ) { |
||
| 146 | $matches = array(); |
||
| 147 | } else { |
||
| 148 | // try to match out of saved matches |
||
| 149 | foreach( $matches as $match ) { |
||
| 150 | $regex = $match->regex; |
||
| 151 | if ( preg_match( "#^$regex\$#", $path ) ) { |
||
| 152 | return $closest_endpoint_cache[ $template_path ][ $request_method ] = $match->version; |
||
| 153 | } |
||
| 154 | } |
||
| 155 | } |
||
| 156 | |||
| 157 | $endpoint_path_versions = $this->get_endpoint_path_versions(); |
||
| 158 | $last_path_segment = $this->get_last_segment_of_relative_path( $path ); |
||
| 159 | $max_version_found = null; |
||
| 160 | |||
| 161 | foreach ( $endpoint_path_versions as $endpoint_last_path_segment => $endpoints ) { |
||
| 162 | |||
| 163 | // Does the last part of the path match the path key? (e.g. 'posts') |
||
| 164 | // If the last part contains a placeholder (e.g. %s), we want to carry on |
||
| 165 | if ( $last_path_segment != $endpoint_last_path_segment && ! strstr( $endpoint_last_path_segment, '%' ) ) { |
||
| 166 | continue; |
||
| 167 | } |
||
| 168 | |||
| 169 | foreach ( $endpoints as $endpoint ) { |
||
| 170 | // Does the request method match? |
||
| 171 | if ( ! in_array( $request_method, $endpoint['request_methods'] ) ) { |
||
| 172 | continue; |
||
| 173 | } |
||
| 174 | |||
| 175 | $endpoint_path = untrailingslashit( $endpoint['path'] ); |
||
| 176 | $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path ); |
||
| 177 | |||
| 178 | if ( ! preg_match( "#^$endpoint_path_regex\$#", $path, $matches ) ) { |
||
| 179 | continue; |
||
| 180 | } |
||
| 181 | |||
| 182 | // Make sure the endpoint exists at the same version |
||
| 183 | if ( version_compare( $this->api->version, $endpoint['min_version'], '>=') && |
||
| 184 | version_compare( $this->api->version, $endpoint['max_version'], '<=') ) { |
||
| 185 | array_push( $matches, (object) array( 'version' => $this->api->version, 'regex' => $endpoint_path_regex ) ); |
||
| 186 | return $closest_endpoint_cache[ $template_path ][ $request_method ] = $this->api->version; |
||
| 187 | } |
||
| 188 | |||
| 189 | // If the endpoint doesn't exist at the same version, record the max version we found |
||
| 190 | if ( empty( $max_version_found ) || version_compare( $max_version_found['version'], $endpoint['max_version'], '<' ) ) { |
||
| 191 | $max_version_found = array( 'version' => $endpoint['max_version'], 'regex' => $endpoint_path_regex ); |
||
| 192 | } |
||
| 193 | } |
||
| 194 | } |
||
| 195 | |||
| 196 | // If the endpoint version is less than the requested endpoint version, return the max version found |
||
| 197 | if ( ! empty( $max_version_found ) ) { |
||
| 198 | array_push( $matches, (object) $max_version_found ); |
||
| 199 | return $max_version_found['version']; |
||
| 200 | } |
||
| 201 | |||
| 202 | // Otherwise, use the API version of the current request |
||
| 203 | return $this->api->version; |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Get an array of endpoint paths with their associated versions |
||
| 208 | * |
||
| 209 | * The result is cached for 30 minutes. |
||
| 210 | * |
||
| 211 | * @return array Array of endpoint paths, min_versions and max_versions, keyed by last segment of path |
||
| 212 | **/ |
||
| 213 | protected function get_endpoint_path_versions() { |
||
| 214 | |||
| 215 | static $cache_result; |
||
| 216 | |||
| 217 | if ( ! empty ( $cache_result ) ) { |
||
| 218 | return $cache_result; |
||
| 219 | } |
||
| 220 | |||
| 221 | /* |
||
| 222 | * Create a map of endpoints and their min/max versions keyed by the last segment of the path (e.g. 'posts') |
||
| 223 | * This reduces the search space when finding endpoint matches in get_closest_version_of_endpoint() |
||
| 224 | */ |
||
| 225 | $endpoint_path_versions = array(); |
||
| 226 | |||
| 227 | foreach ( $this->api->endpoints as $key => $endpoint_objects ) { |
||
| 228 | |||
| 229 | // The key contains a serialized path, min_version and max_version |
||
| 230 | list( $path, $min_version, $max_version ) = unserialize( $key ); |
||
| 231 | |||
| 232 | // Grab the last component of the relative path to use as the top-level key |
||
| 233 | $last_path_segment = $this->get_last_segment_of_relative_path( $path ); |
||
| 234 | |||
| 235 | $endpoint_path_versions[ $last_path_segment ][] = array( |
||
| 236 | 'path' => $path, |
||
| 237 | 'min_version' => $min_version, |
||
| 238 | 'max_version' => $max_version, |
||
| 239 | 'request_methods' => array_keys( $endpoint_objects ) |
||
| 240 | ); |
||
| 241 | } |
||
| 242 | |||
| 243 | $cache_result = $endpoint_path_versions; |
||
| 244 | |||
| 245 | return $endpoint_path_versions; |
||
| 246 | } |
||
| 247 | |||
| 248 | /** |
||
| 249 | * Grab the last segment of a relative path |
||
| 250 | * |
||
| 251 | * @param string $path Path |
||
| 252 | * @return string Last path segment |
||
| 253 | */ |
||
| 254 | protected function get_last_segment_of_relative_path( $path) { |
||
| 255 | $path_parts = array_filter( explode( '/', $path ) ); |
||
| 256 | |||
| 257 | if ( empty( $path_parts ) ) { |
||
| 258 | return null; |
||
| 259 | } |
||
| 260 | |||
| 261 | return end( $path_parts ); |
||
| 262 | } |
||
| 263 | } |
Let’s assume you have a class which uses late-static binding:
The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the
getSomeVariable()on that sub-class, you will receive a runtime error:In the case above, it makes sense to update
SomeClassto useselfinstead: