File: //usr/local/wp/php/WP_CLI/WpOrgApi.php
<?php
namespace WP_CLI;
use RuntimeException;
/**
* Class WpOrgApi.
*
* This is an abstraction of the WordPress.org API.
*
* @see https://codex.wordpress.org/WordPress.org_API
*
* @package WP_CLI
*/
final class WpOrgApi {
/**
* WordPress.org API root URL.
*
* @var string
*/
const API_ROOT = 'https://api.wordpress.org';
/**
* WordPress.org API root URL.
*
* @var string
*/
const DOWNLOADS_ROOT = 'https://downloads.wordpress.org';
/**
* Core checksums endpoint.
*
* @see https://codex.wordpress.org/WordPress.org_API#Checksum
*
* @var string
*/
const CORE_CHECKSUMS_ENDPOINT = self::API_ROOT . '/core/checksums/1.0/';
/**
* Plugin checksums endpoint.
*
* @var string
*/
const PLUGIN_CHECKSUMS_ENDPOINT = self::DOWNLOADS_ROOT . '/plugin-checksums/';
/**
* Plugin info endpoint.
*
* @var string
*/
const PLUGIN_INFO_ENDPOINT = self::API_ROOT . '/plugins/info/1.2/';
/**
* Theme info endpoint.
*
* @var string
*/
const THEME_INFO_ENDPOINT = self::API_ROOT . '/themes/info/1.2/';
/**
* Salt endpoint.
*
* @see https://codex.wordpress.org/WordPress.org_API#Secret_Key
*
* @var string
*/
const SALT_ENDPOINT = self::API_ROOT . '/secret-key/1.1/salt/';
/**
* Version check endpoint.
*
* @see https://codex.wordpress.org/WordPress.org_API#Version_Check
*
* @var string
*/
const VERSION_CHECK_ENDPOINT = self::API_ROOT . '/core/version-check/1.7/';
/**
* Options to pass onto the Requests library for executing the remote calls.
*
* @var array
*/
private $options;
/**
* WpOrgApi constructor.
*
* @param array $options Associative array of options to pass to the API abstraction.
*/
public function __construct( $options = [] ) {
$this->options = $options;
}
/**
* Gets the checksums for the given version of WordPress core.
*
* @param string $version Version string to query.
* @param string $locale Optional. Locale to query. Defaults to 'en_US'.
* @return bool|array False on failure. An array of checksums on success.
* @throws RuntimeException If the remote request fails.
*/
public function get_core_checksums( $version, $locale = 'en_US' ) {
$data = [
'version' => $version,
'locale' => $locale,
];
$url = sprintf(
'%s?%s',
self::CORE_CHECKSUMS_ENDPOINT,
http_build_query( $data, '', '&' )
);
$response = $this->json_get_request( $url );
if (
! is_array( $response )
|| ! isset( $response['checksums'] )
|| ! is_array( $response['checksums'] )
) {
return false;
}
return $response['checksums'];
}
/**
* Gets a core version check.
*
* @param string $locale Optional. Locale to request a version check for. Defaults to 'en_US'.
* @return array|false False on failure. Associative array of the offer on success.
* @throws RuntimeException If the remote request failed.
*/
public function get_core_version_check( $locale = 'en_US' ) {
$url = sprintf(
'%s?%s',
self::VERSION_CHECK_ENDPOINT,
http_build_query( [ 'locale' => $locale ], '', '&' )
);
$response = $this->json_get_request( $url );
if ( ! is_array( $response ) ) {
return false;
}
return $response;
}
/**
* Gets a download offer.
*
* @param string $locale Optional. Locale to request an offer from. Defaults to 'en_US'.
* @return array|false False on failure. Associative array of the offer on success.
* @throws RuntimeException If the remote request failed.
*/
public function get_core_download_offer( $locale = 'en_US' ) {
$response = $this->get_core_version_check( $locale );
if (
! is_array( $response )
|| ! isset( $response['offers'] )
|| ! is_array( $response['offers'] )
) {
return false;
}
$offer = $response['offers'][0];
if ( ! array_key_exists( 'locale', $offer ) || $locale !== $offer['locale'] ) {
return false;
}
return $offer;
}
/**
* Gets the checksums for the given version of plugin.
*
* @param string $plugin Plugin slug to query.
* @param string $version Version string to query.
* @return bool|array False on failure. An array of checksums on success.
* @throws RuntimeException If the remote request fails.
*/
public function get_plugin_checksums( $plugin, $version ) {
$url = sprintf(
'%s%s/%s.json',
self::PLUGIN_CHECKSUMS_ENDPOINT,
$plugin,
$version
);
$response = $this->json_get_request( $url );
if (
! is_array( $response )
|| ! isset( $response['files'] )
|| ! is_array( $response['files'] )
) {
return false;
}
return $response['files'];
}
/**
* Gets a plugin's info.
*
* @param string $plugin Plugin slug to query.
* @param string $locale Optional. Locale to request info for. Defaults to 'en_US'.
* @param array $fields Optional. Fields to include/omit from the response.
* @return array|false False on failure. Associative array of the offer on success.
* @throws RuntimeException If the remote request failed.
*/
public function get_plugin_info( $plugin, $locale = 'en_US', array $fields = [] ) {
$action = 'plugin_information';
$request = [
'locale' => $locale,
'slug' => $plugin,
];
if ( ! empty( $fields ) ) {
$request['fields'] = $fields;
}
$url = sprintf(
'%s?%s',
self::PLUGIN_INFO_ENDPOINT,
http_build_query( compact( 'action', 'request' ), '', '&' )
);
$response = $this->json_get_request( $url );
if ( ! is_array( $response ) ) {
return false;
}
return $response;
}
/**
* Gets a theme's info.
*
* @param string $theme Theme slug to query.
* @param string $locale Optional. Locale to request info for. Defaults to 'en_US'.
* @param array $fields Optional. Fields to include/omit from the response.
* @return array|false False on failure. Associative array of the offer on success.
* @throws RuntimeException If the remote request failed.
*/
public function get_theme_info( $theme, $locale = 'en_US', array $fields = [] ) {
$action = 'theme_information';
$request = [
'locale' => $locale,
'slug' => $theme,
];
if ( ! empty( $fields ) ) {
$request['fields'] = $fields;
}
$url = sprintf(
'%s?%s',
self::THEME_INFO_ENDPOINT,
http_build_query( compact( 'action', 'request' ), '', '&' )
);
$response = $this->json_get_request( $url );
if ( ! is_array( $response ) ) {
return false;
}
return $response;
}
/**
* Gets a set of salts in the format required by `wp-config.php`.
*
* @return string A string of PHP define() statements.
* @throws RuntimeException If the remote request fails.
*/
public function get_salts() {
return $this->get_request( self::SALT_ENDPOINT );
}
/**
* Execute a remote GET request.
*
* @param string $url URL to execute the GET request on.
* @param array $headers Optional. Associative array of headers.
* @param array $options Optional. Associative array of options.
* @return mixed|false False on failure. Decoded JSON on success.
* @throws RuntimeException If the JSON could not be decoded.
*/
private function json_get_request( $url, $headers = [], $options = [] ) {
$headers = array_merge(
[
'Accept' => 'application/json',
],
$headers
);
$response = $this->get_request( $url, $headers, $options );
if ( false === $response ) {
return $response;
}
$data = json_decode( $response, true );
if ( JSON_ERROR_NONE !== json_last_error() ) {
throw new RuntimeException( 'Failed to decode JSON: ' . json_last_error_msg() );
}
return $data;
}
/**
* Execute a remote GET request.
*
* @param string $url URL to execute the GET request on.
* @param array $headers Optional. Associative array of headers.
* @param array $options Optional. Associative array of options.
* @return string Response body.
* @throws RuntimeException If the remote request fails.
*/
private function get_request( $url, $headers = [], $options = [] ) {
$options = array_merge(
$this->options,
[
'halt_on_error' => false,
],
$options
);
$response = Utils\http_request( 'GET', $url, null, $headers, $options );
if (
! $response->success
|| 200 > (int) $response->status_code
|| 300 <= $response->status_code
) {
throw new RuntimeException(
"Couldn't fetch response from {$url} (HTTP code {$response->status_code})."
);
}
return trim( $response->body );
}
}