plugin = $plugin;
$this->plugin_id = 'wc_';
$this->id = $plugin->get_id();
add_action( 'init', array( $this, 'init' ) );
// remove some of our custom fields that shouldn't be saved.
add_action(
'woocommerce_settings_api_sanitized_fields_' . $this->id,
function ( $fields ) {
unset( $fields['general'], $fields['connect'], $fields['import_products'] );
if ( $this->is_sandbox() ) {
$this->update_access_token( $fields['sandbox_token'] );
$this->access_token = false; // Remove encrypted token.
$this->refresh_token = false; // Remove encrypted token.
}
// Update the sync interval if it is changed.
$this->maybe_change_sync_interval( $fields );
$this->init_form_fields(); // Reload form fields after saving token.
return $fields;
}
);
add_action( 'admin_notices', array( $this, 'show_auth_keys_changed_notice' ) );
add_action( 'admin_notices', array( $this, 'show_visit_wizard_notice' ) );
add_action( 'wp_ajax_wc_square_settings_get_locations', array( $this, 'get_locations_ajax_callback' ) );
add_action( 'admin_init', array( $this, 'square_onboarding_redirect' ) );
add_action( 'admin_menu', array( $this, 'register_pages' ) );
add_action( 'woocommerce_settings_square', array( $this, 'render_square_settings_container' ) );
add_action( 'woocommerce_settings_checkout', array( $this, 'render_payments_settings_container' ) );
// Register REST API controllers.
new \WooCommerce\Square\Admin\Rest\WC_REST_Square_Settings_Controller();
new \WooCommerce\Square\Admin\Rest\WC_REST_Square_Credit_Card_Payment_Settings_Controller();
new \WooCommerce\Square\Admin\Rest\WC_REST_Square_Cash_App_Settings_Controller();
new \WooCommerce\Square\Admin\Rest\WC_REST_Square_Gift_Cards_Settings_Controller();
}
/**
* Redirect users to the templates screen on plugin activation.
*
* @since 4.7.0
*/
public function square_onboarding_redirect() {
if ( ! $this->get_plugin()->get_dependency_handler()->meets_php_dependencies() ) {
return;
}
if ( ! get_option( 'wc_square_show_wizard_on_activation' ) ) {
add_option( 'wc_square_show_wizard_on_activation', true, '', 'no' );
wp_safe_redirect( admin_url( 'admin.php?page=woocommerce-square-onboarding' ) );
exit;
}
}
/**
* Registers square page(s).
*
* @since 4.7.0
*/
public function register_pages() {
if ( ! $this->get_plugin()->get_dependency_handler()->meets_php_dependencies() ) {
return;
}
$current_page = isset( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
if ( ! get_option( 'wc_square_connected_page_visited' ) || 'woocommerce-square-onboarding' === $current_page ) {
add_submenu_page( 'woocommerce', __( 'Square Onboarding', 'woocommerce-square' ), __( 'Square Onboarding', 'woocommerce-square' ), 'manage_woocommerce', 'woocommerce-square-onboarding', array( $this, 'render_onboarding_page' ) ); // phpcs:ignore WordPress.WP.Capabilities.Unknown
}
}
/**
* Output the Setup Wizard page(s).
*/
public function render_onboarding_page() {
printf(
'
'
);
}
/**
* Show a notice to visit the wizard on plugin activation.
*
* @since 4.7.0
*/
public function show_visit_wizard_notice() {
if ( ! wc_square()->get_dependency_handler()->meets_php_dependencies() ) {
return;
}
if ( get_option( 'wc_square_connected_page_visited' ) ) {
return;
}
wc_square()->get_admin_notice_handler()->add_admin_notice(
sprintf(
/* translators: %1$s - tag, %2$s - tag */
esc_html__(
'Welcome to Square for WooCommerce! Get started by visiting the %1$sOnboarding Wizard%2$s.',
'woocommerce-square'
),
'',
''
),
'wc-square-welcome',
array(
'dismissible' => false,
'notice_class' => 'notice-info',
)
);
}
/**
* Redirect users to the onboarding wizard screen on plugin activation.
*
* @since 4.7.0
*/
public function render_square_settings_container() {
$section = isset( $_GET['section'] ) && ! empty( $_GET['section'] ) ? wp_unslash( $_GET['section'] ) : 'general'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
printf(
'',
);
}
/**
* Redirect users to the onboarding wizard screen on plugin activation.
*
* @since 4.7.0
*/
public function render_payments_settings_container() {
$tab = isset( $_GET['tab'] ) ? wp_unslash( $_GET['tab'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
$section = isset( $_GET['section'] ) ? wp_unslash( $_GET['section'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
if ( 'checkout' !== $tab ) {
return;
}
if ( ! ( 'square_credit_card' === $section || 'square_cash_app_pay' === $section || 'gift_cards_pay' === $section ) ) {
return;
}
printf(
'',
);
}
/**
* Show warning to reconnect if the `SQUARE_ENCRYPTION_KEY` and `SQUARE_ENCRYPTION_SALT` constants
* are newly added.
*
* @since 4.2.0
*/
public function show_auth_keys_changed_notice() {
$is_keys_updated = get_option( 'wc_square_auth_key_updated', false );
$show_message = ( $this->is_custom_square_auth_keys_set() && empty( $is_keys_updated ) )
|| ( ! $this->is_custom_square_auth_keys_set() && $is_keys_updated );
if ( $show_message ) {
wc_square()->get_admin_notice_handler()->add_admin_notice(
esc_html__( 'Square was disconnected because authentication keys were changed. Please connect again.', 'woocommerce-square' ),
'wc-square-disconnected-keys-changed',
array(
'dismissible' => false,
'notice_class' => 'notice-warning',
)
);
delete_option( 'wc_square_access_tokens' );
}
if ( ! $this->is_custom_square_auth_keys_set() && $is_keys_updated ) {
delete_option( 'wc_square_auth_key_updated' );
}
}
/**
* Returns true if `SQUARE_ENCRYPTION_KEY` and `SQUARE_ENCRYPTION_SALT` constants are both set.
*
* @since 4.2.0
*
* @return boolean
*/
public function is_custom_square_auth_keys_set() {
return defined( 'SQUARE_ENCRYPTION_KEY' ) && defined( 'SQUARE_ENCRYPTION_SALT' );
}
/**
* Initializes form fields and settings.
*
* @since 3.5.1
*/
public function init() {
$this->init_form_fields();
$this->init_settings();
}
/**
* Initializes the form fields.
*
* @since 2.0.0
*/
public function init_form_fields() {
$this->form_fields = array();
}
/**
* Gets the form fields.
*
* Overridden to populate the Location settings options on display.
*
* @since 2.0.0
*
* @return array
*/
public function get_form_fields() {
$fields = parent::get_form_fields();
// Confirm our local enable sandbox setting matches what is sent from the front end
// to account for changes from sandbox to production incorrectly fetching sandbox locations.
if ( $this->settings && isset( $_POST['wc_square_environment'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$environment = 'yes' === $this->settings['enable_sandbox'] ? 'sandbox' : 'production';
if ( $environment !== $_POST['wc_square_environment'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
return $fields;
}
}
$location_id_field_key = '';
// Get the location_id field.
foreach ( $fields as $key => $value ) {
if ( strpos( $key, 'location_id' ) ) {
$location_id_field_key = $key;
break;
}
}
if ( did_action( 'wc_square_initialized' ) && $this->is_admin_settings_screen() && ! empty( $location_id_field_key ) ) {
$locations = array(
'' => __( 'Please choose a location', 'woocommerce-square' ),
);
if ( ! empty( $this->get_locations() ) ) {
foreach ( $this->get_locations() as $location ) {
if ( 'ACTIVE' === $location->getStatus() && in_array( 'CREDIT_CARD_PROCESSING', (array) $location->getCapabilities(), true ) ) {
$locations[ $location->getId() ] = $location->getName();
}
}
}
$fields[ $location_id_field_key ]['options'] = $locations;
}
return $fields;
}
/**
* Generates the HTML for import products button.
*
* @param string $id form id.
* @param array $field form fields.
*/
public function generate_import_products_html( $id, $field ) {
$is_location_set = (bool) $this->get_location_id();
$is_sor_set = (bool) $this->get_system_of_record_name();
$display = $is_location_set && $is_sor_set ? '' : 'display: none';
ob_start();
?>
|
'>
|
|
get_access_token() ) {
echo $this->get_plugin()->get_connection_handler()->get_disconnect_button_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo $this->get_plugin()->get_connection_handler()->get_connect_button_html( $this->is_sandbox() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
|
get_refresh_tokens();
$environment = $this->get_environment();
if ( ! empty( $token ) ) {
$this->refresh_token = $token;
if ( Utilities\Encryption_Utility::is_encryption_supported() ) {
$encryption = new Utilities\Encryption_Utility();
try {
$token = $encryption->encrypt_data( $token );
} catch ( \Exception $exception ) {
// log the event, but don't halt the process.
$this->get_plugin()->log( 'Could not encrypt refresh token. ' . $exception->getMessage() );
}
}
$refresh_tokens[ $environment ] = $token;
}
update_option( 'wc_square_refresh_tokens', $refresh_tokens );
}
/**
* Updates the stored access token.
*
* @since 2.0.0
*
* @param string $token access token.
*/
public function update_access_token( $token ) {
$access_tokens = $this->get_access_tokens();
$environment = $this->get_environment();
if ( ! empty( $token ) ) {
$this->access_token = $token;
if ( Utilities\Encryption_Utility::is_encryption_supported() ) {
$encryption = new Utilities\Encryption_Utility();
try {
$token = $encryption->encrypt_data( $token );
} catch ( \Exception $exception ) {
// log the event, but don't halt the process.
$this->get_plugin()->log( 'Could not encrypt access token. ' . $exception->getMessage() );
}
}
$access_tokens[ $environment ] = $token;
} elseif ( isset( $access_tokens[ $environment ] ) ) {
unset( $access_tokens[ $environment ] );
}
update_option( 'wc_square_access_tokens', $access_tokens );
}
/**
* Clears any stored refresh tokens.
*
* @since 2.0.0
*/
public function clear_refresh_tokens() {
delete_option( 'wc_square_refresh_tokens' );
}
/**
* Clears any stored access tokens.
*
* @since 2.0.0
*/
public function clear_access_tokens() {
delete_option( 'wc_square_access_tokens' );
}
/**
* Clears the location ID from the settings.
*
* This is helpful on disconnect / revoke so that previously set location IDs don't stick around and cause confusion.
*
* @since 2.0.0
*/
public function clear_location_id() {
$settings = get_option( $this->get_option_key(), array() );
$settings[ $this->get_environment() . '_location_id' ] = '';
update_option( $this->get_option_key(), $settings );
}
/** Conditional methods *******************************************************************************************/
/**
* Determines if WooCommerce is configured to be the Sync setting.
*
* @since 2.0.0
*
* @return bool
*/
public function is_system_of_record_woocommerce() {
return self::SYSTEM_OF_RECORD_WOOCOMMERCE === $this->get_system_of_record();
}
/**
* Determines if Square is configured to be the Sync setting.
*
* @since 2.0.0
*
* @return bool
*/
public function is_system_of_record_square() {
return self::SYSTEM_OF_RECORD_SQUARE === $this->get_system_of_record();
}
/**
* Determines if there is no Sync setting.
*
* @since 2.0.0
*
* @return bool
*/
public function is_system_of_record_disabled() {
$sor = $this->get_system_of_record();
return empty( $sor ) || self::SYSTEM_OF_RECORD_DISABLED === $sor;
}
/**
* Determines if inventory sync is enabled.
*
* @since 2.0.0
*
* @return bool
*/
public function is_inventory_sync_enabled() {
/**
* Filters the inventory sync setting.
*
* @since 2.0.0
*/
return (bool) apply_filters( 'wc_square_inventory_sync_enabled', 'yes' === get_option( 'woocommerce_manage_stock' ) && $this->is_product_sync_enabled() && 'yes' === $this->get_option( 'enable_inventory_sync' ) );
}
/**
* Determines if image overriding is enabled.
*
* @since 3.9.0
*
* @return bool
*/
public function is_override_product_images_enabled() {
/**
* Filter to enable/disable overriding product images.
*
* @since 3.9.0
*
* @param boolean 'should_override' Boolean flag to toggle overriding image feature.
*/
return (bool) apply_filters( 'wc_square_override_product_images_enabled', 'yes' === $this->get_option( 'override_product_images' ) );
}
/**
* Determines if product sync is enabled.
*
* @since 2.0.0
*
* @return bool
*/
public function is_product_sync_enabled() {
return ! $this->is_system_of_record_disabled();
}
/**
* Determines whether to hide products that don't exist in square from the catalog.
*
* @since 2.0.0
*
* @return bool
*/
public function hide_missing_square_products() {
return 'yes' === $this->get_option( 'hide_missing_products' );
}
/**
* Returns sync interval in seconds.
* Returns 1 hr = 3600 seconds as default.
*
* @since 3.5.1
*
* @return int
*/
public function get_sync_interval() {
$sync_interval = $this->get_option( 'sync_interval', '' );
$sync_interval = empty( $sync_interval ) ? HOUR_IN_SECONDS : $sync_interval * HOUR_IN_SECONDS;
/**
* Filters the frequency with which products should be synced.
*
* @since 2.0.0
*
* @param int $interval sync interval in seconds (defaults to one hour)
*/
return (int) max( MINUTE_IN_SECONDS, (int) apply_filters( 'wc_square_sync_interval', $sync_interval ) );
}
/**
* Determines if the plugin settings are fully configured.
*
* @since 2.0.0
*
* @return bool
*/
public function is_configured() {
return $this->get_location_id() && $this->get_system_of_record();
}
/**
* Determines if the plugin is connected to Square.
*
* @since 2.0.0
*
* @return bool
*/
public function is_connected() {
return (bool) $this->get_access_token();
}
/**
* Determines if configured in the sandbox environment.
*
* @since 2.0.0
*
* @return bool
*/
public function is_sandbox() {
return 'sandbox' === $this->get_environment();
}
/**
* Determines if debug logging is enabled.
*
* @since 2.0.0
*
* @return bool
*/
public function is_debug_enabled() {
return 'yes' === $this->get_option( 'debug_logging_enabled' );
}
/** Getter methods ************************************************************************************************/
/**
* Gets the configured location.
*
* @since 2.0.0
*
* @return string
*/
public function get_location_id() {
$location_id = $this->get_option( $this->get_environment() . '_location_id' );
if ( empty( $location_id ) ) {
$square_db_version = get_option( $this->get_plugin()->get_plugin_version_name() );
// if the Square DB version is still pre-2.2.0, fetch the location ID using the previous option name
if ( ! empty( $square_db_version ) && version_compare( $square_db_version, '2.2.0', '<' ) ) {
$location_id = $this->get_option( 'location_id' );
}
}
return $location_id;
}
/**
* Gets the available locations.
*
* @since 2.0.0
*
* @param bool $force whether to force a refetch of the locations.
*
* @return \Square\Models\Location[]
*/
public function get_locations( $force = false ) {
if ( is_array( $this->locations ) ) {
return $this->locations;
}
$locations_transient_key = 'wc_square_locations_' . $this->get_plugin()->get_version();
$section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! ( ( $this->is_admin_settings_screen() && 'update' !== $section ) || $force ) ) {
$this->locations = get_transient( $locations_transient_key );
}
if ( ! is_array( $this->locations ) && did_action( 'wc_square_initialized' ) ) {
$this->locations = array();
if ( ! $this->get_plugin()->get_dependency_handler()->meets_php_dependencies() ) {
return $this->locations;
}
try {
// cache the locations returned so they can be used elsewhere.
$this->locations = $this->get_plugin()->get_api( $this->get_access_token(), $this->is_sandbox() )->get_locations();
set_transient( $locations_transient_key, $this->locations, HOUR_IN_SECONDS );
// check the returned IDs against what's currently configured.
$stored_location_id = $this->get_location_id();
$found = ! $stored_location_id;
foreach ( $this->locations as $location ) {
if ( $stored_location_id && $location->getId() === $stored_location_id ) {
$found = true;
break;
}
}
// if the currently set location ID is not present in the connected account's available locations, clear it locally.
if ( ! $found ) {
$this->clear_location_id();
}
} catch ( \Exception $exception ) {
$this->get_plugin()->log( 'Could not retrieve business locations.' );
}
}
return $this->locations;
}
/**
* Ajax callback for locations.
*
* @since 4.7.0
*/
public function get_locations_ajax_callback() {
check_ajax_referer( 'wc_square_settings', 'security' );
$locations = $this->get_locations( true );
wp_send_json_success( $locations );
}
/**
* Gets the configured Sync setting.
*
* @since 2.0.0
*
* @return string
*/
public function get_system_of_record() {
return $this->get_option( 'system_of_record' );
}
/**
* Gets the configured Sync setting name.
*
* @since 2.0.0
*
* @return string or empty string if no Sync setting is configured
*/
public function get_system_of_record_name() {
switch ( $this->get_system_of_record() ) {
case 'square':
$sor = __( 'Square', 'woocommerce-square' );
break;
case 'woocommerce':
$sor = __( 'WooCommerce', 'woocommerce-square' );
break;
default:
$sor = '';
break;
}
return $sor;
}
/**
* Gets the refresh token.
*
* @since 2.0.0
*
* @return string|null
*/
public function get_refresh_token() {
if ( empty( $this->refresh_token ) ) {
$tokens = $this->get_refresh_tokens();
$token = null;
if ( ! empty( $tokens[ $this->get_environment() ] ) ) {
$token = $tokens[ $this->get_environment() ];
}
if ( $token && Utilities\Encryption_Utility::is_encryption_supported() ) {
$encryption = new Utilities\Encryption_Utility();
try {
$token = $encryption->decrypt_data( $token );
} catch ( \Exception $exception ) {
// log the event, but don't halt the process.
$this->get_plugin()->log( 'Could not decrypt refresh token. ' . $exception->getMessage() );
}
}
$this->refresh_token = $token;
}
/**
* Filters the configured refresh token.
*
* @since 2.0.0
*
* @param string $refresh_token
*/
return apply_filters( 'wc_square_refresh_token', $this->refresh_token );
}
/**
* Gets the access token.
*
* @since 2.0.0
*
* @return string|null
*/
public function get_access_token() {
if ( empty( $this->access_token ) || $this->is_admin_settings_screen() ) {
$tokens = $this->get_access_tokens();
$token = null;
if ( ! empty( $tokens[ $this->get_environment() ] ) ) {
$token = $tokens[ $this->get_environment() ];
}
if ( $token && Utilities\Encryption_Utility::is_encryption_supported() ) {
$encryption = new Utilities\Encryption_Utility();
try {
$token = $encryption->decrypt_data( $token );
} catch ( \Exception $exception ) {
// log the event, but don't halt the process.
$this->get_plugin()->log( 'Could not decrypt access token. ' . $exception->getMessage() );
}
}
$this->access_token = $token;
}
/**
* Filters the configured access token.
*
* @since 2.0.0
*
* @param string $access_token access token
*/
return apply_filters( 'wc_square_access_token', $this->access_token );
}
/**
* Gets the stored access tokens.
*
* Each environment may have its own token.
*
* @since 2.0.0
*
* @return array
*/
public function get_access_tokens() {
return (array) get_option( 'wc_square_access_tokens', array() );
}
/**
* Gets the stored refresh tokens.
*
* Each environment may have its own token.
*
* @since 2.0.0
*
* @return array
*/
public function get_refresh_tokens() {
return (array) get_option( 'wc_square_refresh_tokens', array() );
}
/**
* Gets setting enabled sandbox.
*
* @since 2.1.2
*
* @return string
*/
public function get_enable_sandbox() {
return $this->get_option( 'enable_sandbox' );
}
/**
* Tells is if the setting for enabling sandbox is checked.
*
* @since 2.1.2
*
* @return boolean
*/
public function is_sandbox_setting_enabled() {
return 'yes' === $this->get_enable_sandbox();
}
/**
* Gets the configured environment.
*
* @since 2.0.0
*
* @return string
*/
public function get_environment() {
$sanboxed = ( defined( 'WC_SQUARE_SANDBOX' ) && WC_SQUARE_SANDBOX ) || $this->is_sandbox_setting_enabled();
return $sanboxed ? 'sandbox' : 'production';
}
/**
* Gets the plugin instance.
*
* @since 2.0.0
*
* @return Plugin
*/
public function get_plugin() {
return $this->plugin;
}
/**
* Determines if the current request is for the Square admin settings screen.
*
* @since 2.1.5
* @return bool True if the current request is for the Square admin settings, otherwise false.
*/
public function is_admin_settings_screen() {
return isset( $_GET['page'], $_GET['tab'] ) && 'wc-settings' === $_GET['page'] && Plugin::PLUGIN_ID === $_GET['tab']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
/**
* Update the sync interval if it has changed.
*
* @param array $settings
* @return void
*/
public function maybe_change_sync_interval( $settings ) {
// Bail if we have a filter in place to manage the sync interval.
if ( has_filter( 'wc_square_sync_interval' ) ) {
return;
}
$old_settings = get_option( $this->get_option_key(), array() );
// Bail if we don't have a sync interval.
if ( empty( $old_settings['sync_interval'] ) || empty( $settings['sync_interval'] ) ) {
return;
}
// If the sync interval has changed, schedule a new sync.
if ( $old_settings['sync_interval'] !== $settings['sync_interval'] ) {
$this->plugin->get_sync_handler()->schedule_sync( true );
}
}
}