plugin = $plugin; $this->add_hooks(); } /** * Adds the action and filter hooks. * * @since 2.0.0 */ protected function add_hooks() { add_action( 'admin_action_wc_square_connected', array( $this, 'handle_connected' ) ); add_action( 'admin_action_wc_square_disconnect', array( $this, 'handle_disconnect' ) ); // refresh the connection, triggered by Action Scheduler add_action( 'wc_square_refresh_connection', array( $this, 'refresh_connection' ) ); // index customers, triggered by Action Scheduler add_action( 'wc_square_index_customers', array( $this, 'index_customers' ) ); } /** * Handles a successful connection. * * @internal * * @since 2.0.0 */ public function handle_connected() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - Setting variable, nonce checked next line. $nonce = isset( $_GET['_wpnonce'] ) ? wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:ignore WordPress.Security.NonceVerification.Recommended $from = isset( $_GET['from'] ) ? wc_clean( wp_unslash( $_GET['from'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // check the user role & nonce if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_connected' ) ) { wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Input is sanitized before use, the call to urldecode() first triggers this warning $access_token = ! empty( $_GET['square_access_token'] ) ? sanitize_text_field( wp_unslash( urldecode( $_GET['square_access_token'] ) ) ) : ''; if ( empty( $access_token ) ) { $this->get_plugin()->log( 'Error: No access token was received.' ); add_action( 'admin_notices', function () { ?>

get_plugin()->get_settings_handler()->update_access_token( $access_token ); $this->get_plugin()->log( 'Access token successfully received.' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Input is sanitized before use, the call to urldecode() first triggers this warning $refresh_token = ! empty( $_GET['square_refresh_token'] ) ? sanitize_text_field( wp_unslash( urldecode( $_GET['square_refresh_token'] ) ) ) : ''; if ( empty( $refresh_token ) ) { $this->get_plugin()->log( 'Failed to receive refresh token from connect server.' ); } else { $this->get_plugin()->get_settings_handler()->update_refresh_token( $refresh_token ); $this->get_plugin()->log( 'Refresh token successfully received.' ); } $this->schedule_refresh(); $this->schedule_customer_index(); // on connect after upgrading to v2.0 from v1.0, initiate a catalog sync to refresh the Square item IDs if ( get_option( 'wc_square_updated_to_2_0_0' ) ) { // delete any old access token from v1, as it will be invalidated delete_option( 'woocommerce_square_merchant_access_token' ); if ( $this->get_plugin()->get_settings_handler()->is_system_of_record_square() ) { $this->get_plugin()->get_sync_handler()->start_manual_sync(); } } delete_option( 'wc_square_updated_to_2_0_0' ); if ( wc_square()->get_settings_handler()->is_custom_square_auth_keys_set() ) { update_option( 'wc_square_auth_key_updated', true ); } wp_safe_redirect( 'wizard' === $from ? admin_url( 'admin.php?page=woocommerce-square-onboarding' ) : $this->get_plugin()->get_settings_url() ); exit; } /** * Handles disconnection. * * @internal * * @since 2.0.0 */ public function handle_disconnect() { // remove the refresh fail flag if previously set delete_option( 'wc_square_refresh_failed' ); $nonce = isset( $_GET['_wpnonce'] ) ? wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // check the user role & nonce if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_disconnect' ) ) { wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) ); } // disconnect by clearing tokens, unscheduling syncs, etc... $this->disconnect(); $this->get_plugin()->log( 'Manually disconnected' ); $this->get_plugin()->get_message_handler()->add_message( __( 'Disconnected successfully', 'woocommerce-square' ) ); wp_safe_redirect( $this->get_plugin()->get_settings_url() ); exit; } /** * Disconnects the plugin. * * @since 2.0.0 */ public function disconnect() { // don't try to refresh anymore $this->unschedule_refresh(); // unschedule the interval sync $this->get_plugin()->get_sync_handler()->unschedule_sync(); // fully clear the access token $this->get_plugin()->get_settings_handler()->clear_access_tokens(); $this->get_plugin()->get_settings_handler()->clear_refresh_tokens(); // clear all background jobs so further API requests aren't attempted $this->get_plugin()->get_background_job_handler()->clear_all_jobs(); } /** Refresh methods ***********************************************************************************************/ /** * Schedules the connection refresh. * * @since 2.0.0 */ public function schedule_refresh() { if ( ! $this->get_plugin()->get_settings_handler()->is_connected() ) { return; } /** * Filters the frequency with which the OAuth connection should be refreshed. * * @since 2.0.0 * * @param int $interval refresh interval */ $interval = apply_filters( 'wc_square_connection_refresh_interval', WEEK_IN_SECONDS ); // Make sure that all refresh actions are cancelled before scheduling it. $this->unschedule_refresh(); as_schedule_single_action( time() + $interval, 'wc_square_refresh_connection', array(), 'square' ); } /** * Refreshes the access token via the Woo proxy. * * @since 2.0.0 */ public function refresh_connection() { if ( $this->get_plugin()->get_settings_handler()->is_sandbox() ) { return; } try { if ( $this->get_plugin()->get_settings_handler()->is_debug_enabled() ) { $this->get_plugin()->log( 'Refreshing connection...' ); } $refresh_token = $this->get_plugin()->get_settings_handler()->get_refresh_token(); if ( ! $refresh_token ) { $this->get_plugin()->log( 'No refresh token stored, cannot refresh connection.' ); update_option( 'wc_square_refresh_failed', 'yes' ); wc_square()->get_email_handler()->get_access_token_email()->trigger(); return; } $request = array( 'body' => array( 'token' => $this->get_plugin()->get_settings_handler()->get_refresh_token(), ), 'timeout' => 45, ); // make the request $response = wp_remote_post( $this->get_refresh_url(), $request ); // handle HTTP errors if ( is_wp_error( $response ) ) { throw new \Exception( $response->get_error_message() ); } $response = new Square\API\Responses\Connection_Refresh_Response( wp_remote_retrieve_body( $response ) ); // check for errors in the response if ( $response->has_error() ) { throw new \Exception( $response->get_error_message() ); } // ensure an access token, just in case if ( ! $response->get_token() ) { throw new \Exception( 'Access token missing from the response' ); } // store the new token $this->get_plugin()->get_settings_handler()->update_access_token( $response->get_token() ); // In case square updates the refresh token. if ( $response->get_refresh_token() ) { $this->get_plugin()->get_settings_handler()->update_refresh_token( $response->get_refresh_token() ); $this->get_plugin()->log( 'Connection successfully refreshed.' ); } // in case this option was set delete_option( 'wc_square_refresh_failed' ); } catch ( \Exception $exception ) { $this->get_plugin()->log( 'Unable to refresh connection: ' . $exception->getMessage() ); update_option( 'wc_square_refresh_failed', 'yes' ); wc_square()->get_email_handler()->get_access_token_email()->trigger(); } $this->schedule_refresh(); } /** * Unschedules the connection refresh. * * @since 2.0.0 */ protected function unschedule_refresh() { as_unschedule_all_actions( 'wc_square_refresh_connection', array(), 'square' ); } /** Customer index methods ****************************************************************************************/ /** * Index existing Square customers. * * @since 2.0.0 * * @param string $cursor pagination cursor */ public function index_customers( $cursor = '' ) { try { $response = $this->get_plugin()->get_api()->get_customers( $cursor ); if ( $response->get_data() instanceof ListCustomersResponse && is_array( $response->get_data()->getCustomers() ) ) { Square\Gateway\Customer_Helper::add_customers( $response->get_data()->getCustomers() ); // if there are more customers to query, schedule a followup action to index the next batch of customers if ( $response->get_data()->getCursor() ) { $this->schedule_customer_index( $response->get_data()->getCursor() ); } } } catch ( \Exception $exception ) { } } /** * Schedules the customer index action. * * @since 2.0.0 * * @param string $cursor pagination cursor */ protected function schedule_customer_index( $cursor = '' ) { if ( false === as_next_scheduled_action( 'wc_square_index_customers', array( $cursor ), 'square' ) ) { as_schedule_single_action( time(), 'wc_square_index_customers', array( $cursor ), 'square' ); } } /** Getter methods ************************************************************************************************/ /** * Gets the Connect button HTML. * * @since 2.0.0 * * @param bool $is_sandbox whether to point the button to production or sandbox * @return string */ public function get_connect_button_html( $is_sandbox = false ) { ob_start(); ?> $value ) { $redirect_url = add_query_arg( $key, $value, $redirect_url ); } $args = array( 'redirect' => rawurlencode( $redirect_url ), 'scopes' => implode( ',', $this->get_scopes() ), ); return add_query_arg( $args, $url ); // nosemgrep:audit.php.wp.security.xss.query-arg -- This URL is escaped on output in get_connect_button_html(). } /** * Gets the disconnect URL. * * @since 2.0.0 * * @return string */ protected function get_disconnect_url() { $action = 'wc_square_disconnect'; $url = add_query_arg( 'action', $action, admin_url() ); return wp_nonce_url( $url, $action ); } /** * Gets the token refresh URL. * * @since 2.0.0 * * @return string */ protected function get_refresh_url() { return $this->get_plugin()->get_settings_handler()->is_sandbox() ? self::REFRESH_URL_SANDBOX : self::REFRESH_URL_PRODUCTION; } /** * Gets the connection scopes. * * @since 2.0.0 * * @return string[] */ protected function get_scopes() { $scopes = array( 'MERCHANT_PROFILE_READ', 'PAYMENTS_READ', 'PAYMENTS_WRITE', 'ORDERS_READ', 'ORDERS_WRITE', 'CUSTOMERS_READ', 'CUSTOMERS_WRITE', 'SETTLEMENTS_READ', 'ITEMS_READ', 'ITEMS_WRITE', 'INVENTORY_READ', 'INVENTORY_WRITE', 'GIFTCARDS_READ', 'GIFTCARDS_WRITE', 'PAYMENTS_WRITE', 'ORDERS_WRITE', ); /** * Hook to filter scopes. * * @since 2.0.0 */ return (array) apply_filters( 'wc_square_connection_scopes', $scopes ); } /** * Gets the plugin instance. * * @since 2.0.0 * * @return Square\Plugin */ public function get_plugin() { return $this->plugin; } }