<?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;
    const DELETABLE = 16;
    const ALLMETHODS = 31;
    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, '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'), 'permalinks_enabled' => '' !== get_option('permalink_structure'), 'generate_password' => 'yes' === get_option('woocommerce_registration_generate_password'), 'links' => array('help' => 'https://woocommerce.github.io/woocommerce-rest-api-docs/'))));
        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://{$host}{$request}");
    }
    public function get_raw_data()
    {
        if (function_exists('phpversion') && version_compare(phpversion(), '5.6', '>=')) {
            return file_get_contents('php://input');
        }
        global $HTTP_RAW_POST_DATA;
        if (!isset($HTTP_RAW_POST_DATA)) {
            $HTTP_RAW_POST_DATA = file_get_contents('php://input');
        }
        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;
    }
}