oont-contents/plugins/woocommerce-square/includes/Lifecycle.php
2025-02-08 15:10:23 +01:00

686 lines
20 KiB
PHP

<?php
/**
* WooCommerce Square
*
* 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 GNU General Public License v3.0 or later
* 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 Square to newer
* versions in the future. If you wish to customize WooCommerce Square for your
* needs please refer to https://docs.woocommerce.com/document/woocommerce-square/
*
* @author WooCommerce
* @copyright Copyright: (c) 2019, Automattic, Inc.
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
*/
namespace WooCommerce\Square;
defined( 'ABSPATH' ) || exit;
use WooCommerce\Square\Handlers\Product;
/**
* The plugin lifecycle handler.
*
* @since 2.0.0
*
* @method Plugin get_plugin()
*/
class Lifecycle extends \WooCommerce\Square\Framework\Lifecycle {
/**
* Lifecycle constructor.
*
* @since 2.0.0
*
* @param Plugin $plugin main instance.
*/
public function __construct( Plugin $plugin ) {
parent::__construct( $plugin );
// plugin upgrade path: maps automatically each semver to upgrade_to_x_y_z() protected method.
$this->upgrade_versions = array(
'2.0.0',
'2.0.4',
'2.1.5',
'2.2.0',
'2.3.0',
'3.0.2',
'3.2.0',
'3.7.1',
'3.8.3',
'4.7.0',
);
}
/**
* Performs plugin installation.
*
* @since 2.0.0
*/
protected function install() {
// create the db table for the customer index.
Gateway\Customer_Helper::create_table();
/**
* Fires upon plugin installed.
*
* @since 2.0.0
*
* @param string $version plugin version (available from v2.0.0)
*/
do_action( 'wc_square_installed', Plugin::VERSION );
}
/**
* Performs upgrade tasks.
*
* @since 2.0.0
*
* @param string $installed_version semver.
*/
protected function upgrade( $installed_version ) {
parent::upgrade( $installed_version );
/**
* Fires upon plugin upgraded (legacy hook).
*
* @since 1.0.0
*
* @param string $version version updating to (available from v2.0.0)
* @param string $version version updating from (available from v2.0.0)
*/
do_action( 'wc_square_updated', Plugin::VERSION, $installed_version );
}
/**
* Upgrades to version 2.0.0
*
* @since 2.0.0
*/
protected function upgrade_to_2_0_0() {
// create the db table for the customer index.
Gateway\Customer_Helper::create_table();
wc_set_time_limit( 300 );
// migrate all the things!
$syncing_products = $this->migrate_plugin_settings();
$this->migrate_gateway_settings();
$this->migrate_orders();
// only set the products "sync" status if v2 is now configured to sync products.
if ( $syncing_products ) {
$this->migrate_products();
// assume a last sync occurred before upgrading.
$this->get_plugin()->get_sync_handler()->set_last_synced_at();
$this->get_plugin()->get_sync_handler()->set_inventory_last_synced_at();
}
$this->migrate_customers();
// mark upgrade complete.
update_option( 'wc_square_updated_to_2_0_0', true );
}
/**
* Upgrades to version 2.0.4.
*
* @since 2.0.4
*/
protected function upgrade_to_2_0_4() {
$v1_settings = get_option( 'woocommerce_squareconnect_settings', array() );
$v2_settings = get_option( 'wc_square_settings', array() );
$v2_settings = $this->get_migrated_system_of_record( $v1_settings, $v2_settings );
update_option( 'wc_square_settings', $v2_settings );
}
/**
* Upgrades to version 2.1.5
*
* 2.1.5 updated the woocommerce_square_customers database schema.
*
* @see https://github.com/woocommerce/woocommerce-square/issues/359
* @since 2.1.5
*/
protected function upgrade_to_2_1_5() {
Gateway\Customer_Helper::create_table();
}
/**
* Adds the missing Square customer table.
*
* @see https://github.com/woocommerce/woocommerce-square/issues/825
* @since 3.2.0
*/
protected function upgrade_to_3_2_0() {
Gateway\Customer_Helper::create_table();
}
/**
* Deletes all transient data related to payment tokens.
*
* @see https://github.com/woocommerce/woocommerce-square/issues/1050
* @since 3.8.3
*/
protected function upgrade_to_3_8_3() {
wc_square()->get_gateway()->get_payment_tokens_handler()->clear_all_transients();
}
/**
* Upgrades to version 3.7.1
*
* This upgrade disables gift cards, and shows a notice to inform the merchant.
* Gift Cards are in beta status and not recommended for production.
*
* @see https://github.com/woocommerce/woocommerce-square/issues/1003
* @see https://github.com/woocommerce/woocommerce-square/issues/1009
* @since 3.7.1.
*/
protected function upgrade_to_3_7_1() {
// Early return if Gift Cards are already disabled.
$gateway_settings = get_option( 'woocommerce_square_credit_card_settings', array() );
if ( ! isset( $gateway_settings['enable_gift_cards'] ) || 'no' === $gateway_settings['enable_gift_cards'] ) {
return;
}
// Force-disable Gift Cards (only if store has it enabled).
$gateway_settings['enable_gift_cards'] = 'no';
update_option( 'woocommerce_square_credit_card_settings', $gateway_settings );
// Store an option to inform the merchant that Gift Cards has been force-disabled.
// Using version number in the option name so it's easy to remove in the future.
update_option( 'woocommerce_square_3_7_1_gift_cards_force_disable_notice', 'yes' );
}
/**
* Generates a milestone notice message.
*
* @since 2.1.7
*
* @param string $custom_message Custom text that notes what milestone was completed.
* @return string
*/
protected function generate_milestone_notice_message( $custom_message ) {
$message = '';
if ( $this->get_plugin()->get_reviews_url() ) {
// to be prepended at random to each milestone notice.
$exclamations = array(
__( 'Awesome', 'woocommerce-square' ),
__( 'Congratulations', 'woocommerce-square' ),
__( 'Great', 'woocommerce-square' ),
__( 'Fantastic', 'woocommerce-square' ),
);
$message = $exclamations[ array_rand( $exclamations ) ] . ', ' . esc_html( $custom_message ) . ' ';
$message .= sprintf(
/* translators: Placeholders: %1$s - plugin name, %2$s - <a> tag, %3$s - </a> tag, %4$s - <a> tag, %5$s - </a> tag */
__( 'Are you having a great experience with %1$s so far? Please consider %2$sleaving a review%3$s! If things aren\'t going quite as expected, we\'re happy to help -- please %4$sreach out to our support team%5$s.', 'woocommerce-square' ),
'<strong>' . esc_html( $this->get_plugin()->get_plugin_name() ) . '</strong>',
'<a href="' . esc_url( $this->get_plugin()->get_reviews_url() ) . '">',
'</a>',
'<a href="' . esc_url( $this->get_plugin()->get_support_url() ) . '">',
'</a>'
);
}
return $message;
}
/**
* Upgrades to version 2.2.0.
*
* @since 2.2.0
*/
protected function upgrade_to_2_2_0() {
$v1_settings = get_option( 'wc_square_settings', array() );
$v2_settings = $this->set_environment_location_id( $v1_settings );
update_option( 'wc_square_settings', $v2_settings );
}
/**
* Upgrades to version 2.3.0.
*
* @since 2.3.0
*/
protected function upgrade_to_2_3_0() {
// Set `enable_digital_wallets` default to no for existing stores
$gateway_settings = get_option( 'woocommerce_square_credit_card_settings', array() );
if ( ! isset( $gateway_settings['enable_digital_wallets'] ) ) {
$gateway_settings['enable_digital_wallets'] = 'no';
}
update_option( 'woocommerce_square_credit_card_settings', $gateway_settings );
}
/**
* Deletes the transient that holds locations data.
*
* @see https://github.com/woocommerce/woocommerce-square/issues/786#issuecomment-1121388650
*
* @since 3.0.2
*/
protected function upgrade_to_3_0_2() {
delete_transient( 'wc_square_locations' );
}
/**
* Upgrades to version 4.7.0.
*
* @since 4.7.0
*/
protected function upgrade_to_4_7_0() {
// Skip if already upgraded.
if ( get_option( 'wc_square_updated_to_4_7_0' ) ) {
return;
}
// Migrate Gift Cards settings.
$this->migrate_gateway_settings_dynamically(
'woocommerce_square_credit_card_settings',
'woocommerce_gift_cards_pay_settings',
array(
'enable_gift_cards' => 'enabled',
)
);
// Migrate Gateway fields to the Square settings.
$this->migrate_gateway_settings_dynamically(
'woocommerce_square_credit_card_settings',
'wc_square_settings',
array(
'debug_mode' => 'debug_mode',
'enable_customer_decline_messages' => 'enable_customer_decline_messages',
),
false
);
// Mark upgrade complete.
update_option( 'wc_square_updated_to_4_7_0', true );
// Skip redirect existing users to the setup wizard on upgrade.
add_option( 'wc_square_show_wizard_on_activation', true, '', 'no' );
// Mark the onboarding wizard as visited for existing users.
add_option( 'wc_square_connected_page_visited', true, '', 'no' );
}
/**
* Migrates plugin settings from v1 to v2.
*
* @see Lifecycle::upgrade_to_2_0_0()
*
* @since 2.0.0
*
* @return bool whether a Sync setting was enabled from migration
*/
private function migrate_plugin_settings() {
$this->get_plugin()->log( 'Migrating plugin settings...' );
// get legacy and new default settings.
$new_settings = get_option( 'wc_square_settings', array() );
$legacy_settings = get_option( 'woocommerce_squareconnect_settings', array() );
$email_settings = get_option( 'woocommerce_wc_square_sync_completed_settings', array() );
// bail if they already have v2 settings present.
if ( ! empty( $new_settings ) ) {
return;
}
// handle access token first.
$legacy_access_token = get_option( 'woocommerce_square_merchant_access_token' );
if ( $legacy_access_token ) {
// the access token was previously stored unencrypted.
if ( ! empty( $legacy_access_token ) && Utilities\Encryption_Utility::is_encryption_supported() ) {
$encryption = new Utilities\Encryption_Utility();
try {
$legacy_access_token = $encryption->encrypt_data( $legacy_access_token );
} catch ( \Exception $exception ) {
// log the event, but don't halt the process.
$this->get_plugin()->log( 'Could not encrypt access token during upgrade. ' . $exception->getMessage() );
}
}
// previously only 'production' environment was assumed.
$access_tokens = get_option( 'wc_square_access_tokens', array() );
$access_tokens['production'] = is_string( $legacy_access_token ) ? $legacy_access_token : '';
update_option( 'wc_square_access_tokens', $access_tokens );
}
// migrate store location.
if ( ! empty( $legacy_settings['location'] ) ) {
$new_settings['location_id'] = $legacy_settings['location'];
}
// toggle debug logging.
$new_settings['debug_logging_enabled'] = isset( $legacy_settings['logging'] ) && in_array( $legacy_settings['logging'], array( 'yes', 'no' ), true ) ? $legacy_settings['logging'] : 'no';
// set the SOR and inventory sync values.
$new_settings = $this->get_migrated_system_of_record( $legacy_settings, $new_settings );
// migrate email notification settings: if there's a recipient, we enable email and pass recipient(s) to email setting.
if ( isset( $legacy_settings['sync_email'] ) && is_string( $legacy_settings['sync_email'] ) && '' !== trim( $legacy_settings['sync_email'] ) ) {
$email_settings['enabled'] = 'yes';
$email_settings['recipient'] = $legacy_settings['sync_email'];
} else {
$email_settings['enabled'] = 'no';
$email_settings['recipient'] = '';
}
// save email settings.
update_option( 'woocommerce_wc_square_sync_completed_settings', $email_settings );
// save plugin settings.
update_option( 'wc_square_settings', $new_settings );
$this->get_plugin()->log( 'Plugin settings migration complete.' );
return isset( $new_settings['system_of_record'] ) && Settings::SYSTEM_OF_RECORD_DISABLED !== $new_settings['system_of_record'];
}
/**
* Migrates gateway settings from v1 to v2.
*
* @see Lifecycle::upgrade_to_2_0_0()
*
* @since 2.0.0
*/
private function migrate_gateway_settings() {
$this->get_plugin()->log( 'Migrating gateway settings...' );
$legacy_settings = get_option( 'woocommerce_square_settings', array() );
$new_settings = get_option( 'woocommerce_square_credit_card_settings', array() );
// bail if they already have v2 settings present.
if ( ! empty( $new_settings ) ) {
return;
}
if ( isset( $legacy_settings['enabled'] ) ) {
$new_settings['enabled'] = 'yes' === $legacy_settings['enabled'] ? 'yes' : 'no';
}
if ( isset( $legacy_settings['title'] ) && is_string( $legacy_settings['title'] ) ) {
$new_settings['title'] = $legacy_settings['title'];
}
if ( isset( $legacy_settings['description'] ) && is_string( $legacy_settings['description'] ) ) {
$new_settings['description'] = $legacy_settings['description'];
}
// note: the following is not an error, the setting on v1 intends "delayed" capture, hence authorization only, if set.
if ( isset( $legacy_settings['capture'] ) ) {
$new_settings['transaction_type'] = 'yes' === $legacy_settings['capture'] ? Gateway::TRANSACTION_TYPE_AUTHORIZATION : Gateway::TRANSACTION_TYPE_CHARGE;
}
// not quite the same, since tokenization is a new thing, but we could presume the intention to let customers save their payment details.
if ( isset( $legacy_settings['create_customer'] ) ) {
$new_settings['tokenization'] = 'yes' === $legacy_settings['create_customer'] ? 'yes' : 'no';
}
if ( isset( $legacy_settings['logging'] ) ) {
$new_settings['debug_mode'] = 'yes' === $legacy_settings['logging'] ? 'log' : 'off';
}
// there was no card types setting in v1.
$new_settings['card_types'] = array(
'VISA',
'MC',
'AMEX',
'JCB',
// purposefully omit dinersclub & discover.
);
// save migrated settings.
update_option( 'woocommerce_square_credit_card_settings', $new_settings );
$this->get_plugin()->log( 'Gateway settings migration complete.' );
}
/**
* Migrates gateway settings dynamically.
*
* @param string $legacy_option legacy option name.
* @param string $new_option new option name.
* @param array $fields fields to migrate.
*
* @since 4.7.0
*/
private function migrate_gateway_settings_dynamically( $legacy_option, $new_option, $fields = array(), $bail = true ) {
$this->get_plugin()->log(
sprintf(
// translators: 1: legacy option name, 2: new option name.
__(
'Migrating gateway settings from %1$s to %2$s...',
'woocommerce-square'
),
$legacy_option,
$new_option
)
);
$legacy_settings = get_option( $legacy_option, array() );
$new_settings = get_option( $new_option, array() );
// Bail if they already have new settings present.
if ( ! empty( $new_settings ) && $bail ) {
return;
}
// Migrate the fields.
foreach ( $fields as $legacy_field => $new_field ) {
if ( isset( $legacy_settings[ $legacy_field ] ) ) {
$new_settings[ $new_field ] = $legacy_settings[ $legacy_field ];
}
}
// Save migrated settings.
update_option( $new_option, $new_settings );
$this->get_plugin()->log(
sprintf(
// translators: 1: legacy option name, 2: new option name.
__(
'Gateway settings migration from %1$s to %2$s completed.',
'woocommerce-square'
),
$legacy_option,
$new_option
)
);
}
/**
* Migrates order data from v1 to v2.
*
* @see Lifecycle::upgrade_to_2_0_0()
*
* @since 2.0.0
*/
private function migrate_orders() {
global $wpdb;
$this->get_plugin()->log( 'Migrating orders data...' );
$wpdb->update( $wpdb->postmeta, array( 'meta_key' => '_wc_square_credit_card_charge_captured' ), array( 'meta_key' => '_square_charge_captured' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
// move payment ID to new gateway ID meta key value.
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->postmeta,
array(
'meta_value' => 'square_credit_card', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
),
array(
'meta_key' => '_payment_method', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'meta_value' => 'square', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
)
);
$this->get_plugin()->log( 'Orders migration complete.' );
}
/**
* Migrates product data from v1 to v2.
*
* @see Lifecycle::upgrade_to_2_0_0()
*
* @since 2.0.0
*/
private function migrate_products() {
global $wpdb;
$this->get_plugin()->log( 'Migrating products data...' );
// the handling in v1 was reversed, so we want products where sync wasn't disabled.
$legacy_product_ids = get_posts(
array(
'nopaging' => true,
'post_type' => 'product',
'post_status' => 'all',
'fields' => 'ids',
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'relation' => 'OR',
array(
'key' => '_wcsquare_disable_sync',
'value' => 'no',
),
array(
'key' => '_wcsquare_disable_sync',
'compare' => 'NOT EXISTS',
),
),
)
);
// in v2 we turn those products as flagged to be sync-enabled instead.
if ( ! empty( $legacy_product_ids ) ) {
$failed_products = array();
// ensure taxonomy is registered at this stage.
if ( ! taxonomy_exists( Product::SYNCED_WITH_SQUARE_TAXONOMY ) ) {
Product::init_taxonomies();
}
// will not create the term if already exists.
wp_create_term( 'yes', Product::SYNCED_WITH_SQUARE_TAXONOMY );
// set Square sync status via taxonomy term.
foreach ( $legacy_product_ids as $i => $product_id ) {
$set_term = wp_set_object_terms( $product_id, array( 'yes' ), Product::SYNCED_WITH_SQUARE_TAXONOMY );
if ( ! is_array( $set_term ) ) {
unset( $legacy_product_ids[ $i ] );
$failed_products[] = $product_id;
}
}
// log any errors.
if ( ! empty( $failed_products ) ) {
$this->get_plugin()->log( 'Could not update sync with Square status for products with ID: ' . implode( ', ', array_unique( $failed_products ) ) . '.' );
}
}
$this->get_plugin()->log( 'Products migration complete.' );
}
/**
* Migrates customer data.
*
* @since 2.0.0
*/
private function migrate_customers() {
global $wpdb;
$this->get_plugin()->log( 'Migrating customer data.' );
$rows = (int) $wpdb->update( $wpdb->usermeta, array( 'meta_key' => 'wc_square_customer_id' ), array( 'meta_key' => '_square_customer_id' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.DirectQuery
$this->get_plugin()->log( sprintf( '%d customers migrated', $rows ) );
}
/**
* Adds the Sync setting setting to the v2 plugin settings depending on v1 setting values.
*
* @since 2.0.2
*
* @param array $v1_settings v1 plugin settings.
* @param array $v2_settings v2 plugin settings.
* @return array
*/
private function get_migrated_system_of_record( $v1_settings, $v2_settings ) {
$sync_products = isset( $v1_settings['sync_products'] ) && 'yes' === $v1_settings['sync_products'];
$sync_inventory = $sync_products && isset( $v1_settings['sync_inventory'] ) && 'yes' === $v1_settings['sync_inventory'];
$inventory_polling = isset( $v1_settings['inventory_polling'] ) && 'yes' === $v1_settings['inventory_polling'];
$v2_settings['system_of_record'] = $sync_products && $inventory_polling ? Settings::SYSTEM_OF_RECORD_SQUARE : Settings::SYSTEM_OF_RECORD_DISABLED;
$v2_settings['enable_inventory_sync'] = $inventory_polling || $sync_inventory ? 'yes' : 'no';
return $v2_settings;
}
/**
* Adds environment specific location_id to, and removes general location_id from v1 setting array.
*
* @since 2.2.0
*
* @param array $v1_settings v1 plugin settings.
* @return array
*/
private function set_environment_location_id( $v1_settings ) {
$environment = isset( $v1_settings['enable_sandbox'] ) && 'yes' === $v1_settings['enable_sandbox'] ? 'sandbox' : 'production';
if ( ! isset( $v1_settings[ $environment . '_location_id' ] ) ) {
$v1_location_id = isset( $v1_settings['location_id'] ) ? $v1_settings['location_id'] : '';
$v1_settings[ $environment . '_location_id' ] = $v1_location_id;
}
return $v1_settings;
}
}