<?php
/**
 * WooCommerce Braintree Gateway
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@woocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade WooCommerce Braintree Gateway to newer
 * versions in the future. If you wish to customize WooCommerce Braintree Gateway for your
 * needs please refer to http://docs.woocommerce.com/document/braintree/
 *
 * @package   WC-Braintree/Gateway
 * @author    WooCommerce
 * @copyright Copyright: (c) 2016-2019, Automattic, Inc.
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
 */

use WC_Braintree\Plugin_Framework as WC_Braintree_Framework;

defined( 'ABSPATH' ) or exit;

/**
 * The PayPal cart handler class.
 *
 * Sets up the actions & filters for handling PayPal's cart functionality.
 *
 * @since 2.0.0
 */
class WC_Braintree_PayPal_Cart {


	/** @var \WC_Gateway_Braintree_PayPal $gateway the PayPal gateway object */
	protected $gateway;


	/**
	 * Constructs the class.
	 *
	 * @since 2.0.0
	 * @param \WC_Gateway_Braintree_PayPal $gateway the PayPal gateway object
	 */
	public function __construct( WC_Gateway_Braintree_PayPal $gateway ) {

		$this->gateway = $gateway;

		if ( $gateway->cart_checkout_enabled() ) {
			$this->add_hooks();
		}
	}


	/**
	 * Adds the necessary actions & filters.
	 *
	 * @since 2.0.0
	 */
	protected function add_hooks() {

		// add the PayPal button below "Proceed to Checkout"
		add_action( 'woocommerce_proceed_to_checkout', array( $this, 'render' ), 50 );

		// handle the PayPal token generated by Braintree JS
		add_action( 'woocommerce_api_' . strtolower( get_class( $this->get_gateway() ) ), array( $this, 'handle_wc_api' ) );

		// set the checkout address value if it's available from the PayPal session
		add_filter( 'woocommerce_checkout_get_value', array( $this, 'set_checkout_value' ), 10, 2 );

		// set the "Ship to different address" checkbox based on PayPal session data
		add_filter( 'woocommerce_ship_to_different_address_checked', array( $this, 'set_ship_to_different_address' ) );

		// disable all other gateways at checkout when confirming payment
		add_action( 'woocommerce_available_payment_gateways', array( $this, 'disable_other_gateways' ) );

		// add styles for the cart & confirmation page
		add_action( 'wp_head', array( $this, 'enqueue_styles' ) );

		// add a "Cancel" link below the order button at checkout confirmation
		add_action( 'woocommerce_review_order_after_submit', array( $this, 'render_cancel_link' ) );

		// clear the session data when the cart is emptied
		add_action( 'woocommerce_cart_emptied', array( $this, 'clear_session_data' ) );
	}


	/**
	 * Renders the PayPal button markup and related JS data.
	 *
	 * @since 2.0.0
	 */
	public function render() {

		$this->clear_session_data();

		// if there are subscriptions or pre-orders in the cart and the user
		// already has some stored tokens, then we can halt the cart checkout
		// feature since the customer will likely have a better experience
		// choosing a stored payment method using our standard checkout flow.
		if ( ! $this->is_single_use() && ( ! $this->get_gateway()->tokenization_enabled() || $this->user_has_tokens() ) ) {
			return;
		}

		$this->render_button();

		$this->render_js();
	}


	/**
	 * Renders the PayPal button markup.
	 *
	 * @since 2.0.0
	 */
	public function render_button() {

		$total = WC()->cart->total;

		?>

		<div id="wc_braintree_paypal_container"></div>
		<input type="hidden" name="wc_braintree_paypal_amount" value="<?php echo esc_attr( WC_Braintree_Framework\SV_WC_Helper::number_format( $total, 2 ) ); ?>" />
		<input type="hidden" name="wc_braintree_paypal_currency" value="<?php echo esc_attr( get_woocommerce_currency() ); ?>" />
		<input type="hidden" name="wc_braintree_paypal_locale" value="<?php echo esc_attr( $this->get_gateway()->get_safe_locale() ); ?>" />
		<input type="hidden" name="wc_braintree_paypal_single_use" value="<?php echo (int) $this->is_single_use(); ?>" />

		<?php
	}


