<?php
/**
 * @author      Flycart (Alagesan)
 * @license     http://www.gnu.org/licenses/gpl-3.0.html
 * @link        https://www.flycart.org
 * */
defined( 'ABSPATH' ) || exit;

class RestApi {
    /**
     * Authentication error.
     *
     * @var WP_Error
     */
    protected $error = null;

    /**
     * Logged in user data.
     *
     * @var stdClass
     */
    protected $user = null;

    /**
     * Current auth method.
     *
     * @var string
     */
    protected $auth_method = '';

    protected $controllers = array();
    public function __construct() {
        //Router
        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
        //Authentication
        add_filter( 'rest_authentication_errors', array( $this, 'authentication_fallback' ) );
        /*add_filter( 'determine_current_user', array( $this, 'authenticate' ), 14 );
        add_filter( 'rest_authentication_errors', array( $this, 'authentication_fallback' ) );
        add_filter( 'rest_authentication_errors', array( $this, 'check_authentication_error' ), 1500 );
        add_filter( 'rest_post_dispatch', array( $this, 'send_unauthorized_headers' ), 50 );
        add_filter( 'rest_pre_dispatch', array( $this, 'check_user_permissions' ), 10, 3 );*/
    }

    public function register_rest_routes() {
        foreach ( $this->get_rest_namespaces() as $namespace => $controllers ) {
            foreach ( $controllers as $controller_name => $controller_class ) {
                $this->controllers[ $namespace ][ $controller_name ] = new $controller_class();
                $this->controllers[ $namespace ][ $controller_name ]->register_routes();
            }
        }
    }

    protected function get_rest_namespaces() {
        return apply_filters(
            'wlpr_rest_api_get_rest_namespaces',
            array(
                'flycart-loyalty-points/v1' => $this->get_v1_controllers()
            )
        );
    }

    /**
     * List of controllers in the wc/v1 namespace.
     *
     * @return array
     */
    protected function get_v1_controllers() {
        require_once WLPR_PLUGIN_PATH.'App/Api/Controllers/LoyaltyPoints.php';
        return array(
            'custom'                  => 'LoyaltyPoints'
        );
    }

    function authentication_fallback($error){
        if(!empty($error)){
            return $error;
        }

        if($this->is_request_to_rest_api()){

            if ( is_ssl() ) {
                $error = $this->do_basic_authentication();
            }
            if(empty($error)){
                $error = $this->do_oauth_authentication();
            }
        }

        return $error;
    }

    /**
     * Check if is request to our REST API.
     *
     * @return bool
     */
    protected function is_request_to_rest_api() {
        if ( empty( $_SERVER['REQUEST_URI'] ) ) {
            return false;
        }

        $rest_prefix = trailingslashit( rest_get_url_prefix() );
        $request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );

        // Check if the request is to the WC API endpoints.
        $loyalty = ( false !== strpos( $request_uri, $rest_prefix . 'flycart-loyalty-points/' ) );

