<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once ABSPATH . 'wp-admin/includes/admin.php';
class WC_API_Server {
const METHOD_GET = 1;
const METHOD_POST = 2;
const METHOD_PUT = 4;
const METHOD_PATCH = 8;
const METHOD_DELETE = 16;
const READABLE = 1;
const CREATABLE = 2;
const EDITABLE = 14; | PUT | PATCH
const DELETABLE = 16;
const ALLMETHODS = 31; | POST | PUT | PATCH | DELETE
const ACCEPT_RAW_DATA = 64;
const ACCEPT_DATA = 128;
const HIDDEN_ENDPOINT = 256;
public static $method_map = array(
'HEAD' => self::METHOD_GET,
'GET' => self::METHOD_GET,
'POST' => self::METHOD_POST,
'PUT' => self::METHOD_PUT,
'PATCH' => self::METHOD_PATCH,
'DELETE' => self::METHOD_DELETE,
);
public $path = '';
public $method = 'HEAD';
public $params = array( 'GET' => array(), 'POST' => array() );
public $headers = array();
public $files = array();
public $handler;
public function __construct( $path ) {
if ( empty( $path ) ) {
if ( isset( $_SERVER['PATH_INFO'] ) ) {
$path = $_SERVER['PATH_INFO'];
} else {
$path = '/';
}
}
$this->path = $path;
$this->method = $_SERVER['REQUEST_METHOD'];
$this->params['GET'] = $_GET;
$this->params['POST'] = $_POST;
$this->headers = $this->get_headers( $_SERVER );
$this->files = $_FILES;
if ( isset( $_GET['_method'] ) ) {
$this->method = strtoupper( $_GET['_method'] );
} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
$this->method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
}
$handler_class = apply_filters( 'woocommerce_api_default_response_handler', 'WC_API_JSON_Handler', $this->path, $this );
$this->handler = new $handler_class();
}
public function check_authentication() {
$user = apply_filters( 'woocommerce_api_check_authentication', null, $this );
if ( is_a( $user, 'WP_User' ) ) {
wp_set_current_user( $user->ID );
} elseif ( ! is_wp_error( $user ) ) {
$user = new WP_Error( 'woocommerce_api_authentication_error', __( 'Invalid authentication method', 'woocommerce' ), array( 'code' => 500 ) );
}
return $user;
}
protected function error_to_array( $error ) {
$errors = array();
foreach ( (array) $error->errors as $code => $messages ) {
foreach ( (array) $messages as $message ) {
$errors[] = array( 'code' => $code, 'message' => $message );
}
}
return array( 'errors' => $errors );
}
public function serve_request() {
do_action( 'woocommerce_api_server_before_serve', $this );
$this->header( 'Content-Type', $this->handler->get_content_type(), true );
if ( ! apply_filters( 'woocommerce_api_enabled', true, $this ) || ( 'no' === get_option( 'woocommerce_api_enabled' ) ) ) {
$this->send_status( 404 );
echo $this->handler->generate_response( array( 'errors' => array( 'code' => 'woocommerce_api_disabled', 'message' => 'The WooCommerce API is disabled on this site' ) ) );
return;
}
$result = $this->check_authentication();
if ( ! is_wp_error( $result ) ) {
$result = $this->dispatch();
}
if ( is_wp_error( $result ) ) {
$data = $result->get_error_data();
if ( is_array( $data ) && isset( $data['status'] ) ) {
$this->send_status( $data['status'] );
}
$result = $this->error_to_array( $result );
}
$served = apply_filters( 'woocommerce_api_serve_request', false, $result, $this );
if ( ! $served ) {
if ( 'HEAD' === $this->method ) {
return;
}
echo $this->handler->generate_response( $result );
}
}
public function get_routes() {
$endpoints = array(
'/' => array( array( $this, 'get_index' ), self::READABLE ),
);
$endpoints = apply_filters( 'woocommerce_api_endpoints', $endpoints );
foreach ( $endpoints as $route => &$handlers ) {
if ( count( $handlers ) <= 2 && isset( $handlers[1] ) && ! is_array( $handlers[1] ) ) {
$handlers = array( $handlers );
}
}
return $endpoints;
}
public function dispatch() {
switch ( $this->method ) {
case 'HEAD' :
case 'GET' :
$method = self::METHOD_GET;
break;
case 'POST' :
$method = self::METHOD_POST;
break;
case 'PUT' :
$method = self::METHOD_PUT;
break;
case 'PATCH' :
$method = self::METHOD_PATCH;
break;
case 'DELETE' :
$method = self::METHOD_DELETE;
break;
default :
return new WP_Error( 'woocommerce_api_unsupported_method', __( 'Unsupported request method', 'woocommerce' ), array( 'status' => 400 ) );
}
foreach ( $this->get_routes() as $route => $handlers ) {
foreach ( $handlers as $handler ) {
$callback = $handler[0];
$supported = isset( $handler[1] ) ? $handler[1] : self::METHOD_GET;
if ( ! ( $supported & $method ) ) {
continue;
}
$match = preg_match( '@^' . $route . '$@i', urldecode( $this->path ), $args );
if ( ! $match ) {
continue;
}
if ( ! is_callable( $callback ) ) {
return new WP_Error( 'woocommerce_api_invalid_handler', __( 'The handler for the route is invalid', 'woocommerce' ), array( 'status' => 500 ) );
}
$args = array_merge( $args, $this->params['GET'] );
if ( $method & self::METHOD_POST ) {
$args = array_merge( $args, $this->params['POST'] );
}
if ( $supported & self::ACCEPT_DATA ) {
$data = $this->handler->parse_body( $this->get_raw_data() );
$args = array_merge( $args, array( 'data' => $data ) );
} elseif ( $supported & self::ACCEPT_RAW_DATA ) {
$data = $this->get_raw_data();
$args = array_merge( $args, array( 'data' => $data ) );
}
$args['_method'] = $method;
$args['_route'] = $route;
$args['_path'] = $this->path;
$args['_headers'] = $this->headers;
$args['_files'] = $this->files;
$args = apply_filters( 'woocommerce_api_dispatch_args', $args, $callback );
if ( is_wp_error( $args ) ) {
return $args;
}
$params = $this->sort_callback_params( $callback, $args );
if ( is_wp_error( $params ) ) {
return $params;
}
return call_user_func_array( $callback, $params );
}
}
return new WP_Error( 'woocommerce_api_no_route', __( 'No route was found matching the URL and request method', 'woocommerce' ), array( 'status' => 404 ) );
}
protected function urldecode_deep( $value ) {
if ( is_array( $value ) ) {
return array_map( array( $this, 'urldecode_deep' ), $value );
} else {
return urldecode( $value );
}
}
protected function sort_callback_params( $callback, $provided ) {
if ( is_array( $callback ) ) {
$ref_func = new ReflectionMethod( $callback[0], $callback[1] );
} else {
$ref_func = new ReflectionFunction( $callback );
}
$wanted = $ref_func->getParameters();
$ordered_parameters = array();
foreach ( $wanted as $param ) {
if ( isset( $provided[ $param->getName() ] ) ) {
if ( 'data' == $param->getName() ) {
$ordered_parameters[] = $provided[ $param->getName() ];
continue;
}
$ordered_parameters[] = $this->urldecode_deep( $provided[ $param->getName() ] );
} elseif ( $param->isDefaultValueAvailable() ) {
$ordered_parameters[] = $param->getDefaultValue();
} else {
return new WP_Error( 'woocommerce_api_missing_callback_param', sprintf( __( 'Missing parameter %s', 'woocommerce' ), $param->getName() ), array( 'status' => 400 ) );
}
}
return $ordered_parameters;
}
public function get_index() {
$available = array(
'store' => array(
'name' => get_option( 'blogname' ),
'description' => get_option( 'blogdescription' ),
'URL' => get_option( 'siteurl' ),
'wc_version' => WC()->version,
'version' => WC_API::VERSION,
'routes' => array(),
'meta' => array(
'timezone' => wc_timezone_string(),
'currency' => get_woocommerce_currency(),
'currency_format' => get_woocommerce_currency_symbol(),
'currency_position' => get_option( 'woocommerce_currency_pos' ),
'thousand_separator' => get_option( 'woocommerce_price_thousand_sep' ),
'decimal_separator' => get_option( 'woocommerce_price_decimal_sep' ),
'price_num_decimals' => wc_get_price_decimals(),
'tax_included' => wc_prices_include_tax(),
'weight_unit' => get_option( 'woocommerce_weight_unit' ),
'dimension_unit' => get_option( 'woocommerce_dimension_unit' ),
'ssl_enabled' => ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) || wc_site_is_https() ),
'permalinks_enabled' => ( '' !== get_option( 'permalink_structure' ) ),
'generate_password' => ( 'yes' === get_option( 'woocommerce_registration_generate_password' ) ),
'links' => array(
'help' => 'https:
),
),
),
);
foreach ( $this->get_routes() as $route => $callbacks ) {
$data = array();
$route = preg_replace( '#\(\?P(<\w+?>).*?\)#', '$1', $route );
foreach ( self::$method_map as $name => $bitmask ) {
foreach ( $callbacks as $callback ) {
if ( $callback[1] & self::HIDDEN_ENDPOINT ) {
continue 3;
}
if ( $callback[1] & $bitmask ) {
$data['supports'][] = $name;
}
if ( $callback[1] & self::ACCEPT_DATA ) {
$data['accepts_data'] = true;
}
if ( strpos( $route, '<' ) === false ) {
$data['meta'] = array(
'self' => get_woocommerce_api_url( $route ),
);
}
}
}
$available['store']['routes'][ $route ] = apply_filters( 'woocommerce_api_endpoints_description', $data );
}
return apply_filters( 'woocommerce_api_index', $available );
}
public function send_status( $code ) {
status_header( $code );
}
public function header( $key, $value, $replace = true ) {
header( sprintf( '%s: %s', $key, $value ), $replace );
}
public function link_header( $rel, $link, $other = array() ) {
$header = sprintf( '<%s>; rel="%s"', $link, esc_attr( $rel ) );
foreach ( $other as $key => $value ) {
if ( 'title' == $key ) {
$value = '"' . $value . '"';
}
$header .= '; ' . $key . '=' . $value;
}
$this->header( 'Link', $header, false );
}
public function add_pagination_headers( $query ) {
if ( is_a( $query, 'WP_User_Query' ) ) {
$single = count( $query->get_results() ) == 1;
$total = $query->get_total();
if ( $query->get( 'number' ) > 0 ) {
$page = ( $query->get( 'offset' ) / $query->get( 'number' ) ) + 1;
$total_pages = ceil( $total / $query->get( 'number' ) );
} else {
$page = 1;
$total_pages = 1;
}
} elseif ( is_a( $query, 'stdClass' ) ) {
$page = $query->page;
$single = $query->is_single;
$total = $query->total;
$total_pages = $query->total_pages;
} else {
$page = $query->get( 'paged' );
$single = $query->is_single();
$total = $query->found_posts;
$total_pages = $query->max_num_pages;
}
if ( ! $page ) {
$page = 1;
}
$next_page = absint( $page ) + 1;
if ( ! $single ) {
if ( $page > 1 ) {
$this->link_header( 'first', $this->get_paginated_url( 1 ) );
$this->link_header( 'prev', $this->get_paginated_url( $page -1 ) );
}
if ( $next_page <= $total_pages ) {
$this->link_header( 'next', $this->get_paginated_url( $next_page ) );
}
if ( $page != $total_pages ) {
$this->link_header( 'last', $this->get_paginated_url( $total_pages ) );
}
}
$this->header( 'X-WC-Total', $total );
$this->header( 'X-WC-TotalPages', $total_pages );
do_action( 'woocommerce_api_pagination_headers', $this, $query );
}
private function get_paginated_url( $page ) {
$request = remove_query_arg( 'page' );
$request = urldecode( add_query_arg( 'page', $page, $request ) );
$host = parse_url( get_home_url(), PHP_URL_HOST );
return set_url_scheme( "http:
}
public function get_raw_data() {
if ( function_exists( 'phpversion' ) && version_compare( phpversion(), '5.6', '>=' ) ) {
return file_get_contents( 'php:
}
global $HTTP_RAW_POST_DATA;
if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
$HTTP_RAW_POST_DATA = file_get_contents( 'php:
}
return $HTTP_RAW_POST_DATA;
}
public function parse_datetime( $datetime ) {
if ( strpos( $datetime, '.' ) !== false ) {
$datetime = preg_replace( '/\.\d+/', '', $datetime );
}
$datetime = preg_replace( '/[+-]\d+:+\d+$/', '+00:00', $datetime );
try {
$datetime = new DateTime( $datetime, new DateTimeZone( 'UTC' ) );
} catch ( Exception $e ) {
$datetime = new DateTime( '@0' );
}
return $datetime->format( 'Y-m-d H:i:s' );
}
public function format_datetime( $timestamp, $convert_to_utc = false, $convert_to_gmt = false ) {
if ( $convert_to_gmt ) {
if ( is_numeric( $timestamp ) ) {
$timestamp = date( 'Y-m-d H:i:s', $timestamp );
}
$timestamp = get_gmt_from_date( $timestamp );
}
if ( $convert_to_utc ) {
$timezone = new DateTimeZone( wc_timezone_string() );
} else {
$timezone = new DateTimeZone( 'UTC' );
}
try {
if ( is_numeric( $timestamp ) ) {
$date = new DateTime( "@{$timestamp}" );
} else {
$date = new DateTime( $timestamp, $timezone );
}
if ( $convert_to_utc ) {
$date->modify( -1 * $date->getOffset() . ' seconds' );
}
} catch ( Exception $e ) {
$date = new DateTime( '@0' );
}
return $date->format( 'Y-m-d\TH:i:s\Z' );
}
public function get_headers( $server ) {
$headers = array();
$additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true );
foreach ( $server as $key => $value ) {
if ( strpos( $key, 'HTTP_' ) === 0 ) {
$headers[ substr( $key, 5 ) ] = $value;
} elseif ( isset( $additional[ $key ] ) ) {
$headers[ $key ] = $value;
}
}
return $headers;
}
}