	/**
	 * Renders the PayPal button JS.
	 *
	 * @since 2.0.0
	 */
	public function render_js() {

		$params = $this->get_gateway()->get_payment_form_instance()->get_payment_form_handler_js_params();

		$params['button_styles']['label'] = 'checkout';

		/**
		 * Filters the PayPal cart button style parameters.
		 *
		 * See https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/customize-button/
		 *
		 * @since 2.1.0
		 *
		 * @param array $styles style parameters
		 */
		$params['button_styles'] = (array) apply_filters( 'wc_' . $this->get_gateway()->get_id() . '_cart_button_styles', $params['button_styles'] );

		$params = array_merge( [
			'id'                       => $this->get_gateway()->get_id(),
			'id_dasherized'            => $this->get_gateway()->get_id_dasherized(),
			'name'                     => $this->get_gateway()->get_method_title(),
			'debug'                    => $this->get_gateway()->debug_log(),
			'client_token_nonce'       => wp_create_nonce( 'wc_' . $this->get_gateway()->get_id() . '_get_client_token' ),
			'set_payment_method_nonce' => wp_create_nonce( 'wc_' . $this->get_gateway()->get_id() . '_cart_set_payment_method' ),
			'cart_handler_url'         => add_query_arg( 'wc-api', get_class( $this->get_gateway() ), home_url() )
		], $params );

		wc_enqueue_js( sprintf( 'window.wc_%1$s_handler = new WC_Braintree_PayPal_Cart_Handler( %2$s );', esc_js( $this->get_gateway()->get_id() ), json_encode( $params ) ) );
	}


	/**
	 * Handles the request made to the WC API after the JS has generated a token.
	 *
	 * @since 2.0.0
	 */
	public function handle_wc_api() {

		if ( ! wp_verify_nonce( WC_Braintree_Framework\SV_WC_Helper::get_post( 'wp_nonce' ), 'wc_' . $this->get_gateway()->get_id() . '_cart_set_payment_method' ) ) {
			return;
		}

		WC()->session->set( 'wc_braintree_paypal_cart_nonce', WC_Braintree_Framework\SV_WC_Helper::get_post( 'nonce' ) );

		if ( ! empty( $_POST['details'] ) ) {
			$this->set_customer_data( $_POST['details'] );
		}

		wp_send_json( array(
			'redirect_url' => wc_get_checkout_url(),
		) );
	}


	/**
	 * Sets the customer address session data from a PayPal token request.
	 *
	 * @since 2.0.0
	 * @param array $data the customer data
	 */
	protected function set_customer_data( $data = array() ) {

		// if there is no data to set, bail
		if ( ! is_array( $data ) || empty( $data ) ) {
			return;
		}

		$data = wp_parse_args( $data, array(
			'email'       => '',
			'payerId'     => '',
			'firstName'   => '',
			'lastName'    => '',
			'phone'       => '',
			'countryCode' => '',
			'shippingAddress' => array(),
			'billingAddress'  => array(),
		) );

		$address_defaults = array(
			'streetAddress'     => '',
			'extendedAddress'   => '',
			'locality'          => '',
			'region'            => '',
			'postalCode'        => '',
			'countryCodeAlpha2' => '',
		);

		$billing_address  = wp_parse_args( $data['billingAddress'], $address_defaults );
		$shipping_address = wp_parse_args( $data['shippingAddress'], $address_defaults );

		$details = array(
			'billing' => array(
				'first_name' => $data['firstName'],
				'last_name'  => $data['lastName'],
				'email'      => $data['email'],
				'phone'      => $data['phone'],
				'country'    => $billing_address['countryCodeAlpha2'],
				'address_1'  => $billing_address['streetAddress'],
				'address_2'  => $billing_address['extendedAddress'],
				'city'       => $billing_address['locality'],
				'state'      => $billing_address['region'],
				'postcode'   => $billing_address['postalCode'],
			),
			'shipping' => array(
				'first_name' => $data['firstName'],
				'last_name'  => $data['lastName'],
				'country'    => $shipping_address['countryCodeAlpha2'],
				'address_1'  => $shipping_address['streetAddress'],
				'address_2'  => $shipping_address['extendedAddress'],
				'city'       => $shipping_address['locality'],
				'state'      => $shipping_address['region'],
				'postcode'   => $shipping_address['postalCode'],
			),
		);

		WC()->session->set( 'wc_braintree_paypal_cart_customer_details', $details );
	}


	/**
	 * Sets the checkout address value if it's available from the PayPal session.
	 *
	 * @since 2.0.0
	 * @param null $value
	 * @param string $key the checkout field key
	 * @return string|null
	 */
	public function set_checkout_value( $value, $key ) {

		$details = WC()->session->get( 'wc_braintree_paypal_cart_customer_details' );

		if ( WC_Braintree_Framework\SV_WC_Helper::str_starts_with( $key, 'billing' ) ) {

			$type = 'billing';
			$key  = str_replace( 'billing_', '', $key );

		} elseif ( WC_Braintree_Framework\SV_WC_Helper::str_starts_with( $key, 'shipping' ) ) {

			$type = 'shipping';
			$key  = str_replace( 'shipping_', '', $key );

		} else {

			return $value;
		}

		if ( ! empty( $details[ $type ][ $key ] ) ) {
			$value = $details[ $type ][ $key ];
		}

		return $value;
	}