        return apply_filters( 'wlpr_rest_is_request_to_rest_api', $loyalty );
    }

    function do_basic_authentication(){
        $error = null;
        $this->auth_method = 'basic_auth';
        $consumer_key      = '';
        $consumer_secret   = '';

        // If the $_GET parameters are present, use those first.
        if ( ! empty( $_GET['consumer_key'] ) && ! empty( $_GET['consumer_secret'] ) ) { // WPCS: CSRF ok.
            $consumer_key    = $_GET['consumer_key']; // WPCS: CSRF ok, sanitization ok.
            $consumer_secret = $_GET['consumer_secret']; // WPCS: CSRF ok, sanitization ok.
        }

        // If the above is not present, we will do full basic auth.
        if ( ! $consumer_key && ! empty( $_SERVER['PHP_AUTH_USER'] ) && ! empty( $_SERVER['PHP_AUTH_PW'] ) ) {
            $consumer_key    = $_SERVER['PHP_AUTH_USER']; // WPCS: CSRF ok, sanitization ok.
            $consumer_secret = $_SERVER['PHP_AUTH_PW']; // WPCS: CSRF ok, sanitization ok.
        }

        // Stop if don't have any key.
        if ( ! $consumer_key || ! $consumer_secret ) {
            return  new WP_Error( 'woocommerce_rest_authentication_error', __( 'Consumer key or Consumer secret is invalid.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ) );
        }
        $setting_option = get_option('wlpr_settings', '');
        $client_id = (isset($setting_option['wlpr_client_id']) && !empty($setting_option['wlpr_client_id'])) ? $setting_option['wlpr_client_id'] : '';
        $client_secret = (isset($setting_option['wlpr_client_secret']) && !empty($setting_option['wlpr_client_secret'])) ? $setting_option['wlpr_client_secret'] : '';

        if ( ! hash_equals( $client_secret, $consumer_secret ) || ($client_id != $consumer_key) ) { // @codingStandardsIgnoreLine
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Consumer key or Consumer secret is invalid.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ));
        }
        return $error;
    }

    function do_oauth_authentication(){
        $this->auth_method = 'oauth1';
        $error = null;
        $params = $this->get_oauth_params();
        if ( empty( $params ) ) {
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Missing OAuth parameters.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ));
        }
        $setting_option = get_option('wlpr_settings', '');
        $client_id = (isset($setting_option['wlpr_client_id']) && !empty($setting_option['wlpr_client_id'])) ? $setting_option['wlpr_client_id'] : '';
        $client_secret = (isset($setting_option['wlpr_client_secret']) && !empty($setting_option['wlpr_client_secret'])) ? $setting_option['wlpr_client_secret'] : '';
        if(empty($client_secret) || empty($client_id)){
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Consumer key or Consumer secret is invalid.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ));
        }

        if(($client_id != $params['oauth_consumer_key'])){
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Consumer key is invalid.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ));
        }

        $signature = $this->validate_oauth_signature( $client_secret,$params );
        if ( is_wp_error( $signature ) ) {
            return $signature;
        }

        $valid_window = 15 * 60; // 15 minute window.

        if ( ( $params['oauth_timestamp'] < time() - $valid_window ) || ( $params['oauth_timestamp'] > time() + $valid_window ) ) {
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Invalid timestamp.', 'woocommerce' ), array( 'status' => 401 ) );
        }
        return $error;
    }

    public function get_oauth_params() {
        $params = array_merge( $_GET, $_POST );
        $params = wp_unslash( $params );
        $header = $this->get_authorization_header();

        if ( ! empty( $header ) ) {
            // Trim leading spaces.
            $header        = trim( $header );
            $header_params = $this->parse_header( $header );

            if ( ! empty( $header_params ) ) {
                $params = array_merge( $params, $header_params );
            }
        }

        $param_names = array(
            'oauth_consumer_key',
            'oauth_timestamp',
            'oauth_nonce',
            'oauth_signature',
            'oauth_signature_method',
        );

        $errors   = array();
        $have_one = false;

        // Check for required OAuth parameters.
        foreach ( $param_names as $param_name ) {
            if ( empty( $params[ $param_name ] ) ) {
                $errors[] = $param_name;
            } else {
                $have_one = true;
            }
        }

        // All keys are missing, so we're probably not even trying to use OAuth.
        if ( ! $have_one ) {
            return array();
        }

        // If we have at least one supplied piece of data, and we have an error,
        // then it's a failed authentication.
        if ( ! empty( $errors ) ) {
            return array();
        }

        return $params;
    }

    public function get_authorization_header() {
        if ( ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
            return wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ); // WPCS: sanitization ok.
        }

        if ( function_exists( 'getallheaders' ) ) {
            $headers = getallheaders();
            // Check for the authoization header case-insensitively.
            foreach ( $headers as $key => $value ) {
                if ( 'authorization' === strtolower( $key ) ) {
                    return $value;
                }
            }
        }

        return '';
    }

    public function parse_header( $header ) {
        if ( 'OAuth ' !== substr( $header, 0, 6 ) ) {
            return array();
        }

        // From OAuth PHP library, used under MIT license.
        $params = array();
        if ( preg_match_all( '/(oauth_[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches ) ) {
            foreach ( $matches[1] as $i => $h ) {
                $params[ $h ] = urldecode( empty( $matches[3][ $i ] ) ? $matches[4][ $i ] : $matches[3][ $i ] );
            }
            if ( isset( $params['realm'] ) ) {
                unset( $params['realm'] );
            }
        }

        return $params;
    }

    function validate_oauth_signature($consumer_secret,$params){
        $http_method  = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( $_SERVER['REQUEST_METHOD'] ) : ''; // WPCS: sanitization ok.
        $request_path = isset( $_SERVER['REQUEST_URI'] ) ? wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) : ''; // WPCS: sanitization ok.
        $wp_base      = get_home_url( null, '/', 'relative' );
        if ( substr( $request_path, 0, strlen( $wp_base ) ) === $wp_base ) {
            $request_path = substr( $request_path, strlen( $wp_base ) );
        }
        $base_request_uri = rawurlencode( get_home_url( null, $request_path, is_ssl() ? 'https' : 'http' ) );
        // Get the signature provided by the consumer and remove it from the parameters prior to checking the signature.
        $consumer_signature = rawurldecode( str_replace( ' ', '+', $params['oauth_signature'] ) );

        unset( $params['oauth_signature'] );

        // Sort parameters.
        if ( ! uksort( $params, 'strcmp' ) ) {
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Invalid signature - failed to sort parameters.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ) );
        }

        // Normalize parameter key/values.
        $params         = $this->normalize_parameters( $params );

        $query_string   = implode( '%26', $this->join_with_equals_sign( $params ) ); // Join with ampersand.
        $string_to_sign = $http_method . '&' . $base_request_uri . '&' . $query_string;

        if ( 'HMAC-SHA1' !== $params['oauth_signature_method'] && 'HMAC-SHA256' !== $params['oauth_signature_method'] ) {
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Invalid signature - signature method is invalid.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ) );
        }

        $hash_algorithm = strtolower( str_replace( 'HMAC-', '', $params['oauth_signature_method'] ) );

        $secret         = $consumer_secret . '&';
        $signature      = base64_encode( hash_hmac( $hash_algorithm, $string_to_sign, $secret, true ) );

        if ( ! hash_equals( $signature, $consumer_signature ) ) { // @codingStandardsIgnoreLine
            return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Invalid signature - provided signature does not match.', WLPR_TEXT_DOMAIN ), array( 'status' => 401 ) );
        }
        return true;
    }

    private function normalize_parameters( $parameters ) {
        $keys       = wc_rest_urlencode_rfc3986( array_keys( $parameters ) );
        $values     = wc_rest_urlencode_rfc3986( array_values( $parameters ) );
        $parameters = array_combine( $keys, $values );

        return $parameters;
    }

    private function join_with_equals_sign( $params, $query_params = array(), $key = '' ) {
        foreach ( $params as $param_key => $param_value ) {
            if ( $key ) {
                $param_key = $key . '%5B' . $param_key . '%5D'; // Handle multi-dimensional array.
            }

            if ( is_array( $param_value ) ) {
                $query_params = $this->join_with_equals_sign( $param_value, $query_params, $param_key );
            } else {
                $string         = $param_key . '=' . $param_value; // Join with equals sign.
                $query_params[] = wc_rest_urlencode_rfc3986( $string );
            }
        }

        return $query_params;
    }
}
new RestApi();