<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
* Jetpack API endpoint base class.
* @package automattic/jetpack
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager;
use Automattic\Jetpack\Connection\Rest_Authentication;
use Automattic\Jetpack\Connection\Tokens;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
require_once __DIR__ . '/json-api-config.php';
require_once __DIR__ . '/sal/class.json-api-links.php';
require_once __DIR__ . '/sal/class.json-api-metadata.php';
require_once __DIR__ . '/sal/class.json-api-date.php';
abstract class WPCOM_JSON_API_Endpoint {
* The link-generating utility class
* @var WPCOM_JSON_API_Links
* Whether to pass wpcom user details.
public $pass_wpcom_user_details = false;
* Object Grouping For Documentation (Users, Posts, Comments)
* Stats extra value to bump
* Minimum version of the api for which to serve this endpoint
public $min_version = '0';
* Maximum version of the api for which to serve this endpoint
* @phan-suppress PhanUndeclaredConstant -- https://github.com/phan/phan/issues/4855
public $max_version = WPCOM_JSON_API__CURRENT_VERSION;
* Forced endpoint environment when running on WPCOM
* @var string '', 'wpcom', 'secure', or 'jetpack'
* Whether the endpoint is deprecated
public $deprecated = false;
* Version of the endpoint this endpoint is deprecated in favor of.
* @phan-suppress PhanUndeclaredConstant -- https://github.com/phan/phan/issues/4855
protected $new_version = WPCOM_JSON_API__CURRENT_VERSION;
* Whether the endpoint is only available on WordPress.com hosted blogs
public $jp_disabled = false;
* Path at which to serve this endpoint: sprintf() format.
* Identifiers to fill sprintf() formatted $path
public $path_labels = array();
* The REST endpoint if available.
* Jetpack Version in which REST support was introduced.
public $rest_min_jp_version;
* Accepted query parameters
// Default value => description.
'display' => 'Formats the output as HTML for display. Shortcodes are parsed, paragraph tags are added, etc..',
// Other possible values => description.
'edit' => 'Formats the output for editing. Shortcodes are left unparsed, significant whitespace is kept, etc..',
'http_envelope' => array(
'true' => 'Some environments (like in-browser JavaScript or Flash) block or divert responses with a non-200 HTTP status code. Setting this parameter will force the HTTP status code to always be 200. The JSON response is wrapped in an "envelope" containing the "real" HTTP status code and headers.',
'true' => 'Output pretty JSON',
'meta' => "(string) Optional. Loads data from the endpoints found in the 'meta' part of the response. Comma-separated list. Example: meta=site,likes",
'fields' => '(string) Optional. Returns specified fields only. Comma-separated list. Example: fields=ID,title',
// Parameter name => description (default value is empty).
'callback' => '(string) An optional JSONP callback function.',
public $response_format = array();
public $request_format = array();
* Is this endpoint still in testing phase? If so, not available to the public.
public $in_testing = false;
* Is this endpoint still allowed if the site in question is flagged?
public $allowed_if_flagged = false;
* Is this endpoint allowed if the site is red flagged?
public $allowed_if_red_flagged = false;
* Is this endpoint allowed if the site is deleted?
public $allowed_if_deleted = false;
* Example request to make
public $example_request = '';
* Example request data (for POST methods)
public $example_request_data = '';
* Example response from $example_request
public $example_response = '';
* OAuth2 scope required when running on WPCOM
public $required_scope = '';
* Set to true if the endpoint implements its own filtering instead of the standard `fields` query method
public $custom_fields_filtering = false;
* Set to true if the endpoint accepts all cross origin requests. You probably should not set this flag.
public $allow_cross_origin_request = false;
* Set to true if the endpoint can recieve unauthorized POST requests.
public $allow_unauthorized_request = false;
* Set to true if the endpoint should accept site based (not user based) authentication.
public $allow_jetpack_site_auth = false;
* Set to true if the endpoint should accept user based authentication.
public $allow_jetpack_token_auth = false;
* Set to true if the endpoint should accept auth from an upload token.
public $allow_upload_token_auth = false;
* Set to true if the endpoint should require auth from a Rewind auth token.
public $require_rewind_auth = false;
* Whether this endpoint allows falling back to a blog token for making requests to remote Jetpack sites.
public $allow_fallback_to_jetpack_blog_token = false;
const REST_NAMESPACE = 'jetpack/rest';
public $post_object_format;
public $comment_object_format;
* Dropdown page object format.
public $dropdown_page_object_format;
* @param string|array|object $args Args.
public function __construct( $args ) {
'allowed_if_flagged' => false,
'allowed_if_red_flagged' => false,
'allowed_if_deleted' => false,
'max_version' => WPCOM_JSON_API__CURRENT_VERSION,
'new_version' => WPCOM_JSON_API__CURRENT_VERSION,
'path_labels' => array(),
'rest_min_jp_version' => null,
'request_format' => array(),
'response_format' => array(),
'query_parameters' => array(),
'example_request_data' => '',
'example_response' => '',
'pass_wpcom_user_details' => false,
'custom_fields_filtering' => false,
'allow_cross_origin_request' => false,
'allow_unauthorized_request' => false,
'allow_jetpack_site_auth' => false,
'allow_jetpack_token_auth' => false,
'allow_upload_token_auth' => false,
'allow_fallback_to_jetpack_blog_token' => false,
$args = wp_parse_args( $args, $defaults );
$this->in_testing = $args['in_testing'];
$this->allowed_if_flagged = $args['allowed_if_flagged'];
$this->allowed_if_red_flagged = $args['allowed_if_red_flagged'];
$this->allowed_if_deleted = $args['allowed_if_deleted'];
$this->description = $args['description'];
$this->group = $args['group'];
$this->stat = $args['stat'];
$this->force = $args['force'];
$this->jp_disabled = $args['jp_disabled'];
$this->method = $args['method'];
$this->path = $args['path'];
$this->path_labels = $args['path_labels'];
$this->min_version = $args['min_version'];
$this->max_version = $args['max_version'];
$this->deprecated = $args['deprecated'];
$this->new_version = $args['new_version'];
$this->rest_route = $args['rest_route'];
$this->rest_min_jp_version = $args['rest_min_jp_version'];
// Ensure max version is not less than min version.
if ( version_compare( $this->min_version, $this->max_version, '>' ) ) {
$this->max_version = $this->min_version;
$this->pass_wpcom_user_details = $args['pass_wpcom_user_details'];
$this->custom_fields_filtering = (bool) $args['custom_fields_filtering'];
$this->allow_cross_origin_request = (bool) $args['allow_cross_origin_request'];
$this->allow_unauthorized_request = (bool) $args['allow_unauthorized_request'];
$this->allow_jetpack_site_auth = (bool) $args['allow_jetpack_site_auth'];
$this->allow_jetpack_token_auth = (bool) $args['allow_jetpack_token_auth'];
$this->allow_upload_token_auth = (bool) $args['allow_upload_token_auth'];
$this->allow_fallback_to_jetpack_blog_token = (bool) $args['allow_fallback_to_jetpack_blog_token'];
$this->require_rewind_auth = isset( $args['require_rewind_auth'] ) ? (bool) $args['require_rewind_auth'] : false;
$this->version = $args['version'];
$this->required_scope = $args['required_scope'];
if ( $this->request_format ) {
$this->request_format = array_filter( array_merge( $this->request_format, $args['request_format'] ) );
$this->request_format = $args['request_format'];
if ( $this->response_format ) {
$this->response_format = array_filter( array_merge( $this->response_format, $args['response_format'] ) );
$this->response_format = $args['response_format'];
if ( false === $args['query_parameters'] ) {
} elseif ( is_array( $args['query_parameters'] ) ) {
$this->query = array_filter( array_merge( $this->query, $args['query_parameters'] ) );
$this->api = WPCOM_JSON_API::init(); // Auto-add to WPCOM_JSON_API.
$this->links = WPCOM_JSON_API_Links::getInstance();
/** Example Request/Response */
// Examples for endpoint documentation request.
$this->example_request = $args['example_request'];
$this->example_request_data = $args['example_request_data'];
$this->example_response = $args['example_response'];
$this->api->add( $this );
if ( ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) && $this->rest_route && ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) ) {
$this->create_rest_route_for_endpoint();
* Get all query args. Prefill with defaults.
* @param bool $return_default_values Whether to include default values in the response.
* @param bool $cast_and_filter Whether to cast and filter input according to the documentation.
public function query_args( $return_default_values = true, $cast_and_filter = true ) {
$args = array_intersect_key( $this->api->query, $this->query );
if ( ! $cast_and_filter ) {
return $this->cast_and_filter( $args, $this->query, $return_default_values );
* @param bool $return_default_values Whether to include default values in the response.
* @param bool $cast_and_filter Whether to cast and filter input according to the documentation.
public function input( $return_default_values = true, $cast_and_filter = true ) {
$input = trim( (string) $this->api->post_body );
$content_type = (string) $this->api->content_type;
list ( $content_type ) = explode( ';', $content_type );
$content_type = trim( $content_type );
switch ( $content_type ) {
case 'application/x-javascript':
case 'text/x-javascript':
$return = json_decode( $input, true );
if ( JSON_ERROR_NONE !== json_last_error() ) {
case 'multipart/form-data':