	/**
	 * Sets the "Ship to different address" checkbox based on PayPal session data.
	 *
	 * @since 2.0.0
	 * @param bool $checked
	 * @return bool
	 */
	public function set_ship_to_different_address( $checked ) {

		$details = WC()->session->get( 'wc_braintree_paypal_cart_customer_details', array() );

		if ( ! empty( $details ) ) {

			unset( $details['billing']['email'] );
			unset( $details['billing']['phone'] );

			if ( empty( $details['shipping'] ) ) {
				$checked = false;
			} elseif ( empty( $details['billing'] ) || $details['billing'] !== $details['shipping'] ) {
				$checked = true;
			}
		}

		return $checked;
	}


	/**
	 * Disables all other gateways at checkout when confirming payment.
	 *
	 * @since 2.0.0
	 * @param array $gateways
	 * @return array
	 */
	public function disable_other_gateways( $gateways ) {

		if ( ! $this->is_checkout_confirmation() ) {
			return $gateways;
		}

		foreach ( $gateways as $id => $gateway ) {

			if ( $id !== $this->get_gateway()->get_id() ) {
				unset( $gateways[ $id ] );
			}
		}

		return $gateways;
	}


	/**
	 * Adds styles for the cart & confirmation page.
	 *
	 * @since 2.0.0
	 */
	public function enqueue_styles() {

		if ( ! is_cart() && ! $this->is_checkout_confirmation() ) {
			return;
		}

		?>

		<style>
			.payment_box.payment_method_braintree_paypal {
				padding: 0;
			}
			.payment_box.payment_method_braintree_paypal fieldset {
				display: none;
			}
			.wc-braintree-paypal-cancel {
				display: block;
				text-align: center;
				padding: 15px;
			}
		</style>

		<?php
	}


	/**
	 * Renders a "Cancel" link.
	 *
	 * @since 2.0.0
	 */
	public function render_cancel_link() {

		if ( ! $this->is_checkout_confirmation() ) {
			return;
		}

		printf(
			'<a href="%1$s" class="wc-' . sanitize_html_class( $this->get_gateway()->get_id_dasherized() ) . '-cancel">%2$s</a>',
			esc_url( add_query_arg( array( 'wc_' . $this->get_gateway()->get_id() . '_clear_session' => true ), wc_get_cart_url() ) ),
			esc_html__( 'Cancel', 'woocommerce-gateway-paypal-powered-by-braintree' )
		);
	}


	/**
	 * Determines if the current view is at Checkout, confirming the cart PayPal
	 * purchase.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_checkout_confirmation() {

		return is_checkout() && $this->get_gateway()->is_available() && $this->get_cart_nonce();
	}


	/**
	 * Gets the cart nonce from the session, if any.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_cart_nonce() {

		return WC()->session->get( 'wc_braintree_paypal_cart_nonce', '' );
	}


	/**
	 * Clears any PayPal cart session data.
	 *
	 * @since 2.0.0
	 */
	public function clear_session_data() {

		unset( WC()->session->wc_braintree_paypal_cart_nonce );
		unset( WC()->session->wc_braintree_paypal_cart_customer_details );
	}


	/**
	 * Determines if the PayPal token should be considered "single use".
	 *
	 * This is primarily dependent on if there are subscriptions or Pre-Orders
	 * present in the cart.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_single_use() {

		$single_use = true;

		if ( $this->get_gateway()->get_plugin()->is_pre_orders_active() && \WC_Pre_Orders_Cart::cart_contains_pre_order() && \WC_Pre_Orders_Product::product_is_charged_upon_release( \WC_Pre_Orders_Cart::get_pre_order_product() ) ) {
			$single_use = false;
		}

		if ( $this->get_gateway()->get_plugin()->is_subscriptions_active() && ( \WC_Subscriptions_Cart::cart_contains_subscription() || wcs_cart_contains_renewal() ) ) {
			$single_use = false;
		}

		return $single_use;
	}


	/**
	 * Determines if the current user has any saved tokens.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	protected function user_has_tokens() {

		$token_count = 0;

		if ( $this->get_gateway()->tokenization_enabled() && is_user_logged_in() ) {

			foreach ( $this->get_gateway()->get_payment_tokens_handler()->get_tokens( get_current_user_id() ) as $token ) {

				// skip this token if it's not a PayPal account
				if ( ! $token->is_paypal_account() ) {
					continue;
				}

				$token_count++;
			}
		}

		return $token_count > 0;
	}


	/**
	 * Gets the PayPal gateway object.
	 *
	 * @since 2.0.0
	 * @return \WC_Gateway_Braintree_PayPal
	 */
	protected function get_gateway() {

		return $this->gateway;
	}


}
